Summary
jac tool jac2js emits raw ir.gen.js without calling prepend_active_runtime_globals(), so JSX client modules reference __jacJsx with no import. Loading the output as ESM throws ReferenceError: __jacJsx is not defined.
Confirmed still reproducing on jac 0.32.1.
Root cause
In jac/jaclang/cli/commands/impl/transform.impl.jac (lines 66–71), the jac2js implementation prints codegen output directly:
js_output = ir.gen.js or '';
...
console.print(js_output, markup=False, highlight=False); // raw print, no wrapper
No call to prepend_active_runtime_globals() anywhere in this path.
The injection helper exists and works at jac/jaclang/runtimelib/client_surface.jac:82 (prepend_active_runtime_globals → with_runtime_globals).
Other consumers (all correct)
| Caller |
File |
jac_to_js.impl.jac |
return prepend_active_runtime_globals(js_code) |
compiler.impl.jac |
core_js = prepend_active_runtime_globals(core_js) |
eject.impl.jac |
prepend_active_runtime_globals(js_code) |
cl_test_runner.impl.jac |
module_js = prepend_active_runtime_globals(module_js) |
hmr.impl.jac |
js_code = prepend_active_runtime_globals(js_code) |
jac_client_compiler.impl.jac |
add_runtime_imports(module_js) |
The standalone jac tool jac2js CLI path is the only transform consumer that skips it.
Also affected
- MCP
CompilerBridge.jac_to_js (jac/jaclang/cli/mcp/impl/compiler_bridge.impl.jac:427) returns raw ir.gen.js without prepend.
Repro
Compile any .cl.jac with JSX via jac tool jac2js:
jac tool jac2js app.cl.jac
Output contains __jacJsx(...) but no import { __jacJsx } from "@jac/runtime";.
Full build (jac start) works because compiler.impl.jac injects globals — only standalone emission paths are broken.
Suggested fix (short term)
In transform.impl.jac:
import from jaclang.runtimelib.client_surface { prepend_active_runtime_globals }
...
console.print(prepend_active_runtime_globals(js_output), markup=False, highlight=False);
Same for MCP compiler_bridge.impl.jac.
Suggested fix (long term / root cause)
Centralize finalization at codegen so ir.gen.js is always emission-ready:
// esast_gen_pass.impl.jac, when assigning nd.gen.js:
nd.gen.js = prepend_active_runtime_globals(es_to_js(nd.gen.es_ast));
with_runtime_globals is already idempotent (skips when imports present or symbols defined inline), so existing consumer-level calls become harmless redundancy rather than required correctness.
This closes the entire class of bugs: any new emission surface that reads mod.gen.js / ir.gen.js automatically gets required @jac/runtime globals.
Bonus finding
The old top-level jac jac2js command was removed in #7255 — only jac tool jac2js remains. Dead-code fallback in scripts/jac-client-compile.mjs (catch → execSync('jac jac2js …')) is moot.
Environment
- jac 0.32.1
- Repro: any client
.jac / .cl.jac using JSX
Summary
jac tool jac2jsemits rawir.gen.jswithout callingprepend_active_runtime_globals(), so JSX client modules reference__jacJsxwith no import. Loading the output as ESM throwsReferenceError: __jacJsx is not defined.Confirmed still reproducing on jac 0.32.1.
Root cause
In
jac/jaclang/cli/commands/impl/transform.impl.jac(lines 66–71), thejac2jsimplementation prints codegen output directly:No call to
prepend_active_runtime_globals()anywhere in this path.The injection helper exists and works at
jac/jaclang/runtimelib/client_surface.jac:82(prepend_active_runtime_globals→with_runtime_globals).Other consumers (all correct)
jac_to_js.impl.jacreturn prepend_active_runtime_globals(js_code)compiler.impl.jaccore_js = prepend_active_runtime_globals(core_js)eject.impl.jacprepend_active_runtime_globals(js_code)cl_test_runner.impl.jacmodule_js = prepend_active_runtime_globals(module_js)hmr.impl.jacjs_code = prepend_active_runtime_globals(js_code)jac_client_compiler.impl.jacadd_runtime_imports(module_js)The standalone
jac tool jac2jsCLI path is the only transform consumer that skips it.Also affected
CompilerBridge.jac_to_js(jac/jaclang/cli/mcp/impl/compiler_bridge.impl.jac:427) returns rawir.gen.jswithout prepend.Repro
Compile any
.cl.jacwith JSX viajac tool jac2js:Output contains
__jacJsx(...)but noimport { __jacJsx } from "@jac/runtime";.Full build (
jac start) works becausecompiler.impl.jacinjects globals — only standalone emission paths are broken.Suggested fix (short term)
In
transform.impl.jac:Same for MCP
compiler_bridge.impl.jac.Suggested fix (long term / root cause)
Centralize finalization at codegen so
ir.gen.jsis always emission-ready:with_runtime_globalsis already idempotent (skips when imports present or symbols defined inline), so existing consumer-level calls become harmless redundancy rather than required correctness.This closes the entire class of bugs: any new emission surface that reads
mod.gen.js/ir.gen.jsautomatically gets required@jac/runtimeglobals.Bonus finding
The old top-level
jac jac2jscommand was removed in #7255 — onlyjac tool jac2jsremains. Dead-code fallback inscripts/jac-client-compile.mjs(catch → execSync('jac jac2js …')) is moot.Environment
.jac/.cl.jacusing JSX