Problem
@doenet/standalone ships a single 28.9 MB doenet-standalone.js (plus 3.3 MB CSS). Hosts that embed many activities in iframes (PreTeXt books, doenetml-iframe/assignment-viewer pages) re-parse it once per iframe; measured cost is ~184 MB per embedded viewer, and pages with many activities reach multiple GB (#874).
Bundle composition findings:
- ~11 MB is the entire worker bundle embedded as a raw string (
?raw import in packages/doenetml/src/doenetml-inline-worker.ts:8-11, turned into a blob URL at module-import time). The string is retained in every realm's heap forever, and ~4.9 MB of it is base64-encoded WASM.
- The editor stack (
@doenet/codemirror 5.7 MB built — including the 2.3 MB LSP worker string — prettier via the pretty-printer, diagnostics UI, context help) is statically reachable from the entry through the DoenetEditor export, so pure-viewer hosts pay for it. Note: a viewer-only entry point alone does not help, because the <codeEditor> component's renderer imports EditorViewer directly (Viewer/renderers/codeEditor.tsx:8); the fix must be a lazy boundary. Evidence that the boundary already almost exists: the code-split codeEditor renderer chunk is only 4.8 KB — the editor weight got hoisted into the main chunk by the static export.
- The component schema is bundled 5 times and each copy is eagerly decompressed at module scope (~2.5 MB heap each):
@doenet/codemirror and others bundle @doenet/static-assets into their dists instead of externalizing it (as @doenet/lsp-tools already does).
- Vite lib-mode ES builds skip whitespace minification: running full esbuild minify over the current bundle yields 27.6 → 22.8 MB.
Proposed changes (separable steps)
Expected impact
For viewer-only documents (the common embedding case) the eagerly-parsed payload drops from ~29 MB to roughly 10–12 MB, plus ~15–20 MB less retained heap per iframe realm. Combines with lazy iframes/coordination (#874, PR #880) and the per-worker costs tracked in #1428–#1432.
🤖 Generated with Claude Code
Problem
@doenet/standaloneships a single 28.9 MBdoenet-standalone.js(plus 3.3 MB CSS). Hosts that embed many activities in iframes (PreTeXt books,doenetml-iframe/assignment-viewer pages) re-parse it once per iframe; measured cost is ~184 MB per embedded viewer, and pages with many activities reach multiple GB (#874).Bundle composition findings:
?rawimport inpackages/doenetml/src/doenetml-inline-worker.ts:8-11, turned into a blob URL at module-import time). The string is retained in every realm's heap forever, and ~4.9 MB of it is base64-encoded WASM.@doenet/codemirror5.7 MB built — including the 2.3 MB LSP worker string — prettier via the pretty-printer, diagnostics UI, context help) is statically reachable from the entry through theDoenetEditorexport, so pure-viewer hosts pay for it. Note: a viewer-only entry point alone does not help, because the<codeEditor>component's renderer importsEditorViewerdirectly (Viewer/renderers/codeEditor.tsx:8); the fix must be a lazy boundary. Evidence that the boundary already almost exists: the code-splitcodeEditorrenderer chunk is only 4.8 KB — the editor weight got hoisted into the main chunk by the static export.@doenet/codemirrorand others bundle@doenet/static-assetsinto their dists instead of externalizing it (as@doenet/lsp-toolsalready does).Proposed changes (separable steps)
fetch(...).then(r => r.blob())→ blob URL, lazily on first viewer mount, instead of embedding the source string. −11 MB from the main file, −11 MB retained heap per realm, no double parse. Keep the inlined variant for hosts that need a truly single file (VS Code).React.lazy-importEditorViewerin both thecodeEditorrenderer andDoenetEditor/renderDoenetEditorToContainer, and re-enable code splitting for standalone (dropinlineDynamicImports; module chunks resolve relatively from jsDelivr). The editor chunk then loads only when a document contains<codeEditor>or the host calls the editor API.@doenet/static-assetsin the codemirror build; decompress lazily. (Largely subsumed by the editor split for viewer hosts, still worth it for editor hosts.)Expected impact
For viewer-only documents (the common embedding case) the eagerly-parsed payload drops from ~29 MB to roughly 10–12 MB, plus ~15–20 MB less retained heap per iframe realm. Combines with lazy iframes/coordination (#874, PR #880) and the per-worker costs tracked in #1428–#1432.
🤖 Generated with Claude Code