aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/js_parser.zig126
-rw-r--r--src/options.zig7
-rw-r--r--test/bun.js/transpiler.test.js49
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",