From de0729b52a99af8a0915357de57a23e6349a8e35 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:58:44 +0000 Subject: [PATCH 1/7] bundler: fix re-exports of the "bun" builtin and module.exports leaks from non-entry export stars With --target=bun, a module doing `export * from "bun"` or `export { default } from "bun"` produced undefined bindings, and with --format=cjs the re-export also copied the whole Bun object onto the entry point's module.exports. - convertStmtsForChunk: a runtime `export * from "bun"` in ESM output was turned into `import * as ns from "bun"` plus `__reExport(exports, ns)`. The printer lowers that import to `var ns = globalThis.Bun`, which is not hoisted, so __reExport ran before ns was assigned. The "bun" record now passes the module expression (printed as globalThis.Bun) to __reExport directly, as the CJS path already did. - convertStmtsForChunk: the module.exports argument of __reExport was added for every file in an entry chunk instead of only the entry point file, so any non-entry `export * from ` leaked onto the entry's module.exports. For "bun" that copied fetch/serve there, which made `bun out.cjs` start a server. importstar/ ReExportStarEntryPointAndInnerFileExternal had the leaked key baked into its expected output; the non-external variant of the same test already expected the key to be absent. - js_printer: `import { default as x } from "bun"` (which is also what the bundler turns `export { default } from "bun"` into) was printed as `var { default: x } = globalThis.Bun`. The default export of "bun" is the Bun object itself, so such items now bind to the module value. - js_printer: in CJS output the linker marks default/namespace imports of "bun" with WRAP_WITH_TO_ESM and emits the __toESM helper, but the globalThis.Bun shortcut ignored the flag, so `import x from "bun"` became `import_bun.default` on the bare Bun object. The flag is now honored, giving the import the same namespace shape the module loader produces for "bun". --- .../linker_context/convertStmtsForChunk.rs | 73 +++++++---- .../generateCodeForFileInChunkJS.rs | 2 - src/js_printer/lib.rs | 122 +++++++++++------- test/bundler/bundler_bun.test.ts | 64 +++++++++ test/bundler/bundler_cjs.test.ts | 27 ++++ test/bundler/bundler_minify.test.ts | 18 +++ test/bundler/esbuild/importstar.test.ts | 4 +- test/js/bun/resolve/import-meta.test.js | 7 +- 8 files changed, 243 insertions(+), 74 deletions(-) diff --git a/src/bundler/linker_context/convertStmtsForChunk.rs b/src/bundler/linker_context/convertStmtsForChunk.rs index 5035de8fbae6..b183ff1e7a91 100644 --- a/src/bundler/linker_context/convertStmtsForChunk.rs +++ b/src/bundler/linker_context/convertStmtsForChunk.rs @@ -2,15 +2,14 @@ use crate::BundledAst as JSAst; use crate::mal_prelude::*; use bun_alloc::Arena as Bump; use bun_ast::ImportRecordFlags; +use bun_ast::ImportRecordTag; use bun_ast::Loc; use bun_ast::{self as js_ast, Binding, Expr, ExprNodeList, Stmt}; use bun_ast::{B, E, G, S}; use bun_collections::VecExt; use bun_core::FeatureFlags; -use crate::EntryPoint; use crate::WrapKind; -use crate::chunk::Chunk; use crate::linker_context_mod::{LinkerContext, LinkerOptionsMode, StmtList, StmtListWhich}; use crate::options::Format; @@ -49,15 +48,15 @@ pub(crate) fn convert_stmts_for_chunk( source_index: u32, stmts: &mut StmtList, part_stmts: &[bun_ast::Stmt], - chunk: &mut Chunk, bump: &Bump, wrap: WrapKind, ast: &JSAst<'_>, ) -> Result<(), crate::Error> { let _ = bump; let should_extract_esm_stmts_for_wrap = wrap != WrapKind::None; - let should_strip_exports = c.options.mode != LinkerOptionsMode::Passthrough - || c.graph.files.items_entry_point_kind()[source_index as usize] != EntryPoint::Kind::None; + let is_entry_point = + c.graph.files.items_entry_point_kind()[source_index as usize].is_entry_point(); + let should_strip_exports = c.options.mode != LinkerOptionsMode::Passthrough || is_entry_point; let output_format = c.options.output_format; @@ -68,8 +67,12 @@ pub(crate) fn convert_stmts_for_chunk( // one must have the "__esModule" marker. This is done because an ES module // importing itself should not see the "__esModule" marker but a CommonJS module // importing us should see the "__esModule" marker. + // + // This is about the file being converted, not the chunk it lands in: a + // non-entry file bundled into the entry chunk must not leak its re-exports + // onto the entry's "module.exports". let mut module_exports_for_export: Option = None; - if output_format == Format::Cjs && chunk.is_entry_point() { + if output_format == Format::Cjs && is_entry_point { module_exports_for_export = Some(Expr::allocate( bump, E::Dot { @@ -169,16 +172,41 @@ pub(crate) fn convert_stmts_for_chunk( .flags .contains(ImportRecordFlags::CALLS_RUNTIME_RE_EXPORT_FN) { - // Turn this statement into "import * as ns from 'path'" - stmt = Stmt::alloc( - S::Import { - namespace_ref: s.namespace_ref, - import_record_index: s.import_record_index, - star_name_loc: stmt.loc, - ..Default::default() - }, - stmt.loc, - ); + let is_bun_builtin = record.tag == ImportRecordTag::Bun; + let re_exported_module: Expr = if is_bun_builtin { + // The printer lowers an import of "bun" to a plain + // `var ns = globalThis.Bun`, which unlike an import + // statement is not hoisted above the "__reExport()" + // call emitted below, so reference the module directly + // (printed as `globalThis.Bun`) instead of going + // through a namespace binding. + Expr::init( + E::RequireString { + import_record_index: s.import_record_index, + ..Default::default() + }, + stmt.loc, + ) + } else { + // Turn this statement into "import * as ns from 'path'" + stmt = Stmt::alloc( + S::Import { + namespace_ref: s.namespace_ref, + import_record_index: s.import_record_index, + star_name_loc: stmt.loc, + ..Default::default() + }, + stmt.loc, + ); + + Expr::init( + E::Identifier { + ref_: s.namespace_ref, + ..Default::default() + }, + stmt.loc, + ) + }; // Prefix this module with "__reExport(exports, ns, module.exports)" let export_star_ref = c.runtime_function(b"__reExport"); @@ -191,13 +219,7 @@ pub(crate) fn convert_stmts_for_chunk( }, stmt.loc, )); - args.push(Expr::init( - E::Identifier { - ref_: s.namespace_ref, - ..Default::default() - }, - stmt.loc, - )); + args.push(re_exported_module); if let Some(mod_) = module_exports_for_export { // Per the "__reExport(exports, ns, module.exports)" @@ -233,6 +255,11 @@ pub(crate) fn convert_stmts_for_chunk( stmt.loc, ))?; + if is_bun_builtin { + // There is no import statement left to keep + continue 'stmt_loop; + } + // Make sure these don't end up in the wrapper closure if should_extract_esm_stmts_for_wrap { stmts.append(StmtListWhich::OutsideWrapperPrefix, stmt); diff --git a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs index e41a1cb53ba8..86bcab4cf9e4 100644 --- a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs +++ b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs @@ -293,7 +293,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( source_index as u32, stmts, ns_part_stmts, - chunk, temp_arena, flags.wrap, &ast, @@ -494,7 +493,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( source_index as u32, stmts, part_stmts, - chunk, temp_arena, flags.wrap, &ast, diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index c712a54b64ef..38d3f4082f5c 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -1819,17 +1819,7 @@ pub(crate) mod __gated_printer { self.print_space(); self.print(b"="); self.print_space_before_identifier(); - match statement { - None => self.print_require_or_import_expr( - import.import_record_index, - false, - &[], - Expr::EMPTY, - Level::Lowest, - ExprFlag::none(), - ), - Some(s) => self.print(s), - } + self.print_internal_bun_module(import.import_record_index, statement); self.print_semicolon_after_statement(); self.print_indent(); } @@ -1838,27 +1828,30 @@ pub(crate) mod __gated_printer { self.print_semicolon_if_needed(); self.print(b"var "); self.print_symbol(default.ref_); - match statement { - None => { - self.print_equals(); - self.print_require_or_import_expr( - import.import_record_index, - false, - &[], - Expr::EMPTY, - Level::Lowest, - ExprFlag::none(), - ); - } - Some(s) => { - self.print_equals(); - self.print(s); - } + self.print_equals(); + self.print_internal_bun_module(import.import_record_index, statement); + self.print_semicolon_after_statement(); + } + + // The module's default export is the module object itself, so + // `import { default as bun } from "bun"` (and the `import` that the + // bundler turns `export { default } from "bun"` into) binds the whole + // module rather than reading a "default" property off of it. + let mut named_item_count: usize = 0; + for item in slice_of(import.items).iter() { + if item.alias.slice() != b"default" { + named_item_count += 1; + continue; } + self.print_semicolon_if_needed(); + self.print(b"var "); + self.print_symbol(item.name.ref_); + self.print_equals(); + self.print_internal_bun_import_value(import, statement); self.print_semicolon_after_statement(); } - if slice_of(import.items).len() > 0 { + if named_item_count > 0 { self.print_semicolon_if_needed(); self.print_whitespacer(ws!(b"var {")); @@ -1868,7 +1861,11 @@ pub(crate) mod __gated_printer { self.print_indent(); } - for (i, item) in slice_of(import.items).iter().enumerate() { + let mut i: usize = 0; + for item in slice_of(import.items).iter() { + if item.alias.slice() == b"default" { + continue; + } if i > 0 { self.print(b","); self.print_space(); @@ -1877,6 +1874,7 @@ pub(crate) mod __gated_printer { self.print_indent(); } } + i += 1; self.print_clause_item_as(item, ClauseItemAs::Var); } @@ -1888,26 +1886,43 @@ pub(crate) mod __gated_printer { } self.print_whitespacer(ws!(b"} = ")); + self.print_internal_bun_import_value(import, statement); + self.print_semicolon_after_statement(); + } + } - if import.star_name_loc.is_empty() && import.default_name.is_none() { - match statement { - None => self.print_require_or_import_expr( - import.import_record_index, - false, - &[], - Expr::EMPTY, - Level::Lowest, - ExprFlag::none(), - ), - Some(s) => self.print(s), - } - } else if let Some(name) = &import.default_name { - self.print_symbol(name.ref_); - } else { - self.print_symbol(import.namespace_ref); - } + /// Prints the module object that the bindings of `import` are read from: + /// the binding declared by an earlier `var` of the same statement when + /// there is one, otherwise the module expression itself. + fn print_internal_bun_import_value( + &mut self, + import: &S::Import, + statement: Option<&'static [u8]>, + ) { + if let Some(default) = &import.default_name { + self.print_symbol(default.ref_); + } else if !import.star_name_loc.is_empty() { + self.print_symbol(import.namespace_ref); + } else { + self.print_internal_bun_module(import.import_record_index, statement); + } + } - self.print_semicolon_after_statement(); + fn print_internal_bun_module( + &mut self, + import_record_index: u32, + statement: Option<&'static [u8]>, + ) { + match statement { + None => self.print_require_or_import_expr( + import_record_index, + false, + &[], + Expr::EMPTY, + Level::Lowest, + ExprFlag::none(), + ), + Some(s) => self.print(s), } } @@ -2494,7 +2509,20 @@ pub(crate) mod __gated_printer { return; } else if record.kind == ImportKind::Require || record.kind == ImportKind::Stmt { + // The linker asks for __toESM() when the import needs ESM + // namespace semantics (a default import or `import *`); the + // bare Bun object has no "default" property. + let wrap_with_to_esm = + record.flags.contains(ImportRecordFlags::WRAP_WITH_TO_ESM); + if wrap_with_to_esm { + self.print_space_before_identifier(); + self.print_symbol(self.options.to_esm_ref); + self.print(b"("); + } self.print(b"globalThis.Bun"); + if wrap_with_to_esm { + self.print(b")"); + } if wrap { self.print(b")"); } diff --git a/test/bundler/bundler_bun.test.ts b/test/bundler/bundler_bun.test.ts index 674da11059bb..f458a4f8c5fc 100644 --- a/test/bundler/bundler_bun.test.ts +++ b/test/bundler/bundler_bun.test.ts @@ -38,6 +38,70 @@ describe("bundler", () => { }, run: { stdout: "RedisClient\nRedisClient\nRedisClient\n" }, }); + // A non-entry `export * from "bun"` is evaluated at runtime with __reExport(), + // which has to read the Bun object before the re-exporting module's body runs. + itBundled("bun/ReExportStarFromBunInNonEntryFile", { + target: "bun", + files: { + "/entry.ts": /* js */ ` + import { Glob, version } from "./re-export"; + console.log(typeof Glob, version === Bun.version); + `, + "/re-export.ts": `export * from "bun";`, + }, + run: { stdout: "function true" }, + }); + itBundled("bun/ReExportStarFromBunInNonEntryFileCJS", { + target: "bun", + format: "cjs", + files: { + "/entry.ts": /* js */ ` + import { Glob, version } from "./re-export"; + console.log(typeof Glob, version === Bun.version); + `, + "/re-export.ts": `export * from "bun";`, + }, + runtimeFiles: { + // The entry point exports nothing, so nothing may be copied onto its + // module.exports. (When the whole Bun object was, running the bundle + // directly made Bun treat the copied "fetch" as a server entry point.) + "/test.js": /* js */ ` + const entry = require("./out.js"); + console.log(typeof entry.fetch, Object.keys(entry).length); + `, + }, + run: { + file: "/test.js", + stdout: "function true\nundefined 0", + }, + }); + // The default export of "bun" is the Bun object itself. + itBundled("bun/ReExportDefaultFromBun", { + target: "bun", + files: { + "/entry.ts": /* js */ ` + import ReExported, { G } from "./re-export"; + import { default as Aliased } from "bun"; + console.log(ReExported === Bun, Aliased === Bun, typeof G); + `, + "/re-export.ts": `export { default, Glob as G } from "bun";`, + }, + run: { stdout: "true true function" }, + }); + itBundled("bun/ReExportDefaultFromBunCJS", { + target: "bun", + format: "cjs", + files: { + "/entry.ts": /* js */ ` + import ReExported from "./re-export"; + import Direct, { Glob } from "bun"; + import * as ns from "bun"; + console.log(ReExported === Bun, Direct === Bun, typeof Glob, ns.default === Bun, ns.Glob === Glob); + `, + "/re-export.ts": `export { default } from "bun";`, + }, + run: { stdout: "true true function true true" }, + }); itBundled("bun/embedded-sqlite-file", { target: "bun", outfile: "", diff --git a/test/bundler/bundler_cjs.test.ts b/test/bundler/bundler_cjs.test.ts index aee7fb02d9fd..0c4f4ceeb753 100644 --- a/test/bundler/bundler_cjs.test.ts +++ b/test/bundler/bundler_cjs.test.ts @@ -597,4 +597,31 @@ describe("bundler", () => { stdout: "loaded ok", }, }); + + // Test 29: export * from an external package in a file that is not the entry + // point. Only the entry point's own re-exports are mirrored onto module.exports; + // a re-exporting dependency bundled into the entry chunk must not be. + itBundled("cjs/__reExport_external_in_non_entry_file", { + files: { + "/entry.js": /* js */ ` + import { foo } from "./re-export.js"; + console.log(foo); + `, + "/re-export.js": `export * from "ext";`, + }, + runtimeFiles: { + "/node_modules/ext/index.js": /* js */ `module.exports = { foo: "foo", bar: "bar" };`, + "/test.js": /* js */ ` + const entry = require("./out.js"); + console.log(JSON.stringify(Object.keys(entry))); + `, + }, + external: ["ext"], + target: "node", + format: "cjs", + run: { + file: "/test.js", + stdout: "foo\n[]", + }, + }); }); diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts index f6220592d6d9..421a3978b7f4 100644 --- a/test/bundler/bundler_minify.test.ts +++ b/test/bundler/bundler_minify.test.ts @@ -1251,6 +1251,24 @@ describe("bundler", () => { }, }); + itBundled("minify/BunImportDefaultAliasAndNamed", { + files: { + "/entry.js": /* js */ ` + import { default as bun, embeddedFiles } from "bun" + import { default as bunAgain } from "bun" + console.log(typeof embeddedFiles) + console.log(bun === Bun, bunAgain === Bun) + `, + }, + minifySyntax: true, + minifyWhitespace: true, + minifyIdentifiers: true, + target: "bun", + run: { + stdout: "object\ntrue true", + }, + }); + // https://github.com/oven-sh/bun/issues/31722 // An arrow whose body is a single `return ` collapses to a shorthand // expression body when minifying: `(a) => { return a; }` becomes `(a) => a`. diff --git a/test/bundler/esbuild/importstar.test.ts b/test/bundler/esbuild/importstar.test.ts index 89261b19282e..686263aeab47 100644 --- a/test/bundler/esbuild/importstar.test.ts +++ b/test/bundler/esbuild/importstar.test.ts @@ -1398,7 +1398,9 @@ describe("bundler", () => { }, run: { file: "/test.js", - stdout: '{"inner":{"b":456},"a":123,"b":456}', + // inner.js re-exports "b", but entry.js only exports the "inner" namespace, + // so "b" must not show up on the entry point's module.exports. + stdout: '{"inner":{"b":456},"a":123}', }, }); itBundled("importstar/ReExportStarEntryPointAndInnerFile", { diff --git a/test/js/bun/resolve/import-meta.test.js b/test/js/bun/resolve/import-meta.test.js index b2a3cc54b06b..c5d73e96196e 100644 --- a/test/js/bun/resolve/import-meta.test.js +++ b/test/js/bun/resolve/import-meta.test.js @@ -1,4 +1,4 @@ -import { spawnSync } from "bun"; +import { default as BunViaDefaultAlias, spawnSync } from "bun"; import { isModuleResolveFilenameSlowPathEnabled } from "bun:internal-for-testing"; import { expect, it, mock } from "bun:test"; import { bunEnv, bunExe, ospath } from "harness"; @@ -228,6 +228,11 @@ it('import("bun") works', async () => { expect(await import("bun")).toBe(Bun); }); +it('import { default as x } from "bun" is the Bun object', () => { + expect(BunViaDefaultAlias).toBe(Bun); + expect(spawnSync).toBe(Bun.spawnSync); +}); + it("require.resolve with empty options object", () => { expect(require.resolve(import.meta.path + String(""), {})).toBe(import.meta.path); }); From da5a6f34889794d05b9bdff76d6d6c7323a5b98a Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:16:31 +0000 Subject: [PATCH 2/7] test: link the CJS default import case to issue #20670 --- test/bundler/bundler_bun.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/bundler/bundler_bun.test.ts b/test/bundler/bundler_bun.test.ts index f458a4f8c5fc..0b105f79e6ef 100644 --- a/test/bundler/bundler_bun.test.ts +++ b/test/bundler/bundler_bun.test.ts @@ -88,6 +88,7 @@ describe("bundler", () => { }, run: { stdout: "true true function" }, }); + // https://github.com/oven-sh/bun/issues/20670 (--bytecode defaults to this format) itBundled("bun/ReExportDefaultFromBunCJS", { target: "bun", format: "cjs", From 41206bc49481b1daea76c5c9b416f60df76e521f Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:26:21 +0000 Subject: [PATCH 3/7] Trim comments --- .../linker_context/convertStmtsForChunk.rs | 12 ++---------- src/js_printer/lib.rs | 15 +++++---------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/bundler/linker_context/convertStmtsForChunk.rs b/src/bundler/linker_context/convertStmtsForChunk.rs index b183ff1e7a91..e5e817c773bc 100644 --- a/src/bundler/linker_context/convertStmtsForChunk.rs +++ b/src/bundler/linker_context/convertStmtsForChunk.rs @@ -67,10 +67,6 @@ pub(crate) fn convert_stmts_for_chunk( // one must have the "__esModule" marker. This is done because an ES module // importing itself should not see the "__esModule" marker but a CommonJS module // importing us should see the "__esModule" marker. - // - // This is about the file being converted, not the chunk it lands in: a - // non-entry file bundled into the entry chunk must not leak its re-exports - // onto the entry's "module.exports". let mut module_exports_for_export: Option = None; if output_format == Format::Cjs && is_entry_point { module_exports_for_export = Some(Expr::allocate( @@ -174,12 +170,8 @@ pub(crate) fn convert_stmts_for_chunk( { let is_bun_builtin = record.tag == ImportRecordTag::Bun; let re_exported_module: Expr = if is_bun_builtin { - // The printer lowers an import of "bun" to a plain - // `var ns = globalThis.Bun`, which unlike an import - // statement is not hoisted above the "__reExport()" - // call emitted below, so reference the module directly - // (printed as `globalThis.Bun`) instead of going - // through a namespace binding. + // A "bun" import prints as a non-hoisted var, which would + // land after the "__reExport()" call below. Expr::init( E::RequireString { import_record_index: s.import_record_index, diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index 38d3f4082f5c..5a1d19a2dff6 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -1833,10 +1833,7 @@ pub(crate) mod __gated_printer { self.print_semicolon_after_statement(); } - // The module's default export is the module object itself, so - // `import { default as bun } from "bun"` (and the `import` that the - // bundler turns `export { default } from "bun"` into) binds the whole - // module rather than reading a "default" property off of it. + // The default export is the module object itself, not a "default" property on it. let mut named_item_count: usize = 0; for item in slice_of(import.items).iter() { if item.alias.slice() != b"default" { @@ -1891,9 +1888,8 @@ pub(crate) mod __gated_printer { } } - /// Prints the module object that the bindings of `import` are read from: - /// the binding declared by an earlier `var` of the same statement when - /// there is one, otherwise the module expression itself. + /// The object the clause items are read from: a binding this statement + /// already declared, or else the module itself. fn print_internal_bun_import_value( &mut self, import: &S::Import, @@ -2509,9 +2505,8 @@ pub(crate) mod __gated_printer { return; } else if record.kind == ImportKind::Require || record.kind == ImportKind::Stmt { - // The linker asks for __toESM() when the import needs ESM - // namespace semantics (a default import or `import *`); the - // bare Bun object has no "default" property. + // Set by the linker for default and namespace imports; + // the Bun object itself has no "default". let wrap_with_to_esm = record.flags.contains(ImportRecordFlags::WRAP_WITH_TO_ESM); if wrap_with_to_esm { From 5c8d42746eb7fc4eac5958cde501bcf8c6ef2c64 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:31:51 +0000 Subject: [PATCH 4/7] Shorten comments to one line --- src/bundler/linker_context/convertStmtsForChunk.rs | 3 +-- src/js_printer/lib.rs | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/bundler/linker_context/convertStmtsForChunk.rs b/src/bundler/linker_context/convertStmtsForChunk.rs index e5e817c773bc..bf0d075ce81b 100644 --- a/src/bundler/linker_context/convertStmtsForChunk.rs +++ b/src/bundler/linker_context/convertStmtsForChunk.rs @@ -168,10 +168,9 @@ pub(crate) fn convert_stmts_for_chunk( .flags .contains(ImportRecordFlags::CALLS_RUNTIME_RE_EXPORT_FN) { + // A "bun" import prints as an unhoisted var, too late for __reExport() let is_bun_builtin = record.tag == ImportRecordTag::Bun; let re_exported_module: Expr = if is_bun_builtin { - // A "bun" import prints as a non-hoisted var, which would - // land after the "__reExport()" call below. Expr::init( E::RequireString { import_record_index: s.import_record_index, diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index 5a1d19a2dff6..500559281f91 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -1888,8 +1888,6 @@ pub(crate) mod __gated_printer { } } - /// The object the clause items are read from: a binding this statement - /// already declared, or else the module itself. fn print_internal_bun_import_value( &mut self, import: &S::Import, @@ -2505,8 +2503,7 @@ pub(crate) mod __gated_printer { return; } else if record.kind == ImportKind::Require || record.kind == ImportKind::Stmt { - // Set by the linker for default and namespace imports; - // the Bun object itself has no "default". + // The Bun object has no "default" property; __toESM() adds it. let wrap_with_to_esm = record.flags.contains(ImportRecordFlags::WRAP_WITH_TO_ESM); if wrap_with_to_esm { From 69f726c98124495da7bac4c063ea7f124db7acf2 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:47:46 +0000 Subject: [PATCH 5/7] ci: retrigger From c41e724d65bf16751303ab4fe237a833128e0f3d Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:18:00 +0000 Subject: [PATCH 6/7] Bump the runtime transpiler cache version The printer now lowers `import { default as x } from "bun"` differently, and cached output written by an older binary would otherwise be reused as-is after an upgrade (a release build restoring such an entry still binds x to undefined). --- src/jsc/RuntimeTranspilerCache.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jsc/RuntimeTranspilerCache.rs b/src/jsc/RuntimeTranspilerCache.rs index 3373c1402e99..6539e2ce34db 100644 --- a/src/jsc/RuntimeTranspilerCache.rs +++ b/src/jsc/RuntimeTranspilerCache.rs @@ -51,7 +51,9 @@ bun_core::declare_scope!(cache, visible); /// Version 25: Every ModuleInfo record carries a trailing FetchParameters slot /// so ImportEntry/ExportEntry/StarExportEntry moduleRequestType matches JSC's /// after WebKit 90b2ecf79ae3 keyed m_loadedModules on (specifier, type). -const EXPECTED_VERSION: u32 = 25; +/// Version 26: `import { default as x } from "bun"` binds x to the Bun object +/// instead of destructuring a "default" property that does not exist. +const EXPECTED_VERSION: u32 = 26; /// Source files smaller than this are not written to / read from the on-disk /// transpiler cache. Originally 50 KiB, which excluded almost every file in a From 336d975a3a51116c0a1951d4ad87813056f30ed0 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:19:14 +0000 Subject: [PATCH 7/7] Shorten the cache version note --- src/jsc/RuntimeTranspilerCache.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/jsc/RuntimeTranspilerCache.rs b/src/jsc/RuntimeTranspilerCache.rs index 6539e2ce34db..3b4dc19491f5 100644 --- a/src/jsc/RuntimeTranspilerCache.rs +++ b/src/jsc/RuntimeTranspilerCache.rs @@ -51,8 +51,7 @@ bun_core::declare_scope!(cache, visible); /// Version 25: Every ModuleInfo record carries a trailing FetchParameters slot /// so ImportEntry/ExportEntry/StarExportEntry moduleRequestType matches JSC's /// after WebKit 90b2ecf79ae3 keyed m_loadedModules on (specifier, type). -/// Version 26: `import { default as x } from "bun"` binds x to the Bun object -/// instead of destructuring a "default" property that does not exist. +/// Version 26: `import { default as x } from "bun"` now binds x to the Bun object itself. const EXPECTED_VERSION: u32 = 26; /// Source files smaller than this are not written to / read from the on-disk