Skip to content

jac tool jac2js omits @jac/runtime globals (__jacJsx ReferenceError in ESM) #7541

Description

@chess10kp

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_globalswith_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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions