Repository navigation
Conversation
The production build loads server chunks from memory under bake:/ keys.
With split require on, the bundler printed a require() of an ES module
as import.meta.require("./<chunk>.js"), which the file-system resolver
cannot find there. The build failed with:
error: Cannot find module './<chunk>.js' from '/bake:/_bun/<chunk>.js'
Framework::init_transpiler now sets split_require to false, so the
required module stays in the calling chunk behind its lazy wrapper.
|
Status
|
|
Updated 9:39 AM PT - Oct 21st, 2026
✅ @robobun, your commit d4e03cd372e82f12adad90cf67bf6d1c56229797 passed in 🧪 To try this PR locally: bunx bun-pr 46112That installs a local version of the PR into your bun-46112 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughChangesProduction require loading
Suggested reviewers: Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline findings, I also checked whether the unconditional split_require = false changes the dev server or client graph: find_reachable_files (src/bundler/bundle_v2.rs:2379) only consults the flag under code_splitting, which Bake disables in Development, and the lazy-chunk gate at :2239 already requires a Bun-target importer, so browser-side files never split a require() either way.
Extended reasoning...
The diff adds one unconditional option assignment in Framework::init_transpiler and one serial production-build test; it touches no security-sensitive surface. The flag's only consumer is the reachable-file visitor in bundle_v2.rs, and the dev-server and client-graph paths were confirmed unaffected, leaving the two inline findings (the try/catch-require exit-code regression and the slow serial test) as the open points for a human to weigh.
The case spawns its own build in its own directory, so it does not depend on its neighbours.
There was a problem hiding this comment.
Re-reviewed after the second push: no new bugs found, and moving the new case to test.concurrent matches the sibling test.concurrent.each cases in this file. A human look is still worthwhile because the one-line change unconditionally alters how every Bake production build bundles a server-side require() of an ES module, and the author-documented tradeoffs (wrapped module keeps its imports linked eagerly; larger runtime chunk when client code is present) are a product call rather than a correctness question.
What was reviewed:
- Traced
split_requireto its single bundler reader (find_reachable_files, src/bundler/bundle_v2.rs:2363), only reached whencode_splittingis on — so the dev server (code splitting off) never reads it and the unconditional assignment for Client/Server/Ssr is inert there; the Client graph is also excluded by theis_bun()target check at :2240. - Confirmed
bun build(src/runtime/cli/build_command.rs:233) andBun.build(src/runtime/api/js_bundle_completion_task.rs:1086) set this same field per driver, so a driver-level override is the established pattern rather than a bundler default change. - Test:
bunEnvis spread into the subprocess env, assertions on rendered output precede the exit-code check, andtempDirWithBakeDepswithoutusingmatches every sibling in the describe block.
Extended reasoning...
The diff adds one unconditional option assignment in Framework::init_transpiler (src/runtime/bake/config/transpiler.rs:52) and one test.concurrent case in test/bake/dev/production.test.ts covering eight require()-of-ESM shapes in a Bake production build. It touches no security-sensitive surface: no auth, crypto, path handling, or untrusted-input parsing. The code is small and follows the same per-driver pattern the CLI and JS API use for this option, and the second commit addressed the earlier serial-test note. Not approved because the change is a behavioral tradeoff for all Bake production builds that a maintainer should weigh (the PR itself lists downsides such as a never-executed require() no longer hiding an unloadable import), rather than a purely mechanical fix.
Still open from earlier reviews (1):
- Unresolved: 1 minor or pre-existing.
There was a problem hiding this comment.
I re-reviewed the latest push and found no bugs; the test.concurrent change from the earlier round is in. What remains is a design call rather than a defect: with split_require off, a try/catch or never-executed require() of an ES module whose imports the production loader cannot serve now fails the build where the base exited 0 (the description lists this as a downside, matching 1.4.0), so a maintainer should weigh that trade-off before merging.
What was reviewed:
- Traced
split_requirefrominit_transpilerto its single reader infind_reachable_files(src/bundler/bundle_v2.rs:2363): the server transpiler is the primary transpiler forgenerate_from_bake_production_cli, so the assignment is live, and nothing later ininit_transpiler(sync_resolver_opts,configure_linker) overwrites it. - Checked the new test:
tempDirWithBakeDeps,normalizePath, andpathare existing imports in the file; thetoEqualmap over all rendered pages and the exactwithClientCodelist cannot pass vacuously. - Checked
.github/CODEOWNERS: neither changed file is owned.
Extended reasoning...
The diff adds one unconditional assignment in src/runtime/bake/config/transpiler.rs (split_require = false in Framework::init_transpiler, applied to every Bake graph in both modes) and a test.concurrent regression test in test/bake/dev/production.test.ts that builds a React Bake app with eight pages covering the require-of-ESM shapes. It touches no security-sensitive surface. The fix is small and verified live, and the prior test-speed feedback was addressed by a later commit; the earlier inline thread about the behavior change for never-executed or try/catch require() of an unservable ESM was resolved by the author only, and it is an intentional trade-off documented in the description rather than something a commit fixed, so a human should weigh it instead of this review approving over it.
…-production-split-require
Problem
bun build --appexits 1 when server code callsrequire()on an ES module:error: Cannot find module './sf6jz4sb.js' from '/bake:/_bun/qj34pke0.js'. Intry/catchit exits 0 with the wrong page. bun 1.4.0 builds both (regression from bundler: with --splitting --target bun, require() of an ES module is a chunk boundary #40519).import.meta.require("./<chunk>.js")(src/bundler/bundle_v2.rs:2264), which looks for a file. The build keeps server chunks in memory.Framework::init_transpiler(src/runtime/bake/config/transpiler.rs:49) never setsplit_require.Fix
init_transpilersetssplit_require = false. The required module stays in the calling chunk behind its lazy wrapper, as in 1.4.0.require()saves nothing.test/bake/dev/production.test.ts(new case, fails on main),test/bake/dev/esm.test.ts, theSplitRequirebundler tests.Background
--splitting --target bun, arequire()of an ES module is a chunk of its own.bun build --apprenders every page at build time from server chunks in a map underbake:/keys. Onlyimportandimport()read that map.bake:/chunks torequire(): that adds a test to everyrequire()in every process.Downsides
require()loads client code again, as in 1.4.0.require()of an ES module, the runtime chunk that the browser loads grows from 867 to 1,292 bytes. Without thatrequire(),dist/is byte-identical.require()that never runs no longer hides an import that the build cannot load (node:sea, see Notes).Notes
Where this comes from
module.exports = require()file per import, not per file #45830. No issue reports it. bundler: redirect amodule.exports = require()file per import, not per file #45830 lists this failure as a downside of its change. With this PR that downside does not apply.BUN_FEATURE_FLAG_EXPERIMENTAL_BAKE=1) builds the app of the test. 1.4.1 and later fail.--no-split-requireis parsed with--appand not read:src/runtime/cli/build_command.rs:76returns into the bake driver before the copy at:233.Each page of the test, built as its own app (release builds of main at c1df64b, without and with the change)
app: 1.2.3 functionin-page: 1.2.3lazy: undefined 1.2.3 true 1client: 1.2.3island: button, one module scriptrequire()intry/catchoptional: missingoptional: 1.2.3require()andimport()of one moduleboth: 1.2.3 true 1require()that never runs,import())The test passes on bun 1.4.0 too, so every expected value is what 1.4.0 rendered.
Why this place
find_reachable_filesreadssplit_requireonce, from the server transpiler (src/bundler/bundle_v2.rs:2388).bun buildandBun.buildassign the field next tocode_splitting. Bake turned code splitting on and kept the defaulttrue.Measurements (release and debug builds of main at c1df64b, without and with the change)
size: 106,785,051 both).bun build --splitting --target bunon thebench/split-requireapp: 0 of 42 output files differ between the two release binaries (sha256). With--no-split-require: 0 of 1./bake:duringbun build --appon the repro: 4 before (allopenat, all ENOENT), 0 after (gdbcatch syscall, release builds).--debug-dump-server-files --debug-no-minify: server JS files 6 to 5, the calling chunk 774 to 1,106 bytes, relativeimport.meta.requirecall sites 1 to 0.require()that never runs: module-map entries 5 to 4. The same module also imported statically: entries 6 to 4 and loads 5 to 4. A control app: 4 and 4 on both.require()of an ES module in a server chunk creates 16 cells (Object 9, Function 3, GetterSetter 3, WeakMap 1) plus its code and strings. Each later call creates 0 (heapStats().objectTypeCounts, debug build).require()of an ES module that is already loaded, on disk output of one release binary: 20.4 ns per call with the wrapper, 691.4 ns with the split form (median of 10 interleaved runs of 2,000,000 calls, noise floor 4.3 ns).require()that never runs and reaches a client component:index.htmlis 545 bytes with no module script before, 681 bytes with one after and on 1.4.0. That script loads 199,064 bytes of JavaScript.__toCommonJSand__export, which a wrapped ES module needs.bun build --appand per dev server start (one per graph). 1 of the 6init_transpilercall sites has a reader.The
node:seacasenode:seafrom server code:Non-relative import "node:sea" from "bake:/_bun/..." are not allowed in production assets. That is the same on 1.4.0, on main and on this branch.require()that never runs or that is intry/catch: exit 1 on 1.4.0, exit 0 on main, exit 1 on this branch.Not in this PR (same result on 1.4.0, on main and on this branch)
require("./addon.node")in server code:Cannot find module './hvncczhx.node' from '/bake:/_bun/...'.import db from "./data.sqlite" with { type: "sqlite", embed: "true" }:Cannot find module './arerxbxt.sqlite' from '/bake:/_bun/...'.import.meta.require("../lib/dep.mjs"):Cannot find module '../lib/dep.mjs' from '/bake:/_bun/...'.require()of a"use client"module:TypeError: Cannot destructure property 'default' from null or undefined value.These do not read
split_require. The first three end in animport.meta.require()of a path that does not exist next to the in-memory chunk. No issue tracks them yet.The test
Runtime file not found(bun build --app panics with "Runtime file not found" for any custom Bake.Framework #32142, fixed since by Hardening: fix crashes across the runtime, bundler, dev server, shell and CLI #46021).bun bd test. With--timeout 180000the file gives 22 pass. CI passes its own--timeout.Cannot find moduleerror. With the change the file gives 22 pass. Release builds at c1df64b: 18 pass and 1 fail without the change, 19 pass with it.Other suites on the debug build with the change
test/bake/dev/esm.test.ts: 18 pass.test/bundler/bundler_splitting.test.ts -t SplitRequire: 24 pass.test/bundler/bundler_compile_splitting.test.ts -t SplitRequire: 2 pass.test/bundler/bundler_dynamic_import_dce.test.ts: 333 pass.Designs weighed
split_requireoff unless a driver sets it): the same output for bake, but it changes the starting value for every other user of those options.import.meta.requirethat loadsbake:/chunks: new loader code to keep a chunk boundary that a build with every chunk in memory does not use.require()would also return the module namespace object, not the copy that 1.4.0 returned.bake:/branch in the shared synchronous resolve path: see Background.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/bake/dev/production.test.ts