diff options
author | 2023-06-12 21:43:45 -0700 | |
---|---|---|
committer | 2023-06-12 21:43:45 -0700 | |
commit | 067a0235e49c52ac66df10cf2240b7ecb22eb599 (patch) | |
tree | 2a69492b64a994920b5d7e5ec45b5fd640df17bb | |
parent | dbb2416542ee391fac5a11ba56090bf946117b9d (diff) | |
download | bun-067a0235e49c52ac66df10cf2240b7ecb22eb599.tar.gz bun-067a0235e49c52ac66df10cf2240b7ecb22eb599.tar.zst bun-067a0235e49c52ac66df10cf2240b7ecb22eb599.zip |
handle unwrapping `require` in any expression (#3292)
-rw-r--r-- | src/js_parser.zig | 30 | ||||
-rw-r--r-- | test/bundler/bundler_cjs2esm.test.ts | 64 |
2 files changed, 89 insertions, 5 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig index b844aa9b4..7c227ce75 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -308,7 +308,8 @@ const ExportsStringName = "exports"; const TransposeState = struct { is_await_target: bool = false, is_then_catch_target: bool = false, - loc: logger.Loc, + is_require_immediately_assigned_to_decl: bool = false, + loc: logger.Loc = logger.Loc.Empty, }; var true_args = &[_]Expr{ @@ -2248,6 +2249,10 @@ const ExprIn = struct { // isn't something real-world code would do but it matters for conformance // tests. assign_target: js_ast.AssignTarget = js_ast.AssignTarget.none, + + // Currently this is only used when unwrapping a call to `require()` + // with `__toESM()`. + is_immediately_assigned_to_decl: bool = false, }; const ExprOut = struct { @@ -5102,7 +5107,7 @@ fn NewParser_( return state; } - pub fn transposeRequire(p: *P, arg: Expr, _: anytype) Expr { + pub fn transposeRequire(p: *P, arg: Expr, state: anytype) Expr { switch (arg.data) { .e_string => |str| { @@ -5146,6 +5151,13 @@ fn NewParser_( p.import_items_for_namespace.put(p.allocator, namespace_ref, ImportItemForNamespaceMap.init(p.allocator)) catch unreachable; p.ignoreUsage(p.require_ref); p.recordUsage(namespace_ref); + + if (!state.is_require_immediately_assigned_to_decl) { + return p.newExpr(E.Identifier{ + .ref = namespace_ref, + }, arg.loc); + } + return p.newExpr( E.RequireString{ .import_record_index = import_record_index, @@ -16453,15 +16465,18 @@ fn NewParser_( // error from the unbundled require() call failing. if (e_.args.len == 1) { const first = e_.args.first_(); + const state = TransposeState{ + .is_require_immediately_assigned_to_decl = in.is_immediately_assigned_to_decl and first.data == .e_string, + }; switch (first.data) { .e_string => { // require(FOO) => require(FOO) - return p.transposeRequire(first, null); + return p.transposeRequire(first, state); }, .e_if => { // require(FOO ? '123' : '456') => FOO ? require('123') : require('456') // This makes static analysis later easier - return p.require_transposer.maybeTransposeIf(first, null); + return p.require_transposer.maybeTransposeIf(first, state); }, else => {}, } @@ -18936,7 +18951,12 @@ fn NewParser_( } } - decl.value = p.visitExpr(val); + if (only_scan_imports_and_do_not_visit) { + @compileError("only_scan_imports_and_do_not_visit must not run this."); + } + decl.value = p.visitExprInOut(val, .{ + .is_immediately_assigned_to_decl = true, + }); if (comptime FeatureFlags.unwrap_commonjs_to_esm) { if (prev_require_to_convert_count < p.imports_to_convert_from_require.items.len) { diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index 2c498a4c6..01a17356b 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -3,6 +3,19 @@ import dedent from "dedent"; import { itBundled, testForFile } from "./expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); +const fakeReactNodeModules = { + "/node_modules/react/index.js": /* js */ ` + module.exports = { react: "react" } + `, + "/node_modules/react/package.json": /* json */ ` + { + "name": "react", + "version": "2.0.0", + "main": "index.js" + } + `, +}; + describe("bundler", () => { itBundled("cjs2esm/ModuleExportsFunction", { files: { @@ -222,4 +235,55 @@ describe("bundler", () => { stdout: "development", }, }); + itBundled("cjs2esm/UnwrappedModuleRequireAssigned", { + files: { + "/entry.js": /* js */ ` + const react = require("react"); + console.log(react.react); + + const react1 = (console.log(require("react").react), require("react")); + console.log(react1.react); + + const react2 = (require("react"), console.log(require("react").react)); + console.log(react2); + + let x = {}; + x.react = require("react"); + console.log(x.react.react); + + console.log(require("react").react); + + let y = {}; + y[require("react")] = require("react"); + console.log(y[require("react")].react); + + let r = require("react"); + console.log(r.react); + r = require("react"); + console.log(r.react); + + let n = 1; + n = require("react"); + console.log(n.react); + + let m = 1, + o = require("react"); + console.log(m, o.react); + + let h = Math.random() > 0.5; + let p = require(h ? "react" : "react"); + console.log(p.react); + + console.log(require(h ? "react" : "react").react); + `, + ...fakeReactNodeModules, + }, + onAfterBundle: api => { + const code = api.readFile("out.js"); + expect(code).toContain("__toESM("); + }, + run: { + stdout: "react\nreact\nreact\nreact\nundefined\nreact\nreact\nreact\nreact\nreact\nreact\n1 react\nreact\nreact", + }, + }); }); |