Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 41 additions & 23 deletions src/bundler/linker_context/convertStmtsForChunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -69,7 +68,7 @@ pub(crate) fn convert_stmts_for_chunk(
// importing itself should not see the "__esModule" marker but a CommonJS module
// importing us should see the "__esModule" marker.
let mut module_exports_for_export: Option<Expr> = 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 {
Expand Down Expand Up @@ -169,16 +168,36 @@ 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,
);
// 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 {
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");
Expand All @@ -191,13 +210,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)"
Expand Down Expand Up @@ -233,6 +246,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);
Expand Down
2 changes: 0 additions & 2 deletions src/bundler/linker_context/generateCodeForFileInChunkJS.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
114 changes: 67 additions & 47 deletions src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -1838,27 +1828,27 @@ 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 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" {
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 {"));

Expand All @@ -1868,7 +1858,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();
Expand All @@ -1877,6 +1871,7 @@ pub(crate) mod __gated_printer {
self.print_indent();
}
}
i += 1;
self.print_clause_item_as(item, ClauseItemAs::Var);
}

Expand All @@ -1888,26 +1883,40 @@ 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);
}
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),
}
}

Expand Down Expand Up @@ -2494,7 +2503,18 @@ pub(crate) mod __gated_printer {
return;
} else if record.kind == ImportKind::Require || record.kind == ImportKind::Stmt
{
// 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 {
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")");
}
Comment thread
claude[bot] marked this conversation as resolved.
if wrap {
self.print(b")");
}
Expand Down
4 changes: 3 additions & 1 deletion src/jsc/RuntimeTranspilerCache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
robobun marked this conversation as resolved.
Outdated
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
Expand Down
65 changes: 65 additions & 0 deletions test/bundler/bundler_bun.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,71 @@ 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" },
});
// https://github.com/oven-sh/bun/issues/20670 (--bytecode defaults to this format)
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: "",
Expand Down
Loading
Loading