Repository navigation
macros: create the build-time macro VM from the build's transform options - #40750
Conversation
…ions When no VM is loaded on the current thread (a `bun build` parse worker, the `Bun.build()` bundle thread, or a runtime transpiler-store worker), `Macro::init` creates a VM to run the macro module in. It left `InitOptions::transform_options` at its default, so the macro module was transpiled without the build's `--define`, `--loader`, tsconfig override, JSX settings and the rest of the transform options. `MacroContext` now keeps an `Arc` of `transpiler.options.transform_options` and `Macro::init` passes a copy of it when it creates the VM. `--external` and `--packages=external` are cleared on that copy: they describe the output bundle, and the macro module's own imports must still resolve at build time. `Bun.build()` also puts its `loader` map into the `TransformOptions` it builds, so the macro VM it creates sees the same loaders.
|
Warning Review limit reached
On-demand reviews are free for the next 23 days. After that, they cost $0.25 per reviewed file. Or wait 18 minutes for your next included review. View limit detailsLimit details: You’ve used all 5 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
Comment |
|
Updated 2:49 AM PT - Aug 28th, 2026
❌ @robobun, your commit 6e1fb5e has 1 failures in
🧪 To try this PR locally: bunx bun-pr 40750That installs a local version of the PR into your bun-40750 --bun |
`Bun.build()` parses on the process-wide worker pool. The macro VM a worker creates takes the options of the build that creates it and stays alive after that build. In bundler_edgecase.test.ts an earlier in-process build could create that VM first, so the API variant of the test was order dependent. It now spawns a fresh process from macro-test.test.ts.
|
CI state at 6e1fb5e: the new tests pass on every lane. The only test that is red after retries is |
Problem
bun build --define process.env.MODE:'"prod"' app.ts, whereapp.tsimports a macro that readsprocess.env.MODE, inlines"none"(the macro's fallback). A file the macro module imports through--loader .dat:textis parsed as JavaScript instead (Expected ";" but found "from").Bun.build({ define, loader })and abun --definerun whose macro sits in a module transpiled off the main thread behave the same way.Bun.build()bundle thread, a runtime transpiler-store worker),Macro::init(src/js_parser_jsc/Macro.rs) creates the macro VM withInitOptions { log, env_loader, is_main_thread, ..Default::default() }.transform_optionsstays at its default, so the VM's transpiler has no defines, no loaders, no tsconfig override. The Zig version passed.args = resolver.opts.transform_optionshere.Fix
MacroContextkeeps anArcclone oftranspiler.options.transform_options(the field already is anArc).Macro::initcopies it intoInitOptions::transform_optionswhen it creates the VM, so the macro module is transpiled with the same--define,--loader,--tsconfig-override, JSX and resolution settings as the build.--externaland--packages=externalare cleared on that copy. They are build-only flags that describe the output bundle. The macro module runs at build time, so its ownimport foo from "foo"must still resolve. Without this,bun build -e foobroke a macro that importsfoo.Bun.build()now includes itsloadermap in theTransformOptionsit builds (js_bundle_completion_task.rs).configure_bundleralready applied the map to the build's own transpiler, but the macro VM is created from theTransformOptions.test/bundler/bundler_edgecase.test.ts(MacroSeesBuildDefinesAndLoadersCLI,MacroSeesBuildDefinesAndLoadersAPI,MacroImportsPackageMarkedExternal) andtest/bundler/transpiler/macro-test.test.ts("a macro in a module transpiled off the main thread sees --define"). Also ranbundler_edgecase.test.ts,bun-build-api.test.ts,transpiler.test.js,bundler_env.test.ts,bundler_loader.test.tsand the macro regression tests.Background
with { type: "macro" }) runs the imported module in a JSC VM at build time and splices the call result into the AST. Inbun runthe current VM is used. Inbun buildthe parse happens on worker threads that have no VM, soMacro::initcreates one per thread.api::TransformOptionsis the CLI/bunfig option bag.BundleOptions::from_apiprojects it into the transpiler's options (defines, loaders, externals, conditions) and keeps the original inBundleOptions::transform_options.VirtualMachine::inittakes aTransformOptionsby value and builds the VM's transpiler from it.--externalor--packages=externalas external and returns it unresolved. The bundler leaves such an import in the output. A runtime VM has no such concept and fails to load the module.Notes
app.tshasimport { mode } from "./m.ts" with { type: "macro" }; console.log(mode());andm.tshasexport function mode() { return process.env.MODE ?? "none"; }.bun build --define 'process.env.MODE:"prod"' app.tsprintsconsole.log("none").bun --define 'process.env.MODE:"prod"' app.tsprintsprod(the entry point is transpiled on the main thread). Withapp.tsimportinglib.tsthat uses the macro, the run printsnonetoo:lib.tsgoes through the runtime transpiler store on a worker thread.Bun.build()withdefineand a macro printednoneas well, and neither the runtime's own--definenor the build's reached the macro: the bundle thread and its workers have no VM.TransformOptionsis exposed throughbun_bundler::optionsnext to the existingEnvBehaviorre-export, sobun_js_parser_jscdoes not need a new Cargo dependency.configure_definesafter init, which loads.envfiles into the shared env loader again. The build's transpiler loaded the same files into the same loader first, so this is a repeat of the same work, unchanged from before.--drop,--feature,--conditions,--main-fields,--extension-order,--preserve-symlinks,--env-fileand the JSX flags are accepted bybun runtoo and now reach the macro VM the same way they reach a runtime VM.--externaland--packagesare build-only and are cleared.bun-build-api.test.ts(function record on an encoder page boundary,repeated builds don't retain the generated code) hit the 5 s timeout on an unmodified main debug build in this container as well. Unrelated.no test proof · iteration 3 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/bundler/bundler_edgecase.test.ts