LearnNewsExamplesServices
Frontmatter
id18823
titleDecide whether dist/mermaid.mjs ships by default — 5.0 MB, lazy, and rebuildable by the scaffold
stateOpen
labels
enhancementaiarchitecturebuild
assignees[]
createdAtSep 17, 2026, 2:56 PM
updatedAtOct 8, 2026, 6:59 AM
githubUrlhttps://github.com/neomjs/neo/issues/18823
authorneo-opus-vega
commentsCount5
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

Decide whether dist/mermaid.mjs ships by default — 5.0 MB, lazy, and rebuildable by the scaffold

Open Backlog/active-chunk-23 enhancementaiarchitecturebuild
neo-opus-vega
neo-opus-vega commented on Sep 17, 2026, 2:56 PM

Context

#18814's third question asked whether generated dist/ bundles should ship in the npm package at all. It answered no change, on the grounds that none of them can be produced on the consumer side — every source package (parse5, marked, mermaid, monaco-editor, esbuild) is an engine devDependency.

That premise is false for the path most consumers take, and the correction is @tobiu's.

neomjs/create-app scaffolds a workspace whose postinstall is cd node_modules/neo.mjs && npm i, so the engine's own devDependencies are installed inside node_modules/neo.mjs. The engine's bundle scripts resolve their output relative to their own location — buildScripts/build/parse5.mjs writes to path.join(__dirname, '../../dist/parse5.mjs') — so running them from a scaffolded root repopulates the engine's own dist/ in place. Verified against create-app@d8ae9ffa41 and neomjs/neo@48021153ff.

So a npx neo-app workspace can rebuild these. A direct npm i neo.mjs cannot. The decision turns on which of those the package is for, and #18814 answered it without that distinction available.

Measured package composition, npm pack --dry-run --json on dev at 48021153ff, 46.63 MiB unpacked across 5655 files:

tree size share
resources/examples 13.21 MiB 28%
test/playwright 8.52 MiB 18%
apps/portal 5.87 MiB 13%
dist/ — all five bundles 5.45 MiB 12%
src/ 5.39 MiB 12%

Inside dist/, the split is extreme:

bundle size share of dist
mermaid.mjs 5156 KiB 95%
parse5.mjs 163 KiB 3%
highlight/* (2 files) 216 KiB 4%
marked.mjs 46 KiB 1%

The Problem

Dropping the four small bundles saves 0.9% of the package and risks an engine that cannot boot: dist/parse5.mjs and dist/marked.mjs are imported at module scope by src/functional/util/HtmlTemplateProcessor.mjs and src/component/Markdown.mjs. That trade is not worth making, whatever the rebuild story.

mermaid.mjs is the only one where every property points the other way:

  • 5.0 MB, 95% of dist/ and 11% of the whole package
  • dynamically imported, not at module scope — src/main/addon/Mermaid.mjs sets useLazyLoading, so nothing fetches it until a page renders a diagram
  • already redirectable — main.addon.Mermaid exposes a mermaidPath config
  • rebuildable in a scaffolded workspace, per the Context above

That combination — large, optional, lazily loaded, already redirectable, rebuildable — belongs to no other bundle here.

The Architectural Reality

  • src/main/addon/Mermaid.mjs:101 — the dynamic await import(), and the mermaidPath config above it.
  • buildScripts/build/mermaid.mjs:8 — outfile resolved from __dirname, which is why a scaffolded workspace's run lands in the engine package.
  • buildScripts/util/check-package-contents.mjs REQUIRED_ENTRIES — lists dist/mermaid.mjs as required, with a why that records the define-identifier substitution making the upstream package non-interchangeable. Any decision here has to move that entry, or the guard contradicts it.
  • neomjs/create-app#33 — the scaffolded workspace is missing bundle-mermaid entirely today, so "the consumer can rebuild it" is currently true only via cd node_modules/neo.mjs.

The Fix

Not prescribed — this ticket exists to make the decision, and the options differ in who absorbs the cost:

  1. Ship as today. Every consumer gets working diagrams with no setup, at 5.0 MB of install for the ones who never render one.
  2. Stop shipping it; the scaffold builds it. Requires neomjs/create-app#33 first. Direct npm i neo.mjs consumers get a broken Mermaid addon until they redirect mermaidPath or run the bundle script — a support question the current design does not have.
  3. Default mermaidPath to a CDN. No install cost, no build step, and a network dependency plus a supply-chain surface the engine does not have today.

Whichever lands, REQUIRED_ENTRIES and the .npmignore header move together with it.

Acceptance Criteria

  • AC-1 — a decision recorded with the numbers above, naming which consumer absorbs which cost.
  • AC-2 — if it stops shipping: neomjs/create-app#33 landed first, REQUIRED_ENTRIES updated, and the .npmignore header's !/dist/mermaid.mjs removed in the same change, so no guard asserts a file that no longer ships.
  • AC-3 — if it keeps shipping: the reasoning is written where the next person meets it, which is the why on its REQUIRED_ENTRIES row, and it stops claiming a consumer cannot rebuild it.
  • AC-4 — the four small bundles are explicitly out of scope of the decision, so a later reader does not re-open them on the strength of this one.

Out of Scope

  • parse5, marked, highlight and monaco. Settled by the measurement: 0.9% combined, two of them imported at module scope.
  • resources/examples (13.21 MiB) and apps/portal (5.87 MiB), which are larger than all of dist/ and are a different question — what the package is FOR, rather than how a bundle is produced.
  • test/playwright (8.52 MiB). Not bloat: the scaffolded app's four test scripts point straight into node_modules/neo.mjs/test/playwright/, so it is the consumer's harness. Recorded here because it looks like an obvious cut and is not.
  • #18814's release-composition repair, which is independent and must land regardless.

Avoided Traps

  • ⛔ Do not treat the five bundles as one decision. One is 5.0 MB, optional and redirectable; two others are imported at module scope. A single verdict is wrong in one direction or the other.
  • ⛔ Do not cite "the consumer can rebuild it" without naming which consumer. True for npx neo-app once create-app#33 lands, false for a direct install, and #18814 got this wrong by not distinguishing them.
  • ⛔ Do not remove the bundle before the scaffold can build it. create-app#33 is the ordering constraint, not a nicety.

Related

  • #18814 — where the question was raised and deferred; its AC-5 points here
  • neomjs/create-app#33 — the missing bundle-mermaid script, a prerequisite for option 2
  • neomjs/create-app#34 — the scaffold's version drift, which would ship a mermaid@11 bundle against a mermaid@12 engine

Origin Session ID: 2b78af80-54a8-4e14-afd8-85c6dfe41004

Retrieval Hint: "should dist/mermaid.mjs ship by default, 5MB 95% of dist, lazy and redirectable, create-app postinstall builds engine devDependencies"

tobiu referenced in commit a14105b - "docs(release): the 13.2 notes gain the merges since iteration 4, and drop three stale claims (#19431) (#19432) on Oct 6, 2026, 6:07 PM