You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When a Volt entry contains dynamic import(...), the production code-splitting path can emit async chunks but fail to emit/use the real entry chunk. In the observed case, manifest["app.js"].file points at an async chunk (app-auto-render.js) instead of app.js.
This breaks browser entry loading for apps that lazy-load packages, for example a markdown renderer that imports KaTeX auto-render on demand.
Environment
volt 0.9.1
oxc 0.11.0
quickbeam 0.10.6
Elixir 1.20.0-rc.4
macOS Darwin 25.4.0
comparison: Vite 8.0.8, React 19.2.5, KaTeX 0.16.45
There is no real app.js entry chunk in the output. Loading the manifest entry in the browser loads app-auto-render.js, so the entry code never runs.
With code_splitting: false, the same entry errors with:
Invalid value for option "output.file" - When building multiple chunks, the
"output.dir" option must be used, not "output.file". You may set
`output.inlineDynamicImports` to `true` when using dynamic imports.
Expected
Volt should emit a real entry chunk and map app.js to it:
The async chunks should remain separate and be referenced by rewritten dynamic imports.
Likely cause
Volt already builds its own chunk graph before calling OXC.bundle/2 for each output chunk.
However, the entry chunk still contains raw dynamic import(...) expressions when it is passed to OXC. OXC then appears to attempt its own dynamic-import chunking for that single chunk and returns the output.dir / output.file error above.
Volt.Builder.Output.build_chunk_bundles/4 currently drops failed chunk bundles. Since the entry chunk fails, later manifest logic falls back to the first successful async chunk.
Candidate fix
A small local patch fixed the fixture: protect dynamic imports before per-chunk OXC bundling, restore them after bundling, then let Volt's own dynamic-import rewriter map them to generated chunk URLs.
Patch shape:
# before OXC.bundle/2 for each chunkchunk_js=Rewriter.protect_dynamic_imports(chunk_js)# after BundleResult.extract/1code=Rewriter.restore_dynamic_imports(code)
Vite 8.0.8 handles the same fixture correctly and emits a real entry chunk plus async KaTeX chunks.
Direct Rolldown CLI currently errors on the CSS dynamic import because raw Rolldown no longer bundles CSS directly, but Vite's Rolldown-backed pipeline succeeds.
This issue was drafted and the local repro/audit patch was prepared with assistance from OpenAI Codex. I reviewed the repro, compared it against the current repository docs/issues, tested the candidate fix locally, and am filing it because the fixture appears to isolate a real Volt code-splitting behavior.
Summary
When a Volt entry contains dynamic
import(...), the production code-splitting path can emit async chunks but fail to emit/use the real entry chunk. In the observed case,manifest["app.js"].filepoints at an async chunk (app-auto-render.js) instead ofapp.js.This breaks browser entry loading for apps that lazy-load packages, for example a markdown renderer that imports KaTeX auto-render on demand.
Environment
volt0.9.1oxc0.11.0quickbeam0.10.6Repro
Minimal entry:
Build shape:
Actual
The build returns
{:ok, result}, but the manifest maps the entry to an async chunk:{ "app-auto-render.js": { "file": "app-auto-render.js", "src": "app-auto-render.js" }, "app-katex.js": { "file": "app-katex.js", "src": "app-katex.js" }, "app.js": { "file": "app-auto-render.js", "src": "app.js" } }There is no real
app.jsentry chunk in the output. Loading the manifest entry in the browser loadsapp-auto-render.js, so the entry code never runs.With
code_splitting: false, the same entry errors with:Expected
Volt should emit a real entry chunk and map
app.jsto it:{ "app.js": { "file": "app.js", "src": "app.js" } }The async chunks should remain separate and be referenced by rewritten dynamic imports.
Likely cause
Volt already builds its own chunk graph before calling
OXC.bundle/2for each output chunk.However, the entry chunk still contains raw dynamic
import(...)expressions when it is passed to OXC. OXC then appears to attempt its own dynamic-import chunking for that single chunk and returns theoutput.dir/output.fileerror above.Volt.Builder.Output.build_chunk_bundles/4currently drops failed chunk bundles. Since the entry chunk fails, later manifest logic falls back to the first successful async chunk.Candidate fix
A small local patch fixed the fixture: protect dynamic imports before per-chunk OXC bundling, restore them after bundling, then let Volt's own dynamic-import rewriter map them to generated chunk URLs.
Patch shape:
Candidate implementation PR: #7
After applying that patch locally and recompiling Volt, the fixture emitted:
{ "manifest": { "app.js": { "file": "app.js", "src": "app.js" } }, "entry_path": ".../dynamic_katex-volt_code_splitting/app.js", "js_files": ["app-auto-render.js", "app-katex.js", "app.js"], "markers": { "dynamic_katex_entry": true, "katex_lazy_import": true } }Notes
AI disclosure
This issue was drafted and the local repro/audit patch was prepared with assistance from OpenAI Codex. I reviewed the repro, compared it against the current repository docs/issues, tested the candidate fix locally, and am filing it because the fixture appears to isolate a real Volt code-splitting behavior.