aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-06-12 21:43:45 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-12 21:43:45 -0700
commit067a0235e49c52ac66df10cf2240b7ecb22eb599 (patch)
tree2a69492b64a994920b5d7e5ec45b5fd640df17bb
parentdbb2416542ee391fac5a11ba56090bf946117b9d (diff)
downloadbun-067a0235e49c52ac66df10cf2240b7ecb22eb599.tar.gz
bun-067a0235e49c52ac66df10cf2240b7ecb22eb599.tar.zst
bun-067a0235e49c52ac66df10cf2240b7ecb22eb599.zip
handle unwrapping `require` in any expression (#3292)
-rw-r--r--src/js_parser.zig30
-rw-r--r--test/bundler/bundler_cjs2esm.test.ts64
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",
+ },
+ });
});