Skip to content

feat(editorjs): add batteries-included bundle package and make core headless - #183

Open
gohabereg wants to merge 11 commits into
mainfrom
feat/editorjs-bundle-package
Open

feat(editorjs): add batteries-included bundle package and make core headless#183
gohabereg wants to merge 11 commits into
mainfrom
feat/editorjs-bundle-package

Conversation

@gohabereg

@gohabereg gohabereg commented Jul 22, 2026

Copy link
Copy Markdown
Member

Why

Default tools, plugins, infra (DOM adapter, collaboration), and UI were hardcoded into Core's constructor, so @editorjs/core couldn't be consumed as a pure engine and there was no batteries-included package a v2 user could drop in.

What Changes

  • New package @editorjs/editorjs (packages/editorjs): a batteries-included entry point whose default export EditorJS composes Core + DOMAdapters + CollaborationManager + default tools (paragraph, bold, italic, link) + plugins (clipboard, shortcuts) + UI. It auto-initializes in the constructor and exposes an isReady promise, and honors a v2-style config.tools map merged over the defaults with override-by-name.
  • Core is 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 the defaultBlock tool is missing, and re-throws init failures instead of swallowing them with console.error.
  • ShortcutsPlugin is consumed from @editorjs/shortcuts-plugin — extracted from core by feat(core): extract ShortcutsPlugin into @editorjs/shortcuts-plugin #180, which landed on main while 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.html fixed: the demo page constructed Core with 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 registers DOMAdapters and Paragraph explicitly.
  • Dead deps dropped from @editorjs/ui: @editorjs/dom-adapters, @editorjs/core and editorjs-v2 were production dependencies that packages/ui/src never 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-v2 is dropped. Matching stale entries removed from the vite externals list and both tsconfigs' project references.
  • Dependency alias: the published @editorjs/editorjs v2 dep is aliased as editorjs-v2 (npm:@editorjs/editorjs@…) in core, sdk, tools/paragraph to free the package name. Non-behavioral; import specifiers only.
  • Playground now consumes @editorjs/editorjs instead of wiring Core + UI by hand.

Verification

  • Full workspace build, lint, and tests pass. Core: 166/166. Bundle: 8/8.
  • New packages/core/src/index.spec.ts covers the fail-loud contract, which previously had no tests at all — core had no index.spec.ts. It drives a real Core (real IoC containers and model) with stub adapter/block tool/plugin registered via use(), asserting both throwing paths (missing adapter, missing/mismatched defaultBlock) 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.html verified 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.
  • Playground verified end-to-end in-browser: renders the configured paragraphs, typing updates the model, the inline toolbar appears on selection, and its B button applies <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. ShortcutsPlugin subscribes to ui:key-down (KeydownUIEvent), but nothing dispatches that event here — packages/ui/src/Blocks/Blocks.ts handles 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 the BlocksUI keydown 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 the editorjs-bundle delta 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 new editorjs-bundle capability.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Unit Tests

Package Coverage Delta
@editorjs/core 73.11% -17.36% 🔴
@editorjs/clipboard-plugin 66.66% 0% ⚪️
@editorjs/collaboration-manager 85.81% 0% ⚪️
@editorjs/dom-adapters 86.95% 0% ⚪️
@editorjs/ot-server 20% 0% ⚪️
@editorjs/model N/A N/A
@editorjs/editorjs 100% N/A
@editorjs/shortcuts-plugin 100% 0% ⚪️

Mutation Tests

Package Mutation score Dashboard URL
@editorjs/clipboard-plugin No files to mutate found.
@editorjs/dom-adapters No files to mutate found.
@editorjs/collaboration-manager No files to mutate found.
@editorjs/core 52.52% 🔴 Dashboard
@editorjs/shortcuts-plugin No files to mutate found.

…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>
@gohabereg
gohabereg force-pushed the feat/editorjs-bundle-package branch from 433933c to 182b01d Compare July 22, 2026 21:29
gohabereg and others added 4 commits July 22, 2026 22:46
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 composes Core + DOM adapter + collaboration + default tools/plugins + UI and auto-initializes with isReady.
  • Make @editorjs/core headless (no default use() registrations) and add synchronous initialization precondition validation.
  • Alias v2 @editorjs/editorjs as editorjs-v2 and 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.

Comment thread packages/core/src/index.ts Outdated
Comment on lines +178 to +182
} catch (error) {
console.error('Editor.js initialization failed', error);

throw error;
}
Comment on lines 11 to 14
import { TOKENS } from '../tokens.js';
import ToolsManager from '../tools/ToolsManager.js';
import { BlockAPI } from '@editorjs/editorjs';
import { BlockAPI } from 'editorjs-v2';
import {
Comment on lines 16 to 20
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';

Comment on lines 62 to 65
});

core
.use(EditorjsUI)
.use(BlocksUI)
.use(InlineToolbarUI)
.use(ToolbarUI)
.use(ToolboxUI)
.initialize();
void editor.isReady;
});
Comment on lines +22 to +26
if (userTools !== undefined) {
for (const [name, tool] of Object.entries(userTools)) {
merged.set(name, tool);
}
}
Comment on lines +3 to +5
## 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ShortcutsPlugin isn't registered here, however it was mentioned in openspec/specs/editorjs-bundle/spec.md as one of default plugins.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

gohabereg and others added 5 commits August 4, 2026 19:09
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>
@gohabereg
gohabereg requested a review from Reversean August 4, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants