feat(editorjs): add batteries-included bundle package and make core headless - #183
feat(editorjs): add batteries-included bundle package and make core headless#183gohabereg wants to merge 11 commits into
Conversation
Unit Tests
Mutation Tests
|
…eadless Introduce `@editorjs/editorjs`, a batteries-included entry point that composes `@editorjs/core` with the default DOM adapter, collaboration, tools, plugins, and UI, auto-initializes in its constructor, and exposes an `isReady` promise. It honors a v2-style `config.tools` map merged over the defaults with override-by-name. To support it, `Core` becomes a headless engine: - Remove the hardcoded default tool/plugin/infra `use()` calls from the constructor; drop the corresponding dependencies. - `initialize()` now fails loudly: it throws when no rendering adapter is registered or the `defaultBlock` tool is missing, and re-throws init failures instead of swallowing them. `ShortcutsPlugin` stays in `@editorjs/core` (no longer auto-registered); its relocation is handled by a separate change. Free the `@editorjs/editorjs` package name by aliasing the published v2 dependency as `editorjs-v2` (`npm:@editorjs/editorjs@…`) across core, sdk, ui, and tools/paragraph. The playground now consumes `@editorjs/editorjs` instead of wiring `Core` and UI by hand. Specs updated via OpenSpec change `add-editorjs-bundle-package` (archived). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
433933c to
182b01d
Compare
…gration test Core no longer depends on `@editorjs/dom-adapters`, so its dist is not built in CI when checking `@editorjs/core`. The leftover `jest.unstable_mockModule` call still resolved the specifier, failing the suite (and the Stryker dry run) with "Cannot find module '@editorjs/dom-adapters'". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Runs lint, build and unit tests for the new bundle package on pull requests and merge groups, mirroring the other package workflows. Mutation tests are disabled since the package has no Stryker config yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`tsc --build` only produces output for TypeScript project references, so `@editorjs/ui` — which is bundled by vite — had no `dist/ui.js` after building `@editorjs/editorjs` from a clean checkout. Jest then failed to resolve `@editorjs/ui` in the bundle test. Build dependencies first via `yarn workspaces foreach`, mirroring how the playground package builds its own dependency graph. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a new batteries-included @editorjs/editorjs bundle package and refactors @editorjs/core into a truly headless engine by removing hardcoded defaults and adding fail-loud precondition checks. It also aliases the legacy v2 @editorjs/editorjs dependency as editorjs-v2 across the workspace to free the @editorjs/editorjs name, and updates the playground to consume the new bundle.
Changes:
- Add
packages/editorjs(@editorjs/editorjs) that composesCore+ DOM adapter + collaboration + default tools/plugins + UI and auto-initializes withisReady. - Make
@editorjs/coreheadless (no defaultuse()registrations) and add synchronous initialization precondition validation. - Alias v2
@editorjs/editorjsaseditorjs-v2and update imports/dependencies; switch the playground to the new bundle.
Reviewed changes
Copilot reviewed 46 out of 47 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Updates workspace lockfile for new package and editorjs-v2 aliasing. |
| packages/ui/package.json | Replaces direct v2 @editorjs/editorjs dependency with editorjs-v2 alias. |
| packages/tools/paragraph/src/index.ts | Updates v2 type imports to use editorjs-v2. |
| packages/tools/paragraph/package.json | Swaps v2 @editorjs/editorjs dependency for editorjs-v2 alias. |
| packages/sdk/src/tools/facades/BlockTuneFacade.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/tools/facades/BlockToolFacade.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/tools/facades/BaseToolFacade.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/entities/InlineTool.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/entities/EventBus/events/core/BlockAddedCoreEvent.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/entities/Config.ts | Updates EditorConfig type source to editorjs-v2. |
| packages/sdk/src/entities/BlockTune.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/entities/BlockTool.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/entities/BaseTool.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/src/api/BlocksAPI.ts | Updates v2 type imports to use editorjs-v2. |
| packages/sdk/package.json | Replaces v2 @editorjs/editorjs dependency with editorjs-v2 alias. |
| packages/playground/src/App.vue | Switches from manual Core+UI wiring to new EditorJS() bundle usage. |
| packages/playground/package.json | Simplifies deps by depending on @editorjs/editorjs bundle. |
| packages/editorjs/tsconfig.json | Adds TS project config for new bundle package. |
| packages/editorjs/tsconfig.eslint.json | Adds ESLint TS project config for the new package. |
| packages/editorjs/tsconfig.build.json | Adds build-specific TS project config for the new package. |
| packages/editorjs/src/mergeTools.ts | Adds override-by-name merge helper for default + user tool constructors. |
| packages/editorjs/src/mergeTools.spec.ts | Adds unit tests for tool merging semantics. |
| packages/editorjs/src/index.ts | Implements EditorJS bundle class composition + isReady. |
| packages/editorjs/src/index.spec.ts | Adds bundle tests for composition and isReady resolve/reject behavior. |
| packages/editorjs/package.json | Defines the new @editorjs/editorjs workspace package and scripts. |
| packages/editorjs/jest.config.ts | Adds Jest config for new package tests (ESM + TS). |
| packages/editorjs/eslint.config.mjs | Adds ESLint config for the new package. |
| packages/editorjs/.gitignore | Adds package-local ignores for build/test artifacts. |
| packages/core/tsconfig.json | Removes build references to defaults now moved out of core. |
| packages/core/tsconfig.build.json | Removes build references to defaults now moved out of core. |
| packages/core/src/utils/composeDataFromVersion2.ts | Updates v2 type imports to use editorjs-v2. |
| packages/core/src/tools/ToolsManager.ts | Updates v2 type imports to use editorjs-v2. |
| packages/core/src/tools/ToolsFactory.ts | Updates v2 type imports to use editorjs-v2. |
| packages/core/src/index.ts | Removes default registrations and adds precondition validation + rethrow behavior. |
| packages/core/src/components/BlockRenderer.ts | Switches v2 imports to editorjs-v2. |
| packages/core/src/components/BlockManager.ts | Switches v2 imports to editorjs-v2. |
| packages/core/src/api/BlocksAPI.integration.spec.ts | Adjusts test scaffolding/mocks in response to refactors. |
| packages/core/package.json | Drops baked-in defaults; adds editorjs-v2 alias dependency. |
| openspec/specs/editorjs-bundle/spec.md | Adds main spec for the new bundle capability. |
| openspec/specs/core/spec.md | Updates core spec to reflect headless + fail-loud initialization behavior. |
| openspec/changes/archive/2026-07-22-add-editorjs-bundle-package/tasks.md | Archives change tasks and implementation checklist. |
| openspec/changes/archive/2026-07-22-add-editorjs-bundle-package/specs/editorjs-bundle/spec.md | Archives delta spec for new bundle capability. |
| openspec/changes/archive/2026-07-22-add-editorjs-bundle-package/specs/core/spec.md | Archives delta spec for core headless behavior changes. |
| openspec/changes/archive/2026-07-22-add-editorjs-bundle-package/proposal.md | Archives the change proposal for traceability. |
| openspec/changes/archive/2026-07-22-add-editorjs-bundle-package/design.md | Archives the design decisions and trade-offs. |
| openspec/changes/archive/2026-07-22-add-editorjs-bundle-package/.openspec.yaml | Adds archived change metadata for OpenSpec workflow. |
| .github/workflows/editorjs.yml | Adds CI workflow to run package-check for @editorjs/editorjs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch (error) { | ||
| console.error('Editor.js initialization failed', error); | ||
|
|
||
| throw error; | ||
| } |
| import { TOKENS } from '../tokens.js'; | ||
| import ToolsManager from '../tools/ToolsManager.js'; | ||
| import { BlockAPI } from '@editorjs/editorjs'; | ||
| import { BlockAPI } from 'editorjs-v2'; | ||
| import { |
| import { TOKENS } from '../tokens.js'; | ||
| import ToolsManager from '../tools/ToolsManager.js'; | ||
| import { BlockToolData } from '@editorjs/editorjs'; | ||
| import { BlockToolData } from 'editorjs-v2'; | ||
| import { CoreConfigValidated, EventBus } from '@editorjs/sdk'; | ||
|
|
| }); | ||
|
|
||
| core | ||
| .use(EditorjsUI) | ||
| .use(BlocksUI) | ||
| .use(InlineToolbarUI) | ||
| .use(ToolbarUI) | ||
| .use(ToolboxUI) | ||
| .initialize(); | ||
| void editor.isReady; | ||
| }); |
| if (userTools !== undefined) { | ||
| for (const [name, tool] of Object.entries(userTools)) { | ||
| merged.set(name, tool); | ||
| } | ||
| } |
| ## Purpose | ||
| TBD - created by archiving change add-editorjs-bundle-package. Update Purpose after archive. | ||
| ## Requirements |
- Core.initialize() no longer logs to console.error before re-throwing (dead try/catch removed) - BlockRenderer/BlockManager import editorjs-v2 types with `import type` instead of pulling it in as a runtime dependency - Playground attaches a .catch() to editor.isReady instead of discarding the promise, avoiding an unhandled rejection - mergeTools() now throws when a config.tools key doesn't match the tool's static name, since Core looks up tools by name, not map key - editorjs-bundle spec: replaced TBD Purpose and documented the new fail-loud mismatch scenario Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| /** | ||
| * Default plugins. | ||
| */ | ||
| this.#core.use(ClipboardPlugin); |
There was a problem hiding this comment.
ShortcutsPlugin isn't registered here, however it was mentioned in openspec/specs/editorjs-bundle/spec.md as one of default plugins.
There was a problem hiding this comment.
Ok, read design.md, it explicitly defer this to a separate change.
Still PR desc doesn't reflect that: "What Changes" lists default plugins as "clipboard, shortcuts", and "Verification" claims Cmd+B was verified end-to-end through the bundle – neither matches this diff.
Worth updating description?
| * Validates that the caller has registered everything the headless engine needs before booting. | ||
| * Throws with a clear message instead of failing deep inside module resolution. | ||
| */ | ||
| #validatePreconditions(): void { |
There was a problem hiding this comment.
Worth adding tests that constructs a real Core, registers a stub adapter/tool/plugin via .use(), and cover throw/no-throw paths?
| this.use(CollaborationManager); | ||
| this.use(DOMAdapters); | ||
| this.use(ClipboardPlugin); | ||
| } |
There was a problem hiding this comment.
packages/ui/index.html still does new Core({...}).use(EditorjsUI).use(BlocksUI).use(InlineToolbarUI).use(ToolboxUI).initialize() with no adapter and no block tool registered. That will now throw "No rendering adapter registered" immediately.
Worth registering the missing things directly?
Resolve conflicts in favor of the headless-core direction: `@editorjs/core` keeps no default tool/plugin/infra registrations, dependencies, or project references, so main's `@editorjs/shortcuts-plugin` wiring in core is dropped. Register `ShortcutsPlugin` from the newly extracted `@editorjs/shortcuts-plugin` package in the `@editorjs/editorjs` bundle instead, which is where default plugins now live. The core spec's keyboard-shortcuts requirement is removed (it moved to `openspec/specs/shortcuts-plugin/spec.md` on main), and the bundle spec's default-composition scenario now lists shortcuts alongside clipboard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ange The `@editorjs/shortcuts-plugin` extraction landed on main while this PR was open, so the archived `add-editorjs-bundle-package` change is updated in place rather than superseded: the bundle now registers `ShortcutsPlugin` from that package instead of deferring it. - delta + main `editorjs-bundle` spec: default composition lists clipboard and shortcuts - proposal/design: shortcuts is consumed from `@editorjs/shortcuts-plugin`, not left behind in core - tasks 2.3/3.2/3.3/3.4 reflect what shipped; 4.6 added to re-verify CMD+B end-to-end through the bundle Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`packages/ui/index.html` constructed `Core` with no rendering adapter and no block tool, relying on the defaults core used to register itself. Now that `initialize()` validates its preconditions, that page threw immediately. Register `DOMAdapters` and `Paragraph` explicitly. While wiring that up: `@editorjs/dom-adapters`, `@editorjs/core` and `editorjs-v2` were declared as production dependencies of `@editorjs/ui` but never imported by `packages/ui/src` — dom-adapters has been unused since the package was bootstrapped in #96. Only the demo page needs core and dom-adapters, so they move to devDependencies alongside the new `@editorjs/paragraph`; `editorjs-v2` is dropped outright. The matching stale entries are removed from the vite externals list and from both tsconfigs' project references, which were forcing needless build-order edges from ui to core and dom-adapters. The built bundle is byte-identical (85.72 kB / 67.57 kB, 28 modules), confirming the removed externals were no-ops. Demo page verified in a browser: renders all three paragraphs, no console errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The headless refactor made `initialize()` validate that a rendering adapter and the `defaultBlock` tool are registered, but core had no `index.spec.ts` at all, so the new contract — and the two error messages callers are meant to act on — was untested while `openspec/specs/core/spec.md` already carried its scenarios. Drives a real `Core` with real IoC containers and model, registering stub adapter/block tool/plugin through `use()`, per review feedback on PR #183: - throws naming the rendering adapter when none is registered - throws naming `defaultBlock` when it is missing, and when a differently named block tool is registered instead - resolves when adapter, default block tool and plugin are all registered - constructs registered plugins during boot No DOM environment is needed: with an empty document and stub registrations the boot sequence never reaches into the holder, so this runs under core's existing `node` test environment with no new dependencies. Core: 166/166 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ducer Re-verified in the playground: registering `ShortcutsPlugin` in the bundle is not enough to make CMD+B work. The plugin subscribes to `ui:key-down`, but no code on this branch dispatches `KeydownUIEvent` — `Blocks.ts` handles CMD+Z/CMD+SHIFT+Z inline and returns early for every other key. The dispatch arrives with PR #184. Isolated the failure: the inline toolbar's B button applies `<b>` to the same selection, so formatting works and only the shortcut path is dead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Why
Default tools, plugins, infra (DOM adapter, collaboration), and UI were hardcoded into
Core's constructor, so@editorjs/corecouldn't be consumed as a pure engine and there was no batteries-included package a v2 user could drop in.What Changes
@editorjs/editorjs(packages/editorjs): a batteries-included entry point whose default exportEditorJScomposesCore+DOMAdapters+CollaborationManager+ default tools (paragraph, bold, italic, link) + plugins (clipboard, shortcuts) + UI. It auto-initializes in the constructor and exposes anisReadypromise, and honors a v2-styleconfig.toolsmap merged over the defaults with override-by-name.Coreis now headless (BREAKING): the constructor no longer registers any default tools/plugins/infra/UI.initialize()fails loudly — it throws when no rendering adapter is registered or thedefaultBlocktool is missing, and re-throws init failures instead of swallowing them withconsole.error.ShortcutsPluginis consumed from@editorjs/shortcuts-plugin— extracted from core by feat(core): extract ShortcutsPlugin into @editorjs/shortcuts-plugin #180, which landed onmainwhile this PR was open and is merged in here. The bundle registers it as a default plugin; core neither ships nor registers it.packages/ui/index.htmlfixed: the demo page constructedCorewith no adapter and no block tool, relying on the defaults core used to register itself, so it threw under the new fail-loud validation. It now registersDOMAdaptersandParagraphexplicitly.@editorjs/ui:@editorjs/dom-adapters,@editorjs/coreandeditorjs-v2were production dependencies thatpackages/ui/srcnever imported (dom-adapters has been unused since the package was bootstrapped in chore(ui): ui package bootstrapped #96). Core and dom-adapters move to devDependencies for the demo page;editorjs-v2is dropped. Matching stale entries removed from the vite externals list and both tsconfigs' project references.@editorjs/editorjsv2 dep is aliased aseditorjs-v2(npm:@editorjs/editorjs@…) incore,sdk,tools/paragraphto free the package name. Non-behavioral; import specifiers only.@editorjs/editorjsinstead of wiringCore+ UI by hand.Verification
packages/core/src/index.spec.tscovers the fail-loud contract, which previously had no tests at all — core had noindex.spec.ts. It drives a realCore(real IoC containers and model) with stub adapter/block tool/plugin registered viause(), asserting both throwing paths (missing adapter, missing/mismatcheddefaultBlock) and the succeeding path. No DOM environment needed: with an empty document and stub registrations the boot sequence never reaches the holder.packages/ui/index.htmlverified in-browser: renders all three paragraphs, no console errors. The ui build output is byte-identical after the dependency cleanup (85.72 kB / 67.57 kB, 28 modules), confirming the removed externals were no-ops.<b>to the selection — all through the bundle.Known gap: keyboard shortcuts are inert until #184
CMD+B does not work yet, and cannot on this branch.
ShortcutsPluginsubscribes toui:key-down(KeydownUIEvent), but nothing dispatches that event here —packages/ui/src/Blocks/Blocks.tshandles CMD+Z / CMD+SHIFT+Z locally and returns early for every other key. Registering the plugin is necessary but not sufficient; the producer side is theBlocksUIkeydown dispatch in #184.Isolated in the browser: CMD+B on a live selection leaves the block HTML unchanged, while the inline toolbar's B button applies
<b>to that same selection — so formatting works and only the shortcut path is dead. Tracked as task 4.6 in the archived change, marked blocked.(An earlier revision of this description claimed CMD+B was verified end-to-end. That was wrong — thanks @Reversean for pushing on it.)
Specs
Implemented via OpenSpec change
add-editorjs-bundle-package(archived in this PR). Because the shortcuts extraction landed mid-review, the archived change was updated in place rather than superseded: proposal, design, tasks and theeditorjs-bundledelta now reflect shortcuts being registered from its own package. Main specs updated:core(headless + fail-loud scenarios, now with a validation pointer to the new spec file) and the neweditorjs-bundlecapability.