From 621a6901c23aff0297524c0f975b9e10be96809c Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 23 Oct 2026 00:38:17 +0000 Subject: [PATCH 1/3] bundler: bind `exports` in a CommonJS wrapper that prints lifted exports The parser lifts `exports.a = 1` to `var $a = 1` and counts no use of `exports` for it. When the file is wrapped after all, the printer writes `exports.a = 1` again, so the wrapper needs the `exports` parameter. Link step 4 gave that parameter only to files with the `COMMONJS_LIFTED_TO_ESM` flag. A file that the parser itself made CommonJS has no such flag. If the code that made it CommonJS is dead or its statement is removed, no use of `exports` is counted and the wrapper had no parameter: `ReferenceError: exports is not defined`. - Step 4 now also gives `exports` to a wrapped file that has lifted exports and neither the `exports` nor the `module` parameter. - The parser keeps the wrapper when `exports` was assigned and `module` is still used. The printer then writes `module.exports` as written, and a lifted file has no `module`. - A debug assertion in `print_code_for_file_in_chunk_js` holds the rule. --- src/bundler/LinkerContext.rs | 9 + .../linker_context/scanImportsAndExports.rs | 15 +- src/js_parser/parse/parse_entry.rs | 16 +- test/bundler/bundler_cjs2esm.test.ts | 168 ++++++++++++++++++ 4 files changed, 200 insertions(+), 8 deletions(-) diff --git a/src/bundler/LinkerContext.rs b/src/bundler/LinkerContext.rs index ea8170943457..0c3fc95f5099 100644 --- a/src/bundler/LinkerContext.rs +++ b/src/bundler/LinkerContext.rs @@ -2431,6 +2431,15 @@ impl<'a> LinkerContext<'a> { // outlives only this call (see above). unsafe { bun_ptr::detach_lifetime_ref(&self.mangled_props) }; + debug_assert!( + flags.wrap != WrapKind::Cjs + || ast.commonjs_named_exports.count() == 0 + || ast + .flags + .intersects(AstFlags::USES_EXPORTS_REF | AstFlags::USES_MODULE_REF), + "the wrapper prints `exports.x` for a lifted export and has no `exports` parameter" + ); + let print_options = js_printer::Options { bundling: true, // TODO: IIFE diff --git a/src/bundler/linker_context/scanImportsAndExports.rs b/src/bundler/linker_context/scanImportsAndExports.rs index ffca995c85b8..274a1d99616f 100644 --- a/src/bundler/linker_context/scanImportsAndExports.rs +++ b/src/bundler/linker_context/scanImportsAndExports.rs @@ -111,6 +111,8 @@ pub(crate) fn scan_imports_and_exports( let exports_refs: *mut [Ref] = ast.exports_ref; let module_refs: *mut [Ref] = ast.module_ref; let wrapper_refs: *mut [Ref] = ast.wrapper_ref; + let commonjs_named_exports: *mut [bun_ast::ast_result::CommonJSNamedExports] = + ast.commonjs_named_exports; let parts_list: *mut [PartList] = ast.parts; // Element is a *mutable* nullable // pointer (`BundledAst.css: Option<*mut BundlerStyleSheet>`). @@ -621,9 +623,16 @@ pub(crate) fn scan_imports_and_exports( col!(flags)[source_index] = flag; } - let is_lifted_commonjs = col_ref!(ast_flags_list)[source_index] - .contains(AstFlags::COMMONJS_LIFTED_TO_ESM); - if is_lifted_commonjs && flag.wrap == WrapKind::Cjs { + let ast_flags = col_ref!(ast_flags_list)[source_index]; + let is_lifted_commonjs = ast_flags.contains(AstFlags::COMMONJS_LIFTED_TO_ESM); + if flag.wrap == WrapKind::Cjs + && (is_lifted_commonjs + // Any other file with lifted exports, when neither flag gives its + // wrapper the parameter yet. + || (!ast_flags + .intersects(AstFlags::USES_EXPORTS_REF | AstFlags::USES_MODULE_REF) + && col_ref!(commonjs_named_exports)[source_index].count() > 0)) + { // The wrapper prints `exports.x = ...` again, so it takes `exports`. col!(ast_flags_list)[source_index].insert(AstFlags::USES_EXPORTS_REF); } else if is_lifted_commonjs && export_kind != ExportsKind::Cjs { diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index d1cf892e5f49..ad34cc492390 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -1593,12 +1593,18 @@ impl<'a> Parser<'a> { p.symbols.as_mut_slice()[p.exports_ref.inner_index() as usize] .use_count_estimate += export_refs_len as u32; p.deoptimize_commonjs_named_exports(); - } else if p.symbols.as_slice()[p.module_ref.inner_index() as usize] - .use_count_estimate - > p.module_exports_rewrite_count - { + } else { + let module_uses = p.symbols.as_slice() + [p.module_ref.inner_index() as usize] + .use_count_estimate; // `module.constructor` and other uses of `module` need the wrapper. - p.deoptimize_commonjs_named_exports(); + // So does `module.exports` after `exports = ...`: it prints as written. + if module_uses > p.module_exports_rewrite_count + || (p.commonjs_module_exports_assigned_deoptimized + && module_uses > 0) + { + p.deoptimize_commonjs_named_exports(); + } } } } diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 57e31a43c888..95bafdd36513 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -1706,6 +1706,174 @@ describe("bundler", () => { stdout: "helped:in", }, }); + // A wrapped file prints its lifted `exports.x = ...` statements as written, so + // its wrapper takes `exports`. The parser makes each of these files CommonJS + // and counts no use of `exports` for it: the code that does so is dead, or its + // statement is removed. + const wrappedWithNoUseOfExports: Record = { + "/dead-branch.cjs": `exports.a = 1; if (false) { console.log(this); }`, + "/dead-ternary.cjs": `exports.a = 1; exports.b = true ? 2 : this;`, + "/dead-or.cjs": `exports.a = 1; exports.b = 0 || 2 || this;`, + "/dead-and.cjs": `exports.a = 1; false && this.x;`, + "/dead-call.cjs": `exports.a = 1; function f() { return 1; } if (false) { f.call(this); }`, + "/removed-this.cjs": `exports.a = 1; this;`, + "/removed-arrow.cjs": `exports.a = 1; (() => this);`, + "/removed-delete.cjs": `exports.a = 1; (function () { delete exports.a; });`, + "/removed-delete-all.cjs": `exports.a = 1; (function () { delete module.exports; });`, + "/removed-call.cjs": `exports.a = 1; (function () { module.exports(); });`, + "/removed-delete-member.cjs": `exports.a = 1; (function () { delete module.exports.a; });`, + "/removed-store.cjs": `exports.a = 1; (function () { module.exports = {}; });`, + }; + // These already get `exports`, or stay lifted: dead code does not set the marks of the last two. + const wrappedWithAUseOfExports: Record = { + "/live-this.cjs": `exports.a = 1; exports.b = this === exports;`, + "/live-typeof-this.cjs": `exports.a = 1; exports.b = typeof this;`, + "/later-use.cjs": `exports.a = 1; var self = 1 ? globalThis : this; exports.b = typeof self;`, + "/this-first.cjs": `this; exports.a = 1;`, + }; + const stillLifted: Record = { + "/dead-delete.cjs": `exports.a = 1; if (false) { delete exports.a; }`, + "/dead-store.cjs": `exports.a = 1; if (false) { module.exports = {}; }`, + }; + const importAs: Record string> = { + Default: (file, i) => `import f${i} from ".${file}"; const a${i} = f${i}.a;`, + Named: (file, i) => `import { a as a${i} } from ".${file}";`, + Star: (file, i) => `import * as f${i} from ".${file}"; const a${i} = f${i}.a;`, + Require: (file, i) => `const a${i} = require(".${file}").a;`, + }; + const readA = (files: string[], importLine: (file: string, i: number) => string) => + files.map(importLine).join("\n") + `\nconsole.log(${files.map((_, i) => `a${i}`).join(", ")});`; + for (const [form, importLine] of Object.entries(importAs)) { + const wrapped = Object.keys(wrappedWithNoUseOfExports); + itBundled(`cjs2esm/WrappedFileWithLiftedExportsTakesExports${form}Import`, { + files: { "/entry.js": readA(wrapped, importLine), ...wrappedWithNoUseOfExports }, + run: { stdout: wrapped.map(() => "1").join(" ") }, + }); + } + itBundled("cjs2esm/WrappedFileWithLiftedExportsTakesExports", { + files: { + "/entry.js": readA( + Object.keys({ ...wrappedWithNoUseOfExports, ...wrappedWithAUseOfExports, ...stillLifted }), + importAs.Default, + ), + ...wrappedWithNoUseOfExports, + ...wrappedWithAUseOfExports, + ...stillLifted, + }, + cjs2esm: { unhandled: Object.keys({ ...wrappedWithNoUseOfExports, ...wrappedWithAUseOfExports }) }, + onAfterBundle(api) { + const heads = api.readFile("/out.js").match(/__commonJS\(function\(.*?\) \{/g); + expect(heads).toEqual(Array(16).fill("__commonJS(function(exports) {")); + }, + run: { stdout: Array(18).fill("1").join(" ") }, + }); + itBundled("cjs2esm/WrappedEntryPointWithLiftedExportsTakesExports", { + files: { + "/entry.cjs": `exports.a = 1; if (false) { console.log(this); } console.log("loaded");`, + }, + run: { stdout: "loaded" }, + }); + itBundled("cjs2esm/WrappedFileWithLiftedExportsTakesExportsMinifyIdentifiers", { + files: { + "/entry.js": readA(["/dead-branch.cjs", "/removed-this.cjs", "/removed-store.cjs"], importAs.Default), + ...wrappedWithNoUseOfExports, + }, + minifyIdentifiers: true, + run: { stdout: "1 1 1" }, + }); + itBundled("cjs2esm/WrappedFileWithLiftedExportsTakesExportsSplitting", { + files: { + "/entry.js": `const lib = await import("./dead-branch.cjs"); console.log(lib.default.a);`, + ...wrappedWithNoUseOfExports, + }, + outdir: "/out", + splitting: true, + run: { file: "/out/entry.js", stdout: "1" }, + }); + // The production define makes the branch dead. + itBundled("cjs2esm/ThisInDeadDevelopmentBranchKeepsExports", { + files: { + "/entry.js": `import lib from "./lib.cjs"; console.log(lib.a);`, + "/lib.cjs": /* js */ ` + exports.a = 1; + if (process.env.NODE_ENV !== "production") { + console.log("dev", this === exports); + } + `, + }, + define: { "process.env.NODE_ENV": '"production"' }, + run: { stdout: "1" }, + }); + // `process.browser` is `false` for these targets without a flag. + for (const target of ["bun", "node"] as const) { + itBundled(`cjs2esm/ThisInDeadProcessBrowserBranchKeepsExports_${target}`, { + files: { + "/entry.js": `import lib from "./lib.cjs"; console.log(lib.a, lib.a2);`, + "/lib.cjs": /* js */ ` + exports.a = 1; + if (process.browser) { this.a2 = 1; } + `, + }, + target, + run: { stdout: "1 undefined" }, + }); + } + // The parser lifts this file and does not make it CommonJS. The linker wraps it + // for the default import, and its `import` keeps it out of the files the linker + // already gave `exports`. + itBundled("cjs2esm/LinkerWrappedFileWithLiftedExportsTakesExports", { + files: { + "/entry.js": `import React from "react"; console.log(React.a);`, + "/node_modules/react/index.js": `import { z } from "./z.js"; exports.a = z;`, + "/node_modules/react/z.js": `export const z = 7;`, + }, + run: { stdout: "7" }, + }); + // After `exports = ...` the printer writes `module.exports` as written, and only + // a wrapper has a `module`. Here the assignment is dead or removed, so it counts + // no use of `exports` that keeps the wrapper. + const exportsAssignedWithNoUse: Record = { + "/dead-assign.cjs": `exports.a = 1; if (false) { exports = {}; } exports.b = typeof module.exports;`, + "/dead-ternary-assign.cjs": `exports.a = 1; exports.b = 1 ? 2 : (exports = {}); exports.c = typeof module.exports;`, + "/removed-assign.cjs": `exports.a = 1; (function () { exports = {}; }); exports.b = typeof module.exports;`, + // Lifted, this file would run too. The parser does not tell the store from a bare `module.exports`. + "/member-store.cjs": `exports.a = 1; if (false) { exports = {}; } module.exports.b = 2;`, + }; + for (const form of ["Named", "Star"] as const) { + itBundled(`cjs2esm/ModuleExportsAfterDeadExportsAssignmentKeepsWrapper${form}Import`, { + files: { + "/entry.js": + form === "Named" + ? /* js */ ` + import { a, b } from "./dead-assign.cjs"; + import { b as b1, c } from "./dead-ternary-assign.cjs"; + import { b as b2 } from "./removed-assign.cjs"; + import { a as a3, b as b3 } from "./member-store.cjs"; + console.log(a, b, b1, c, b2, a3, b3); + ` + : /* js */ ` + import * as f0 from "./dead-assign.cjs"; + import * as f1 from "./dead-ternary-assign.cjs"; + import * as f2 from "./removed-assign.cjs"; + import * as f3 from "./member-store.cjs"; + console.log(f0.a, f0.b, f1.b, f1.c, f2.b, f3.a, f3.b); + `, + ...exportsAssignedWithNoUse, + }, + cjs2esm: { unhandled: Object.keys(exportsAssignedWithNoUse) }, + onAfterBundle(api) { + const heads = api.readFile("/out.js").match(/__commonJS\(function\(.*?\) \{/g); + expect(heads).toEqual(Array(4).fill("__commonJS(function(exports, module) {")); + }, + run: { stdout: "1 object 2 object object 1 2" }, + }); + } + itBundled("cjs2esm/ModuleExportsAfterDeadExportsAssignmentKeepsWrapperEntryPoint", { + files: { + "/entry.cjs": `exports.a = 1; if (false) { exports = {}; } console.log(typeof module.exports);`, + }, + run: { stdout: "object" }, + }); itBundled("cjs2esm/DefaultImportJsxClassicRuntime", { files: { "/entry.jsx": /* jsx */ ` From f0c70ff0e9678c6c0ad40aed5341bd4d5791cbd5 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:46:49 +0000 Subject: [PATCH 2/3] bundler: keep the parser's wrap rule as on main, enable three define tests The parser rule kept the wrapper only for a file with a dead or removed `exports = ...`. The same `module.exports` read without that assignment still prints a name that nothing declares, and a file with a `module.exports.x = ...` store lost its lift. Remove the rule and its tests. The linker rule stays. `extra/DefineExports1`, `extra/DefineExports2` and `extra/DefineExportsArray2` failed with `exports is not defined` and pass with the linker rule, so they are no longer `todo`. --- src/js_parser/parse/parse_entry.rs | 16 +++------ test/bundler/bundler_cjs2esm.test.ts | 51 ++-------------------------- test/bundler/esbuild/extra.test.ts | 3 -- 3 files changed, 8 insertions(+), 62 deletions(-) diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index ad34cc492390..d1cf892e5f49 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -1593,18 +1593,12 @@ impl<'a> Parser<'a> { p.symbols.as_mut_slice()[p.exports_ref.inner_index() as usize] .use_count_estimate += export_refs_len as u32; p.deoptimize_commonjs_named_exports(); - } else { - let module_uses = p.symbols.as_slice() - [p.module_ref.inner_index() as usize] - .use_count_estimate; + } else if p.symbols.as_slice()[p.module_ref.inner_index() as usize] + .use_count_estimate + > p.module_exports_rewrite_count + { // `module.constructor` and other uses of `module` need the wrapper. - // So does `module.exports` after `exports = ...`: it prints as written. - if module_uses > p.module_exports_rewrite_count - || (p.commonjs_module_exports_assigned_deoptimized - && module_uses > 0) - { - p.deoptimize_commonjs_named_exports(); - } + p.deoptimize_commonjs_named_exports(); } } } diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 95bafdd36513..5c76b3a8a4bb 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -1724,13 +1724,14 @@ describe("bundler", () => { "/removed-delete-member.cjs": `exports.a = 1; (function () { delete module.exports.a; });`, "/removed-store.cjs": `exports.a = 1; (function () { module.exports = {}; });`, }; - // These already get `exports`, or stay lifted: dead code does not set the marks of the last two. + // A counted use of `exports` already gives these wrappers the parameter. const wrappedWithAUseOfExports: Record = { "/live-this.cjs": `exports.a = 1; exports.b = this === exports;`, "/live-typeof-this.cjs": `exports.a = 1; exports.b = typeof this;`, "/later-use.cjs": `exports.a = 1; var self = 1 ? globalThis : this; exports.b = typeof self;`, "/this-first.cjs": `this; exports.a = 1;`, }; + // Dead code of these kinds does not make the file CommonJS. const stillLifted: Record = { "/dead-delete.cjs": `exports.a = 1; if (false) { delete exports.a; }`, "/dead-store.cjs": `exports.a = 1; if (false) { module.exports = {}; }`, @@ -1762,8 +1763,7 @@ describe("bundler", () => { }, cjs2esm: { unhandled: Object.keys({ ...wrappedWithNoUseOfExports, ...wrappedWithAUseOfExports }) }, onAfterBundle(api) { - const heads = api.readFile("/out.js").match(/__commonJS\(function\(.*?\) \{/g); - expect(heads).toEqual(Array(16).fill("__commonJS(function(exports) {")); + api.expectFile("/out.js").not.toContain("__commonJS(function() {"); }, run: { stdout: Array(18).fill("1").join(" ") }, }); @@ -1829,51 +1829,6 @@ describe("bundler", () => { }, run: { stdout: "7" }, }); - // After `exports = ...` the printer writes `module.exports` as written, and only - // a wrapper has a `module`. Here the assignment is dead or removed, so it counts - // no use of `exports` that keeps the wrapper. - const exportsAssignedWithNoUse: Record = { - "/dead-assign.cjs": `exports.a = 1; if (false) { exports = {}; } exports.b = typeof module.exports;`, - "/dead-ternary-assign.cjs": `exports.a = 1; exports.b = 1 ? 2 : (exports = {}); exports.c = typeof module.exports;`, - "/removed-assign.cjs": `exports.a = 1; (function () { exports = {}; }); exports.b = typeof module.exports;`, - // Lifted, this file would run too. The parser does not tell the store from a bare `module.exports`. - "/member-store.cjs": `exports.a = 1; if (false) { exports = {}; } module.exports.b = 2;`, - }; - for (const form of ["Named", "Star"] as const) { - itBundled(`cjs2esm/ModuleExportsAfterDeadExportsAssignmentKeepsWrapper${form}Import`, { - files: { - "/entry.js": - form === "Named" - ? /* js */ ` - import { a, b } from "./dead-assign.cjs"; - import { b as b1, c } from "./dead-ternary-assign.cjs"; - import { b as b2 } from "./removed-assign.cjs"; - import { a as a3, b as b3 } from "./member-store.cjs"; - console.log(a, b, b1, c, b2, a3, b3); - ` - : /* js */ ` - import * as f0 from "./dead-assign.cjs"; - import * as f1 from "./dead-ternary-assign.cjs"; - import * as f2 from "./removed-assign.cjs"; - import * as f3 from "./member-store.cjs"; - console.log(f0.a, f0.b, f1.b, f1.c, f2.b, f3.a, f3.b); - `, - ...exportsAssignedWithNoUse, - }, - cjs2esm: { unhandled: Object.keys(exportsAssignedWithNoUse) }, - onAfterBundle(api) { - const heads = api.readFile("/out.js").match(/__commonJS\(function\(.*?\) \{/g); - expect(heads).toEqual(Array(4).fill("__commonJS(function(exports, module) {")); - }, - run: { stdout: "1 object 2 object object 1 2" }, - }); - } - itBundled("cjs2esm/ModuleExportsAfterDeadExportsAssignmentKeepsWrapperEntryPoint", { - files: { - "/entry.cjs": `exports.a = 1; if (false) { exports = {}; } console.log(typeof module.exports);`, - }, - run: { stdout: "object" }, - }); itBundled("cjs2esm/DefaultImportJsxClassicRuntime", { files: { "/entry.jsx": /* jsx */ ` diff --git a/test/bundler/esbuild/extra.test.ts b/test/bundler/esbuild/extra.test.ts index 7cd5621a0b63..bf5566b7f8be 100644 --- a/test/bundler/esbuild/extra.test.ts +++ b/test/bundler/esbuild/extra.test.ts @@ -611,7 +611,6 @@ describe("bundler", () => { run: true, }); itBundled("extra/DefineExports1", { - todo: true, files: { "in.js": `if (exports.x !== void 0) throw 'fail'; this;`, }, @@ -619,7 +618,6 @@ describe("bundler", () => { run: true, }); itBundled("extra/DefineExports2", { - todo: true, files: { "in.js": `if (exports.foo !== void 0) throw 'fail'; this;`, }, @@ -663,7 +661,6 @@ describe("bundler", () => { run: true, }); itBundled("extra/DefineExportsArray2", { - todo: true, files: { "in.js": `if (exports.foo !== void 0) throw 'fail'; this`, }, From eaaec2c94dc9afd47038c5db1b59869a5504df22 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:55:23 +0000 Subject: [PATCH 3/3] bundler: name the wrapper-parameter test in link step 4 The name replaces a two-line comment. Shorten two test comments. --- src/bundler/linker_context/scanImportsAndExports.rs | 7 +++---- test/bundler/bundler_cjs2esm.test.ts | 10 +++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/bundler/linker_context/scanImportsAndExports.rs b/src/bundler/linker_context/scanImportsAndExports.rs index 274a1d99616f..f0c9848dffce 100644 --- a/src/bundler/linker_context/scanImportsAndExports.rs +++ b/src/bundler/linker_context/scanImportsAndExports.rs @@ -625,12 +625,11 @@ pub(crate) fn scan_imports_and_exports( let ast_flags = col_ref!(ast_flags_list)[source_index]; let is_lifted_commonjs = ast_flags.contains(AstFlags::COMMONJS_LIFTED_TO_ESM); + let wrapper_has_exports = + ast_flags.intersects(AstFlags::USES_EXPORTS_REF | AstFlags::USES_MODULE_REF); if flag.wrap == WrapKind::Cjs && (is_lifted_commonjs - // Any other file with lifted exports, when neither flag gives its - // wrapper the parameter yet. - || (!ast_flags - .intersects(AstFlags::USES_EXPORTS_REF | AstFlags::USES_MODULE_REF) + || (!wrapper_has_exports && col_ref!(commonjs_named_exports)[source_index].count() > 0)) { // The wrapper prints `exports.x = ...` again, so it takes `exports`. diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 5c76b3a8a4bb..5e90112e218b 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -1706,10 +1706,8 @@ describe("bundler", () => { stdout: "helped:in", }, }); - // A wrapped file prints its lifted `exports.x = ...` statements as written, so - // its wrapper takes `exports`. The parser makes each of these files CommonJS - // and counts no use of `exports` for it: the code that does so is dead, or its - // statement is removed. + // A wrapped file prints its lifted `exports.x = ...` as written, so its wrapper takes `exports`. + // Here dead code or a removed statement makes the file CommonJS and counts no use of `exports`. const wrappedWithNoUseOfExports: Record = { "/dead-branch.cjs": `exports.a = 1; if (false) { console.log(this); }`, "/dead-ternary.cjs": `exports.a = 1; exports.b = true ? 2 : this;`, @@ -1818,9 +1816,7 @@ describe("bundler", () => { run: { stdout: "1 undefined" }, }); } - // The parser lifts this file and does not make it CommonJS. The linker wraps it - // for the default import, and its `import` keeps it out of the files the linker - // already gave `exports`. + // The linker wraps this file for the default import. No parser mark is involved. itBundled("cjs2esm/LinkerWrappedFileWithLiftedExportsTakesExports", { files: { "/entry.js": `import React from "react"; console.log(React.a);`,