Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions src/bundler/LinkerContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,44 @@ impl<'a> LinkerContext<'a> {
.is_entry_point()
}

fn module_strict_mode(&self, source_index: usize) -> bun_ast::StrictModeKind {
self.graph.ast.items_module_scope()[source_index].strict_mode
}

/// The runtime helpers and data loaders such as JSON have no code of their own to be sloppy.
fn file_is_strict(&self, source_index: usize) -> bool {
source_index == Index::RUNTIME.value() as usize
|| !self.parse_graph().input_files.items_loader()[source_index].is_javascript_like()
|| self.module_strict_mode(source_index) != bun_ast::StrictModeKind::SloppyMode
}

/// Strict code in a `__commonJS` or `__esm` closure needs its own directive in cjs and iife output.
pub(crate) fn wrapper_needs_use_strict(&self, source_index: usize) -> bool {
matches!(self.options.output_format, Format::Cjs | Format::Iife)
&& self.module_strict_mode(source_index) != bun_ast::StrictModeKind::SloppyMode
}

/// An ES module entry point gets the chunk-top directive only when every file in the chunk is strict.
pub(crate) fn entry_chunk_needs_use_strict(&self, chunk: &Chunk) -> bool {
let entry_strict_mode = self.module_strict_mode(chunk.entry_point.source_index() as usize);
match self.options.output_format {
Format::Esm => false,
// The dev server's module closures share the chunk, so only a spelled-out directive is kept.
Format::InternalBakeDev => {
entry_strict_mode == bun_ast::StrictModeKind::ExplicitStrictMode
}
Format::Cjs | Format::Iife => match entry_strict_mode {
bun_ast::StrictModeKind::SloppyMode => false,
bun_ast::StrictModeKind::ExplicitStrictMode => true,
_ => chunk
.files_with_parts_in_chunk
.keys()
.iter()
.all(|&source_index| self.file_is_strict(source_index as usize)),
},
}
}

/// `"sideEffects": false` (or the resolver's equivalent), unless
/// `--ignore-dce-annotations` says not to trust it.
pub(crate) fn file_has_no_side_effects(&self, source_index: u32) -> bool {
Expand Down Expand Up @@ -4287,6 +4325,12 @@ impl InsideWrapperPrefix {
Ok(())
}

/// Inserted first: a directive has to precede the `init_*()` calls added at `sync_dependencies_end`.
pub(crate) fn append_directive(&mut self, stmt: Stmt) {
self.stmts.insert(0, stmt);
self.sync_dependencies_end += 1;
}

fn append_sync_dependency(&mut self, call_expr: Expr) -> Result<(), AllocError> {
self.stmts.insert(
self.sync_dependencies_end,
Expand Down
37 changes: 19 additions & 18 deletions src/bundler/linker_context/generateCodeForFileInChunkJS.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,26 +252,20 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>(
u32::MAX
};

let output_format = c.options.output_format;

// The top-level directive must come first (the non-wrapped case is handled
// by the chunk generation code, although only for the entry point)
// Skipped when the chunk top carries the directive for this file (`post_process_js_chunk`).
let covered_by_chunk_top = chunk.is_entry_point()
&& chunk.entry_point.source_index() as usize == source_index
&& c.entry_chunk_needs_use_strict(chunk);
if flags.wrap != WrapKind::None
&& ast
.flags
.contains(AstFlags::HAS_EXPLICIT_USE_STRICT_DIRECTIVE)
&& !chunk.is_entry_point()
&& !output_format.is_always_strict_mode()
&& !covered_by_chunk_top
&& c.wrapper_needs_use_strict(source_index)
{
stmts
.inside_wrapper_prefix
.append_non_dependency(Stmt::alloc(
S::Directive {
value: bun_ast::StoreStr::new(b"use strict"),
},
bun_ast::Loc::EMPTY,
))
.expect("unreachable");
stmts.inside_wrapper_prefix.append_directive(Stmt::alloc(
S::Directive {
value: bun_ast::StoreStr::new(b"use strict"),
},
bun_ast::Loc::EMPTY,
));
}

// `convert_stmts_for_chunk` takes `&mut c` inside the loop body, so capture
Expand Down Expand Up @@ -780,6 +774,13 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>(
inner_stmts.slice_mut()[end] = transformed;
end += 1;
}
// A closure with only directives stays empty: `needs_wrapper_ref` allocated no symbol for it.
if inner_stmts.slice()[..end]
.iter()
.all(|stmt| matches!(stmt.data, StmtData::SDirective(_)))
{
end = 0;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
inner_stmts.truncate(end);
}

Expand Down
26 changes: 12 additions & 14 deletions src/bundler/linker_context/postProcessJSChunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,16 +410,12 @@ pub(crate) fn post_process_js_chunk(
newline_before_comment = true;
}

// Add the top-level directive if present (but omit "use strict" in ES
// modules because all ES modules are automatically in strict mode)
if chunk.is_entry_point() && !output_format.is_always_strict_mode() {
let flags = c.graph.ast.items_flags()[chunk.entry_point.source_index() as usize];

if flags.contains(crate::bundled_ast::Flags::HAS_EXPLICIT_USE_STRICT_DIRECTIVE) {
j.push_static(b"\"use strict\";\n");
line_offset.advance(b"\"use strict\";\n");
newline_before_comment = true;
}
// For iife the directive goes inside the wrapper below, so it does not leak into concatenated scripts.
let use_strict_directive = chunk.is_entry_point() && c.entry_chunk_needs_use_strict(chunk);
if use_strict_directive && output_format != options::OutputFormat::Iife {
j.push_static(b"\"use strict\";\n");
line_offset.advance(b"\"use strict\";\n");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
newline_before_comment = true;
}

// For Kit, hoist runtime.js outside of the IIFE
Expand Down Expand Up @@ -447,13 +443,15 @@ pub(crate) fn post_process_js_chunk(
}
options::OutputFormat::Iife => {
// Bun does not do arrow function lowering. So the wrapper can be an arrow.
let start: &[u8] = if c.options.minify_whitespace {
b"(()=>{"
} else {
b"(() => {\n"
let start: &[u8] = match (c.options.minify_whitespace, use_strict_directive) {
(true, false) => b"(()=>{",
(true, true) => b"(()=>{\"use strict\";",
(false, false) => b"(() => {\n",
(false, true) => b"(() => {\n \"use strict\";\n",
};
j.push_static(start);
line_offset.advance(start);
newline_before_comment |= use_strict_directive;
}
_ => {} // no wrapper
}
Expand Down
48 changes: 24 additions & 24 deletions test/bundler/bundler_bytecode_portable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,10 @@ describe("bytecode cache portability", () => {
"sha256": "2a5d62fb4ca9d107e3a5bb2abe6a73f3c859a6f1a91c6c3d77653cb8c2053361",
},
"bun build --bytecode --minify all.js": {
"js": "52c1e0868de8da5d8bf4b89d68afbd027f3c64fdce490cd46800674b0de3f0c1",
"js": "7cf9dd3d8bc03517a212eed923d3d3f8d856d53cd6d7fd915af776f50e645dd3",
"jsc": {
"bytes": 1999232,
"sha256": "a6043ef5e8aaae25c9581e6802fc2508f0b7b2fb0681be536768f9d09ef4cac6",
"bytes": 2004632,
"sha256": "3bf715e72f2789878902f0d3bbaa6668d6f6fd0627cc15415ed16ee24235a6b5",
},
},
"bun build --bytecode --minify features.js": {
Expand All @@ -481,17 +481,17 @@ describe("bytecode cache portability", () => {
},
},
"bun build --bytecode acorn/dist/acorn.mjs": {
"js": "2ed858fa1b38a20673cee13a857ccdaedb9da0a325ebc47e81c4851de77eef3c",
"js": "481e1c346358dc5c4d1f901ade95ecc569b8206e4394e070891ebb32e2c75d16",
"jsc": {
"bytes": 266176,
"sha256": "9fec89f154cf2ae1593f52d49bb3ba6bae0634a56e4cc8fe76aabf6abb70bfe1",
"bytes": 266776,
"sha256": "02f137aae885055b35965959836f8bf0af1fa3cb2b69665fe2af91b4fa38a497",
},
},
"bun build --bytecode all.js": {
"js": "46d723ea07e5e2d8db673a51fec52b3248edcd3e216029f243d8172244f7cd2c",
"js": "73f27a1414b26406c14a9d4c95cc8a5eb7271fad3792bfc020d39df02e7cf907",
"jsc": {
"bytes": 2178792,
"sha256": "c5d1eaae5c4d198b1f8e0d768bb41502162081c75b67249032e66a2b95e26ec1",
"bytes": 2184416,
"sha256": "3d1f952144dcd37335b757b12b5a8cfa9497ad7d14cc8fbe37ff9cf5f822b088",
},
},
"bun build --bytecode big.js": {
Expand All @@ -509,24 +509,24 @@ describe("bytecode cache portability", () => {
},
},
"bun build --bytecode happy-dom/lib/index.js": {
"js": "148f0d3e4baf485281725f859deb3e717a6da25a4a08de9af288d5ef54b6414b",
"js": "4172eb9d6db4c3f4d5eb2a5c009ba90f7db27530bdd032414b0c46fe0298bc96",
"jsc": {
"bytes": 2528840,
"sha256": "d68b260282a74f545c15b5a3b812b69cb4e828972f85c5de60f8307b73c43e57",
"bytes": 2531968,
"sha256": "625cc4e89f74a2e82e62494501f2c3f889c3cb082a23692354a894603978acef",
},
},
"bun build --bytecode immutable/dist/immutable.es.js": {
"js": "c9a2ba9f6b6a662e6bdfd44128bc66284276f5eac2a8875adb4578472328dc9f",
"js": "cab5a3ef2865aa8ee9518ac4082265b9cc1ce0ee5d2421b4ef53cbabf7a3e63d",
"jsc": {
"bytes": 280016,
"sha256": "2a99c33a2516e2d41187bc322ddd4523318530b51c60c3d9a8bd566d4f2929a1",
"bytes": 281512,
"sha256": "d77950ed20795e6303e5f56d17a470ede464f8870a00aa74bb0e2b0a87708e97",
},
},
"bun build --bytecode libraries.js": {
"js": "e685164a8316f60b665bc3d47e42b4623cfe7385f87310e655478a50ecd8f959",
"js": "96964ca12b5189cb7354e591f9c1ed2506357e9bf4201b6592d8b1fb487ef892",
"jsc": {
"bytes": 23806352,
"sha256": "5f23309532d868e1c985d568207c92b77a7f3974e5c6c3f37b21accf7d429327",
"bytes": 23874600,
"sha256": "26e62f3ff317ff64680bc2ae9c2cd01e9f0558106d97ef938cd9ecfa2d9c6bd2",
},
},
"bun build --bytecode lodash/lodash.js": {
Expand All @@ -537,10 +537,10 @@ describe("bytecode cache portability", () => {
},
},
"bun build --bytecode react-dom/cjs/react-dom.development.js": {
"js": "e2cf34c8eb6f5952a33ca91e3949b28786349cf97ea3fbf0b3b8500dbccd4860",
"js": "54451a126f78d48cafb01c7c3e9e459bf0f2821d084ed15d29adccfdf2d06875",
"jsc": {
"bytes": 980080,
"sha256": "32fa8492ad9fdf3ea31395756898d556df9dc6bdfbac9bba07bcafa748ab38ca",
"bytes": 980088,
"sha256": "56e259fd06b1fba8ed7dcc1aef8d785a39b4190e45a67479851932c6bbef3b9a",
},
},
"bun build --bytecode records.js": {
Expand All @@ -565,10 +565,10 @@ describe("bytecode cache portability", () => {
},
},
"bun build --bytecode undici/index.js": {
"js": "d0bd3791e7c8f77a06814429d5d95cb26a06baaa3c135502bcd3e984310f1d2c",
"js": "7ebc1a93fd924da1107ea2c12bc0a684686717fb6e382ba90d34bd3f1a9062c3",
"jsc": {
"bytes": 937000,
"sha256": "d7cff4df84668b14987703bcd950a6753a574cf48e4372e4fe8779f747c0ed89",
"bytes": 937032,
"sha256": "ecb2f5eb5472bd2fbd8ab08cf64494afa66a5cb74f352cb26c543f0a32b96a79",
},
},
"vm.Script big.js": {
Expand Down
Loading
Loading