Repository navigation
Carry import attributes through the parser, printer, bundler and runtime; add the bytes loader - #40836
Carry import attributes through the parser, printer, bundler and runtime; add the bytes loader#40836robobun wants to merge 9 commits into
Conversation
The parser keeps the whole `with { ... }` clause on the import record
instead of reducing it to a loader. The printer prints it as written on
every import, `export * from` and `export {} from` that stays in the
output, for every target. The bundler keys the module graph on
(path, attributes), so one file imported with two `type` values is two
modules, and reports a `type` that names no loader. The runtime reports
the same case with ERR_IMPORT_ATTRIBUTE_UNSUPPORTED.
`with` is a keyword, so a newline before it is allowed; `assert` keeps
the no-newline rule. A duplicate attribute key is a syntax error.
Adds the `bytes` loader: `with { type: "bytes" }` gives the file's bytes
as a Uint8Array. The bundler inlines them as base64 decoded by the new
`__toBytes` runtime helper; a standalone executable embeds the raw bytes.
|
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 13 seconds 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 (43)
Comment |
require.extensions wrappers call Bun__transpileFile with an empty string for the type attribute, which the new unknown-type check rejected.
…he external check on the plugin path
|
CI on cb28fb7: every lane passes except darwin x64, where |
… under --isolate (#42679) Fixes #33904. Re-lands #33905. Same printer change as @alii's 0de84ef on #35605, carried alone to land first. ### Problem - A runtime plugin resolves `./data.bar?custom` into namespace `custom`. `bun test --isolate` and `--parallel` never run the namespaced `onLoad`: `Expected: "FROM_PLUGIN" Received: "<abs>/data.bar"`. Debug builds stop with `error: Imports different between parseFromSourceCode and fallbackParse`. - In `src/js_printer/lib.rs`, `print_import_record_path` prints that record as `"custom:/abs/data.bar"`, but the three `ModuleInfo` recording sites (`import`, `export {} from`, `export * from`) interned only `path.text`. The module record asked for `/abs/data.bar`. ### Fix - `printed_namespace()` decides whether a record prints as `namespace:path`. `print_import_record_path` and the new `ModuleInfo::str_for_import_record` both call it. The three recording sites use the latter. - The transpiler cache version goes from 32 to 33. The on-disk cache stores the same record, so an older entry brings the bug back. - Verified: three new cases in `test/cli/test/isolation.test.ts` (`--isolate`, `--parallel`, on-disk cache) fail on 1.4.3-canary. Also ran `test/js/bun/plugin/`. - Self-reviewed: 4 concerns raised, 3 addressed. Not done: keep plugin-rewritten files out of the on-disk cache, an older separate bug (Notes). ### Background - `--isolate` gives each test file a fresh global. Bun builds the JSC module record from `ModuleInfo`, a table the printer fills, with no second parse. - The runtime linker (`src/bundler/linker.rs:462`) runs plugin `onResolve` before the print, stores the answer as `path.text` plus `path.namespace`, and sets `PRINT_NAMESPACE_IN_PATH`. - A source specifier `virt:thing` breaks the same way when namespace `virt` has an `onResolve`. With only an `onLoad` it works. <details><summary>Notes</summary> **Related PRs** - #33905 was the first version of this fix. A stale-PR cleanup closed it on 2026-09-13 with no review verdict. - #35605 (open, conflicts with main) attaches `ModuleInfo` to every ESM transpile, not only under `--isolate`. With it, this bug reaches plain `bun run`. Its commit 0de84ef makes the same change to the printer (one predicate, the same three sites). This PR does not depend on #35605 and does not change when `ModuleInfo` is built. Whichever lands second keeps one helper. - #40836 (open) rewrites the same three recording sites for import attributes and still interns `path.text` there. It has to call `str_for_import_record` after a rebase. **Which plugins were affected** - The trigger is the linker rewrite, not the `?query`. The record breaks when the namespaced module cannot be found again from the bare `path.text`. - `./data.bar?custom` with an `onResolve` that strips the query and sets a namespace: broken. This is the issue's case. - `virt:thing` in source with an `onResolve` for namespace `virt`: broken (`Cannot find package 'resolved-other'` in the new test). This is the esbuild-style virtual module pattern. - `./data.bar` with `onResolve({ filter: /\.bar$/ })` into a namespace: worked by accident. The runtime resolve of the bare path matched the same `onResolve` again. - A namespaced specifier with only an `onLoad`: worked. The linker does not rewrite the record, so `path.text` keeps the prefix. - `onResolve` with no namespace (a plain redirect): worked, and still takes the `None` arm of `printed_namespace`. The new test keeps one such import. **Scope of the change** - `request_module` and `request_module_with_phase` have three callers in the printer. All three are changed. The `require()` and `import()` printers also call `print_import_record_path`, but they add nothing to the module record. - Only `src/bundler/linker.rs:472` sets `PRINT_NAMESPACE_IN_PATH`, and only the runtime transpile path (`src/runtime/jsc_hooks.rs`) reaches it with a `ModuleInfo`. The bundler and `bun build --compile` never set the flag. **On-disk transpiler cache** - Stale entry check: ran the issue's repro with a test file above the 4 KiB cache floor and `BUN_RUNTIME_TRANSPILER_CACHE_PATH` set. The `.pile` entry from 1.4.3-canary holds `custom:/abs/data.bar` in the output and `/abs/data.bar` in the ESM record, and the second run fails from the cache. The cache file name is the input hash only, so a fixed build reads that entry unless the version changes. Version 22 was bumped for the same reason. - The third new test runs the fixture cold and warm against a private cache directory, with one module padded past 4 KiB. It covers the record round trip through the cache entry. - Not changed here: the on-disk cache stores output that holds a plugin's link-time `onResolve` answer, under a key made from the importing file's content. If the plugin later answers differently, the old answer is served until the importing file changes. This is independent of `--isolate` and older than this bug. One line after the rewrite in `src/bundler/linker.rs` (`result.runtime_transpiler_cache = None`) would stop it. `parse_entry.rs` already drops the cache in a like case, when it rewrites `@jest/globals` to `bun:test`. The cost is a transpile on every run for each file that a resolve plugin touches, for example every importer of a path alias. That trade needs a maintainer's call, so it is left for a separate PR. **Self-review** 1. The PR body did not name #35605 and #40836, and said a source-written `custom:foo` was never affected. Fixed above. 2. No test for a namespace that is already in the source. Added the `virt:` imports. 3. The cache version bump was checked by hand only. Added the cold and warm cache test. 4. Keep plugin-rewritten output out of the on-disk cache. Not done, see above. **Tests** - The new cases fail on the unfixed release build with `Cannot find package 'resolved-other'` (first error) and `SyntaxError: Export named 'named' not found in module '<dir>/reexport-clause.ts'`. The same fixture passes there without `--isolate`. - Suites run with the debug build: `test/cli/test/isolation.test.ts` (39 pass), `test/js/bun/plugin/` (48 pass), `test/cli/run/transpiler-cache.test.ts`, `test/regression/issue/30887.test.ts`, `test/js/bun/typescript/type-export.test.ts`, `test/cli/inspect/debugger-buntranspiledmodule.test.ts`. Two cases in the last four (`--drop invalidates cache`, one `--compile` case) hit the 5 s default timeout on the local debug build and pass with a longer timeout. </details> <!-- robobun:evidence:begin --> --- **[human-review]** gate passed · iteration 0 · 3 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: 3 FAILED $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/cli/test/isolation.test.ts bun test v1.4.3 (b993710) test/cli/test/isolation.test.ts: (pass) bun test --isolate > without --isolate, leaked global is visible to next file [430.63ms] (pass) bun test --isolate > with --isolate, each file gets a fresh global [483.42ms] (pass) bun test --isolate > without --isolate, --preload still runs once (regression) [282.91ms] (pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [448.63ms] (pass) bun test --isolate > with --isolate, module state is not shared between files [379.00ms] (pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [1257.31ms] (pass) bun test --isolate > with --isolate, a file's process.env writes with native side effects are undone before the next file [1568.05ms] 439 | using dir = tempDir("isolate-plugin-namespace", pluginNamespaceFixture); 440 | const { stderr, exitCode } = await runTests(String(dir), args, ["./a.test.ts", "./b.test.ts"], { 441 | ...bunEnv, 442 | ...en ... (truncated) release without fix: 3 FAILED bun test v1.4.3-canary.1 (b993710) test/cli/test/isolation.test.ts: 439 | using dir = tempDir("isolate-plugin-namespace", pluginNamespaceFixture); 440 | const { stderr, exitCode } = await runTests(String(dir), args, ["./a.test.ts", "./b.test.ts"], { 441 | ...bunEnv, 442 | ...env, 443 | }); 444 | expect(normalizeBunSnapshot(stderr, dir)).toContain("2 pass"); ^ error: expect(received).toContain(expected) Expected to contain: "2 pass" Received: "a.test.ts:\n\n# Unhandled error between tests\n-------------------------------\nerror: Cannot find package 'resolved-other' from '<dir>/a.test.ts'\n-------------------------------\n\n\nb.test.ts:\n\n# Unhandled error between tests\n-------------------------------\nerror: Cannot find package 'resolved-other' from '<dir>/b.test.ts'\n-------------------------------\n\n\n 0 pass\n 2 fail\n 2 errors\nRan 2 tests across 2 files." at <anonymous> (/workspace/bun/test/cli/test/isolation.test.ts:444:47) (pass) bun test --isolate > without --isolate, leaked global is visible to next file [26.77ms] (pass) bun test --isolate > with --isolate, --preloa ... (truncated) ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: all passed $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/cli/test/isolation.test.ts bun test v1.4.3 (b993710) test/cli/test/isolation.test.ts: (pass) bun test --isolate > with --isolate, each file gets a fresh global [352.05ms] (pass) bun test --isolate > without --isolate, leaked global is visible to next file [439.29ms] (pass) bun test --isolate > without --isolate, --preload still runs once (regression) [297.02ms] (pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [336.20ms] (pass) bun test --isolate > with --isolate, module state is not shared between files [371.07ms] (pass) bun test --isolate > with --isolate, a file's process.env writes with native side effects are undone before the next file [1392.12ms] (pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [1461.43ms] (pass) bun test --isolate > cached module records keep the namespace a plugin onResolve gives an import (--isolate) [542.79ms] (pass) bun test --isolate > with --isolate, cached module records keep short, Latin-1 and UTF-16 names ... (truncated) release with fix: all passed $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) target linux-x64-gnu build type Release build dir ./build/release revision 68e0e0b features baseline 23 deps, 131 codegen, 1176 objects in 689ms ninja: Entering directory `/workspace/bun/build/release' [1/1248] install /workspace/bun bun install v1.4.3-canary.1 (b993710) Checked 22 installs across 61 packages (no changes) [7.00ms] [2/1248] gen ErrorCode+*.h [3/1248] install /workspace/bun/packages/bun-error bun install v1.4.3-canary.1 (b993710) Checked 1 install across 2 packages (no changes) [1.00ms] [4/1248] gen bindgenv2 [5/1248] install /workspace/bun/src/node-fallbacks bun install v1.4.3-canary.1 (b993710) Checked 111 installs across 104 packages (no changes) [4.00ms] [6/1248] gen node-fallbacks/react-refresh.js Bundled 1 module in 4ms react-refresh.js 4.81 KB (entry point) [7/1248] fetch zlib [zlib] up to date [8/1248] fetch tinycc [tinycc] up to date [9/1247] fetch libjpeg-turbo [libjpeg-turbo] up to date [10/1247] gen bake.{client,server,error}.js -> bake.client.js, bake.server.js, bake.error.js [11/1247] gen .bind.ts → Gene ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` src/js_printer/lib.rs | 51 +++++++++-------- src/jsc/RuntimeTranspilerCache.rs | 5 +- test/cli/test/isolation.test.ts | 116 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 23 deletions(-) ``` </details> **gate history** · 1 passed · 0 rejected · iteration 0 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/js_printer/lib.rs 6 5 23 src/jsc/RuntimeTranspilerCache.rs 2 1 23 test/cli/test/isolation.test.ts 4 3 23 ``` </details> <!-- robobun:evidence:end --> Co-authored-by: Alistair Smith <hi@alistair.sh>
|
Production use case for the bytes loader. We run a nitro server bundle on Vercel's Bun runtime and bun test on the same modules. The parsers load web-tree-sitter and two So we generate a 435 KB module of base64 strings and commit it, with a regen hook and a drift test. This PR removes all of that. #41834 now carries the module-map part alone. Could the bytes loader and the unknown-type error land the same way, on top of |
… under --isolate (oven-sh#42679) Fixes oven-sh#33904. Re-lands oven-sh#33905. Same printer change as @alii's 0de84ef on oven-sh#35605, carried alone to land first. ### Problem - A runtime plugin resolves `./data.bar?custom` into namespace `custom`. `bun test --isolate` and `--parallel` never run the namespaced `onLoad`: `Expected: "FROM_PLUGIN" Received: "<abs>/data.bar"`. Debug builds stop with `error: Imports different between parseFromSourceCode and fallbackParse`. - In `src/js_printer/lib.rs`, `print_import_record_path` prints that record as `"custom:/abs/data.bar"`, but the three `ModuleInfo` recording sites (`import`, `export {} from`, `export * from`) interned only `path.text`. The module record asked for `/abs/data.bar`. ### Fix - `printed_namespace()` decides whether a record prints as `namespace:path`. `print_import_record_path` and the new `ModuleInfo::str_for_import_record` both call it. The three recording sites use the latter. - The transpiler cache version goes from 32 to 33. The on-disk cache stores the same record, so an older entry brings the bug back. - Verified: three new cases in `test/cli/test/isolation.test.ts` (`--isolate`, `--parallel`, on-disk cache) fail on 1.4.3-canary. Also ran `test/js/bun/plugin/`. - Self-reviewed: 4 concerns raised, 3 addressed. Not done: keep plugin-rewritten files out of the on-disk cache, an older separate bug (Notes). ### Background - `--isolate` gives each test file a fresh global. Bun builds the JSC module record from `ModuleInfo`, a table the printer fills, with no second parse. - The runtime linker (`src/bundler/linker.rs:462`) runs plugin `onResolve` before the print, stores the answer as `path.text` plus `path.namespace`, and sets `PRINT_NAMESPACE_IN_PATH`. - A source specifier `virt:thing` breaks the same way when namespace `virt` has an `onResolve`. With only an `onLoad` it works. <details><summary>Notes</summary> **Related PRs** - oven-sh#33905 was the first version of this fix. A stale-PR cleanup closed it on 2026-09-13 with no review verdict. - oven-sh#35605 (open, conflicts with main) attaches `ModuleInfo` to every ESM transpile, not only under `--isolate`. With it, this bug reaches plain `bun run`. Its commit 0de84ef makes the same change to the printer (one predicate, the same three sites). This PR does not depend on oven-sh#35605 and does not change when `ModuleInfo` is built. Whichever lands second keeps one helper. - oven-sh#40836 (open) rewrites the same three recording sites for import attributes and still interns `path.text` there. It has to call `str_for_import_record` after a rebase. **Which plugins were affected** - The trigger is the linker rewrite, not the `?query`. The record breaks when the namespaced module cannot be found again from the bare `path.text`. - `./data.bar?custom` with an `onResolve` that strips the query and sets a namespace: broken. This is the issue's case. - `virt:thing` in source with an `onResolve` for namespace `virt`: broken (`Cannot find package 'resolved-other'` in the new test). This is the esbuild-style virtual module pattern. - `./data.bar` with `onResolve({ filter: /\.bar$/ })` into a namespace: worked by accident. The runtime resolve of the bare path matched the same `onResolve` again. - A namespaced specifier with only an `onLoad`: worked. The linker does not rewrite the record, so `path.text` keeps the prefix. - `onResolve` with no namespace (a plain redirect): worked, and still takes the `None` arm of `printed_namespace`. The new test keeps one such import. **Scope of the change** - `request_module` and `request_module_with_phase` have three callers in the printer. All three are changed. The `require()` and `import()` printers also call `print_import_record_path`, but they add nothing to the module record. - Only `src/bundler/linker.rs:472` sets `PRINT_NAMESPACE_IN_PATH`, and only the runtime transpile path (`src/runtime/jsc_hooks.rs`) reaches it with a `ModuleInfo`. The bundler and `bun build --compile` never set the flag. **On-disk transpiler cache** - Stale entry check: ran the issue's repro with a test file above the 4 KiB cache floor and `BUN_RUNTIME_TRANSPILER_CACHE_PATH` set. The `.pile` entry from 1.4.3-canary holds `custom:/abs/data.bar` in the output and `/abs/data.bar` in the ESM record, and the second run fails from the cache. The cache file name is the input hash only, so a fixed build reads that entry unless the version changes. Version 22 was bumped for the same reason. - The third new test runs the fixture cold and warm against a private cache directory, with one module padded past 4 KiB. It covers the record round trip through the cache entry. - Not changed here: the on-disk cache stores output that holds a plugin's link-time `onResolve` answer, under a key made from the importing file's content. If the plugin later answers differently, the old answer is served until the importing file changes. This is independent of `--isolate` and older than this bug. One line after the rewrite in `src/bundler/linker.rs` (`result.runtime_transpiler_cache = None`) would stop it. `parse_entry.rs` already drops the cache in a like case, when it rewrites `@jest/globals` to `bun:test`. The cost is a transpile on every run for each file that a resolve plugin touches, for example every importer of a path alias. That trade needs a maintainer's call, so it is left for a separate PR. **Self-review** 1. The PR body did not name oven-sh#35605 and oven-sh#40836, and said a source-written `custom:foo` was never affected. Fixed above. 2. No test for a namespace that is already in the source. Added the `virt:` imports. 3. The cache version bump was checked by hand only. Added the cold and warm cache test. 4. Keep plugin-rewritten output out of the on-disk cache. Not done, see above. **Tests** - The new cases fail on the unfixed release build with `Cannot find package 'resolved-other'` (first error) and `SyntaxError: Export named 'named' not found in module '<dir>/reexport-clause.ts'`. The same fixture passes there without `--isolate`. - Suites run with the debug build: `test/cli/test/isolation.test.ts` (39 pass), `test/js/bun/plugin/` (48 pass), `test/cli/run/transpiler-cache.test.ts`, `test/regression/issue/30887.test.ts`, `test/js/bun/typescript/type-export.test.ts`, `test/cli/inspect/debugger-buntranspiledmodule.test.ts`. Two cases in the last four (`--drop invalidates cache`, one `--compile` case) hit the 5 s default timeout on the local debug build and pass with a longer timeout. </details> <!-- robobun:evidence:begin --> --- **[human-review]** gate passed · iteration 0 · 3 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: 3 FAILED $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/cli/test/isolation.test.ts bun test v1.4.3 (b993710) test/cli/test/isolation.test.ts: (pass) bun test --isolate > without --isolate, leaked global is visible to next file [430.63ms] (pass) bun test --isolate > with --isolate, each file gets a fresh global [483.42ms] (pass) bun test --isolate > without --isolate, --preload still runs once (regression) [282.91ms] (pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [448.63ms] (pass) bun test --isolate > with --isolate, module state is not shared between files [379.00ms] (pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [1257.31ms] (pass) bun test --isolate > with --isolate, a file's process.env writes with native side effects are undone before the next file [1568.05ms] 439 | using dir = tempDir("isolate-plugin-namespace", pluginNamespaceFixture); 440 | const { stderr, exitCode } = await runTests(String(dir), args, ["./a.test.ts", "./b.test.ts"], { 441 | ...bunEnv, 442 | ...en ... (truncated) release without fix: 3 FAILED bun test v1.4.3-canary.1 (b993710) test/cli/test/isolation.test.ts: 439 | using dir = tempDir("isolate-plugin-namespace", pluginNamespaceFixture); 440 | const { stderr, exitCode } = await runTests(String(dir), args, ["./a.test.ts", "./b.test.ts"], { 441 | ...bunEnv, 442 | ...env, 443 | }); 444 | expect(normalizeBunSnapshot(stderr, dir)).toContain("2 pass"); ^ error: expect(received).toContain(expected) Expected to contain: "2 pass" Received: "a.test.ts:\n\n# Unhandled error between tests\n-------------------------------\nerror: Cannot find package 'resolved-other' from '<dir>/a.test.ts'\n-------------------------------\n\n\nb.test.ts:\n\n# Unhandled error between tests\n-------------------------------\nerror: Cannot find package 'resolved-other' from '<dir>/b.test.ts'\n-------------------------------\n\n\n 0 pass\n 2 fail\n 2 errors\nRan 2 tests across 2 files." at <anonymous> (/workspace/bun/test/cli/test/isolation.test.ts:444:47) (pass) bun test --isolate > without --isolate, leaked global is visible to next file [26.77ms] (pass) bun test --isolate > with --isolate, --preloa ... (truncated) ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: all passed $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/cli/test/isolation.test.ts bun test v1.4.3 (b993710) test/cli/test/isolation.test.ts: (pass) bun test --isolate > with --isolate, each file gets a fresh global [352.05ms] (pass) bun test --isolate > without --isolate, leaked global is visible to next file [439.29ms] (pass) bun test --isolate > without --isolate, --preload still runs once (regression) [297.02ms] (pass) bun test --isolate > with --isolate, --preload re-runs in each file's fresh global [336.20ms] (pass) bun test --isolate > with --isolate, module state is not shared between files [371.07ms] (pass) bun test --isolate > with --isolate, a file's process.env writes with native side effects are undone before the next file [1392.12ms] (pass) bun test --isolate > with --isolate, a file's process.chdir() is undone before the next file [1461.43ms] (pass) bun test --isolate > cached module records keep the namespace a plugin onResolve gives an import (--isolate) [542.79ms] (pass) bun test --isolate > with --isolate, cached module records keep short, Latin-1 and UTF-16 names ... (truncated) release with fix: all passed $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) target linux-x64-gnu build type Release build dir ./build/release revision 68e0e0b features baseline 23 deps, 131 codegen, 1176 objects in 689ms ninja: Entering directory `/workspace/bun/build/release' [1/1248] install /workspace/bun bun install v1.4.3-canary.1 (b993710) Checked 22 installs across 61 packages (no changes) [7.00ms] [2/1248] gen ErrorCode+*.h [3/1248] install /workspace/bun/packages/bun-error bun install v1.4.3-canary.1 (b993710) Checked 1 install across 2 packages (no changes) [1.00ms] [4/1248] gen bindgenv2 [5/1248] install /workspace/bun/src/node-fallbacks bun install v1.4.3-canary.1 (b993710) Checked 111 installs across 104 packages (no changes) [4.00ms] [6/1248] gen node-fallbacks/react-refresh.js Bundled 1 module in 4ms react-refresh.js 4.81 KB (entry point) [7/1248] fetch zlib [zlib] up to date [8/1248] fetch tinycc [tinycc] up to date [9/1247] fetch libjpeg-turbo [libjpeg-turbo] up to date [10/1247] gen bake.{client,server,error}.js -> bake.client.js, bake.server.js, bake.error.js [11/1247] gen .bind.ts → Gene ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` src/js_printer/lib.rs | 51 +++++++++-------- src/jsc/RuntimeTranspilerCache.rs | 5 +- test/cli/test/isolation.test.ts | 116 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 23 deletions(-) ``` </details> **gate history** · 1 passed · 0 rejected · iteration 0 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/js_printer/lib.rs 6 5 23 src/jsc/RuntimeTranspilerCache.rs 2 1 23 test/cli/test/isolation.test.ts 4 3 23 ``` </details> <!-- robobun:evidence:end --> Co-authored-by: Alistair Smith <hi@alistair.sh>
with { type: "css" } as a module that exports a CSSStyleSheet in browser builds
#41959
Problem
parse_path(src/js_parser/parse/mod.rs) reducedwith { ... }to a loader and dropped the clause; the printer re-synthesizedwith { type }only forimportunder--target=bun. Other targets,--no-bundleand re-exports lost it (Node:ERR_IMPORT_ATTRIBUTE_MISSING), and non-typekeys were dropped everywhere.typevalues collapsed into one module with the first import's loader. Unknowntypevalues (andbytes) were ignored, a newline beforewithwas a syntax error, duplicate keys were accepted.Fix
ImportRecord.attributeskeeps the clause as written. The printer prints it (aswith) on every import and re-export that stays in the output, for every target, and derives ModuleInfoFetchParametersfrom thetypeattribute as JSC's parser does.(path, attributes)(ImportRecord::module_graph_key), like esbuild and JSC's module map, on the plugin paths too. An unknowntypeis an error when the file is bundled; the runtime throwsERR_IMPORT_ATTRIBUTE_UNSUPPORTED. Duplicate keys are a parse error. Onlyassertkeeps the no-newline rule.bytesloader: aUint8Arrayof the file. Bundles inline base64 decoded by the__toBytesruntime helper;--compileembeds the raw bytes.test/bundler/bundler_import_attributes.test.ts(32 cases, 29 fail on 1.4.1) andtest/js/bun/import-attributes/import-attributes.test.ts. Also the transpiler, loader, plugin, compile, bundler, bake and esbuild suites.Background
(specifier, ScriptFetchParameters::Type):jsonis built in, any othertypeis host-defined and Bun maps it to a loader.export default <expr>.__toBytesis the first runtime helper called from one, sogenerate_code_for_lazy_exportbinds the call to the runtime's export.Notes
Overlapping open PRs this supersedes in part: #37476 (bundler identity), #38407 (re-export loader), #33255 (unknown type). #28512 rejects unknown keys; this PR keeps them, as JSC does under
BUN_JSC_ADDITIONS, sowith { type: "json", custom: "x" }still loads. #38461 (splitting rewrites a dynamic import to a chunk but keeps its attributes) is a separate bug.Decisions:
assertprints aswith. No current engine acceptsassertand rejectswith.typekeys are kept verbatim and never error, in the bundler or the runtime.type: "webassembly"maps to thewasmloader, so the attribute JSC reserves keeps loading.wasmfiles as before.bunBakeGraphis left out of the key: it routes the import to the SSR graph, which has its own map, soreact-dom/serverstays one module there.onResolvefallback path the unknown-type check runs after the external check, like the direct path, so an external import keeps anytype.with.typefrom the loader (an existing test asserts it).readFileSync(__filename)), like the sqlite shim, sorequire("<bunfs path>")gets the array itself.__toBytesreadsBufferandUint8ArrayoffglobalThis. A free reference inruntime.jsbecomes a reserved name in every bundle, which renames user code that shadows it (lodash'svar BufferbecameBuffer2);test/bundler/bundler_bytecode_portable.test.tscaught that.type: "macro"is exempt from the unknown-type error in the runtime as well as the bundler; a dynamicimport(x, { with: { type: "macro" } })loads the module as before.require.extensionswrappers pass an empty type attribute; it counts as none.RuntimeTranspilerCacheversion 27 -> 28.Found while testing, handed off separately:
export * from "./data.json"does not forward the JSON keys inbun build(no attributes involved).Suites run with the debug build: transpiler.test.js, import-attributes.test.ts, bundler_loader, esbuild/loader, esbuild/default, esbuild/metafile, bundler_edgecase, bundler_bun, bundler_cjs, bundler_splitting, bundler_html, html-import-manifest, compile-asset-bunfs, bundler_compile (one pre-existing debug-only failure:
HelloWorldWithProcessVersionsBuncomparesprocess.versions.bunto the release version), bundler_plugin, native-plugin, bake/dev/bundle, webkit-upgrade-3722912f, import-defer, macro-test, the resolve loader suites, the regression tests that mention import attributes, source-lints, bun-types.[review] gate passed · iteration 1 · 43 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 4 passed · 0 rejected · iteration 1
evidence per changed file