mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
Fix preact & other "classic" jsx transforms, most likely
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user