diff --git a/src/js_parser/fold.rs b/src/js_parser/fold.rs index ac17087f399a..89abc250a744 100644 --- a/src/js_parser/fold.rs +++ b/src/js_parser/fold.rs @@ -462,7 +462,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if p.should_unwrap_common_js_to_esm() { if !p.is_control_flow_dead && id.ref_.eql(p.exports_ref) { if !p.commonjs_named_exports_deoptimized { - if identifier_opts.is_delete_target() { + // `exports.__proto__` is the prototype of `module.exports`, not an export. + if identifier_opts.is_delete_target() || name == b"__proto__" { p.deoptimize_common_js_named_exports(); return None; } @@ -673,7 +674,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if p.should_unwrap_common_js_to_esm() { if !p.is_control_flow_dead { if !p.commonjs_named_exports_deoptimized { - if identifier_opts.is_delete_target() { + if identifier_opts.is_delete_target() || name == b"__proto__" { p.deoptimize_common_js_named_exports(); return None; } diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 7c5964726e11..40ccd5f64a7d 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -2280,6 +2280,43 @@ describe("bundler", () => { stdout: "1 2 false", }, }); + // `exports.__proto__` is the prototype of `module.exports`, not an export + // named "__proto__". A lifted binding plus a namespace getter cannot stand in + // for it, so these files keep their `__commonJS` wrapper. + itBundled("cjs2esm/ExportsProtoMemberKeepsWrapper", { + files: { + "/entry.js": /* js */ ` + import dot, * as dotNs from "./dot.cjs"; + import bracket from "./bracket.cjs"; + import viaModule from "./module.cjs"; + import read, { c } from "./read.cjs"; + const describe = m => [Object.keys(m), Object.hasOwn(m, "__proto__"), m.inherited, Object.getPrototypeOf(m).inherited]; + console.log(JSON.stringify([describe(dot), describe(bracket), describe(viaModule), dotNs.a, dotNs.default === dot, read.isObjectPrototype, c])); + `, + "/dot.cjs": /* js */ ` + exports.__proto__ = { inherited: 1 }; + exports.a = 1; + `, + "/bracket.cjs": /* js */ ` + exports.b = 2; + exports["__proto__"] = { inherited: 2 }; + `, + "/module.cjs": /* js */ ` + module.exports.c = 3; + module.exports.__proto__ = { inherited: 3 }; + `, + "/read.cjs": /* js */ ` + exports.c = 4; + exports.isObjectPrototype = exports.__proto__ === Object.prototype; + `, + }, + cjs2esm: { + unhandled: ["/dot.cjs", "/bracket.cjs", "/module.cjs", "/read.cjs"], + }, + run: { + stdout: JSON.stringify([[["a"], false, 1, 1], [["b"], false, 2, 2], [["c"], false, 3, 3], 1, true, true, 4]), + }, + }); // Module code makes a top-level function declaration lexical, so a `var` // with the same name is a SyntaxError there. A function body allows it, so // these files keep their `__commonJS` wrapper. In sloppy mode the bundler