diff options
-rw-r--r-- | src/js_parser.zig | 126 | ||||
-rw-r--r-- | src/options.zig | 7 | ||||
-rw-r--r-- | test/bun.js/transpiler.test.js | 49 |
3 files changed, 162 insertions, 20 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig index f7e4b06ff..379db29f5 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2789,14 +2789,10 @@ pub const Parser = struct { }, loc, ), - .value = p.e( - E.Dot{ - .target = dot_call_target, - .name = p.options.jsx.factory[p.options.jsx.factory.len - 1], - .name_loc = loc, - .can_be_removed_if_unused = true, - }, + .value = p.memberExpression( loc, + dot_call_target, + if (p.options.jsx.factory.len > 1) p.options.jsx.factory[1..] else p.options.jsx.factory, ), }; decl_i += 1; @@ -2812,14 +2808,10 @@ pub const Parser = struct { }, loc, ), - .value = p.e( - E.Dot{ - .target = dot_call_target, - .name = p.options.jsx.fragment[p.options.jsx.fragment.len - 1], - .name_loc = loc, - .can_be_removed_if_unused = true, - }, + .value = p.memberExpression( loc, + dot_call_target, + if (p.options.jsx.fragment.len > 1) p.options.jsx.fragment[1..] else p.options.jsx.fragment, ), }; decl_i += 1; @@ -2952,6 +2944,49 @@ pub const Parser = struct { .tag = .jsx_import, }) catch unreachable; } + } else { + const jsx_fragment_symbol: Symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; + const jsx_factory_symbol: Symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; + + // inject + // var jsxFrag = + if (jsx_fragment_symbol.use_count_estimate + jsx_factory_symbol.use_count_estimate > 0) { + const total = @as(usize, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + @as(usize, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)); + var declared_symbols = try std.ArrayList(js_ast.DeclaredSymbol).initCapacity(p.allocator, total); + var decls = try std.ArrayList(G.Decl).initCapacity(p.allocator, total); + var part_stmts = try p.allocator.alloc(Stmt, 1); + + if (jsx_fragment_symbol.use_count_estimate > 0) declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_fragment.ref, .is_top_level = true }); + if (jsx_factory_symbol.use_count_estimate > 0) declared_symbols.appendAssumeCapacity(.{ .ref = p.jsx_factory.ref, .is_top_level = true }); + + if (jsx_fragment_symbol.use_count_estimate > 0) + decls.appendAssumeCapacity(G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_fragment.ref, + }, + logger.Loc.Empty, + ), + .value = try p.jsxStringsToMemberExpression(logger.Loc.Empty, p.options.jsx.fragment), + }); + + if (jsx_factory_symbol.use_count_estimate > 0) + decls.appendAssumeCapacity(G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_factory.ref, + }, + logger.Loc.Empty, + ), + .value = try p.jsxStringsToMemberExpression(logger.Loc.Empty, p.options.jsx.factory), + }); + part_stmts[0] = p.s(S.Local{ .kind = .k_var, .decls = decls.items }, logger.Loc.Empty); + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols.items, + .tag = .jsx_import, + }) catch unreachable; + } } if (!did_import_fast_refresh and p.options.features.react_fast_refresh) { @@ -11978,7 +12013,7 @@ fn NewParser_( // esbuild's version of this function is much more complicated. // I'm not sure why defines is strictly relevant for this case // do people do <API_URL>? - fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, ref: Ref) Expr { + fn jsxRefToMemberExpression(p: *P, loc: logger.Loc, ref: Ref) Expr { p.recordUsage(ref); return p.e(E.Identifier{ .ref = ref, @@ -11987,6 +12022,57 @@ fn NewParser_( }, loc); } + fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, parts: []const []const u8) !Expr { + const result = try p.findSymbol(loc, parts[0]); + + var value = p.handleIdentifier( + loc, + E.Identifier{ + .ref = result.ref, + .must_keep_due_to_with_stmt = result.is_inside_with_scope, + .can_be_removed_if_unused = true, + }, + parts[0], + .{ + .was_originally_identifier = true, + }, + ); + if (parts.len > 1) { + return p.memberExpression(loc, value, parts[1..]); + } + + return value; + } + + fn memberExpression(p: *P, loc: logger.Loc, initial_value: Expr, parts: []const []const u8) Expr { + var value = initial_value; + + for (parts) |part| { + if (p.maybeRewritePropertyAccess( + loc, + value, + part, + loc, + false, + )) |rewrote| { + value = rewrote; + } else { + value = p.e( + E.Dot{ + .target = value, + .name = part, + .name_loc = loc, + + .can_be_removed_if_unused = true, + }, + loc, + ); + } + } + + return value; + } + // Note: The caller has already parsed the "import" keyword fn parseImportExpr(p: *P, loc: logger.Loc, level: Level) anyerror!Expr { // Parse an "import.meta" expression @@ -13535,7 +13621,7 @@ fn NewParser_( if (e_.tag) |_tag| { break :tagger p.visitExpr(_tag); } else { - break :tagger p.jsxStringsToMemberExpression(expr.loc, p.jsx_fragment.ref); + break :tagger p.jsxRefToMemberExpression(expr.loc, p.jsx_fragment.ref); } }; @@ -13612,7 +13698,7 @@ fn NewParser_( // Call createElement() return p.e(E.Call{ - .target = p.jsxStringsToMemberExpression(expr.loc, p.jsx_factory.ref), + .target = p.jsxRefToMemberExpression(expr.loc, p.jsx_factory.ref), .args = ExprNodeList.init(args[0..i]), // Enable tree shaking .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, @@ -13884,7 +13970,7 @@ fn NewParser_( } return p.e(E.Call{ - .target = p.jsxStringsToMemberExpressionAutomatic(expr.loc, is_static_jsx), + .target = p.jsxRefToMemberExpressionAutomatic(expr.loc, is_static_jsx), .args = ExprNodeList.init(args), // Enable tree shaking .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, @@ -15688,8 +15774,8 @@ fn NewParser_( } } - fn jsxStringsToMemberExpressionAutomatic(p: *P, loc: logger.Loc, is_static: bool) Expr { - return p.jsxStringsToMemberExpression(loc, if (is_static and !p.options.jsx.development) + fn jsxRefToMemberExpressionAutomatic(p: *P, loc: logger.Loc, is_static: bool) Expr { + return p.jsxRefToMemberExpression(loc, if (is_static and !p.options.jsx.development) p.jsxs_runtime.ref else p.jsx_runtime.ref); diff --git a/src/options.zig b/src/options.zig index c709142a1..e0a405184 100644 --- a/src/options.zig +++ b/src/options.zig @@ -800,6 +800,13 @@ pub const ESMConditions = struct { }; pub const JSX = struct { + pub const RuntimeMap = bun.ComptimeStringMap(JSX.Runtime, .{ + .{ "react", JSX.Runtime.classic }, + .{ "react-jsx", JSX.Runtime.automatic }, + .{ "react-jsxDEV", JSX.Runtime.automatic }, + .{ "solid", JSX.Runtime.solid }, + }); + pub const Pragma = struct { // these need to be arrays factory: []const string = Defaults.Factory, diff --git a/test/bun.js/transpiler.test.js b/test/bun.js/transpiler.test.js index 3d3bcc109..e10e4339f 100644 --- a/test/bun.js/transpiler.test.js +++ b/test/bun.js/transpiler.test.js @@ -317,6 +317,55 @@ describe("Bun.Transpiler", () => { `; + it("jsxFactory (two level)", () => { + var bun = new Bun.Transpiler({ + loader: "jsx", + allowBunRuntime: false, + tsconfig: JSON.stringify({ + compilerOptions: { + jsxFragmentFactory: "foo.frag", + jsx: "react", + jsxFactory: "foo.factory", + }, + }), + }); + + const element = bun.transformSync(` +export default <div>hi</div> + `); + + expect(element.includes("var jsxEl = foo.factory;")).toBe(true); + + const fragment = bun.transformSync(` +export default <>hi</> + `); + expect(fragment.includes("var JSXFrag = foo.frag,")).toBe(true); + }); + + it("jsxFactory (one level)", () => { + var bun = new Bun.Transpiler({ + loader: "jsx", + allowBunRuntime: false, + tsconfig: JSON.stringify({ + compilerOptions: { + jsxFragmentFactory: "foo.frag", + jsx: "react", + jsxFactory: "h", + }, + }), + }); + + const element = bun.transformSync(` +export default <div>hi</div> + `); + expect(element.includes("var jsxEl = h;")).toBe(true); + + const fragment = bun.transformSync(` +export default <>hi</> + `); + expect(fragment.includes("var JSXFrag = foo.frag,")).toBe(true); + }); + it("JSX", () => { var bun = new Bun.Transpiler({ loader: "jsx", |