mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
4 Commits
dylan/pyth
...
claude/jsx
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06709da16c | ||
|
|
a7c17814e4 | ||
|
|
5618a7db26 | ||
|
|
373bd8314e |
@@ -72,6 +72,8 @@ pub const Flags = struct {
|
||||
pub const JSXElement = enum {
|
||||
is_key_after_spread,
|
||||
has_any_dynamic,
|
||||
/// The element can be inlined to an object literal (no spread props, no ref prop)
|
||||
can_be_inlined,
|
||||
pub const Bitset = std.enums.EnumSet(JSXElement);
|
||||
};
|
||||
|
||||
|
||||
@@ -5571,7 +5571,7 @@ pub fn NewParser_(
|
||||
}
|
||||
}
|
||||
|
||||
fn runtimeIdentifier(p: *P, loc: logger.Loc, comptime name: string) Expr {
|
||||
pub fn runtimeIdentifier(p: *P, loc: logger.Loc, comptime name: string) Expr {
|
||||
const ref = p.runtimeIdentifierRef(loc, name);
|
||||
p.recordUsage(ref);
|
||||
return p.newExpr(
|
||||
|
||||
@@ -8,6 +8,7 @@ pub const Parser = struct {
|
||||
|
||||
pub const Options = struct {
|
||||
jsx: options.JSX.Pragma,
|
||||
jsx_optimization_inline: Runtime.Features.JsxInlineMode = .none,
|
||||
ts: bool = false,
|
||||
keep_names: bool = true,
|
||||
ignore_dce_annotations: bool = false,
|
||||
@@ -44,10 +45,8 @@ pub const Parser = struct {
|
||||
if (did_use_jsx) {
|
||||
if (this.jsx.parse) {
|
||||
this.jsx.hashForRuntimeTranspiler(hasher);
|
||||
// this holds the values for the jsx optimizaiton flags, which have both been removed
|
||||
// as the optimizations break newer versions of react, see https://github.com/oven-sh/bun/issues/11025
|
||||
const jsx_optimizations = [_]bool{ false, false };
|
||||
hasher.update(std.mem.asBytes(&jsx_optimizations));
|
||||
// Include jsx_optimization_inline mode in hash
|
||||
hasher.update(std.mem.asBytes(&@intFromEnum(this.jsx_optimization_inline)));
|
||||
} else {
|
||||
hasher.update("NO_JSX");
|
||||
}
|
||||
@@ -1538,8 +1537,9 @@ const Define = @import("../defines.zig").Define;
|
||||
const importRecord = @import("../import_record.zig");
|
||||
const ImportRecord = importRecord.ImportRecord;
|
||||
|
||||
const RuntimeFeatures = _runtime.Runtime.Features;
|
||||
const RuntimeImports = _runtime.Runtime.Imports;
|
||||
const Runtime = _runtime.Runtime;
|
||||
const RuntimeFeatures = Runtime.Features;
|
||||
const RuntimeImports = Runtime.Imports;
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
|
||||
@@ -26,11 +26,14 @@ pub fn ParseJSXElement(
|
||||
var key_prop_i: i32 = -1;
|
||||
var flags = Flags.JSXElement.Bitset{};
|
||||
var start_tag: ?ExprNodeIndex = null;
|
||||
// Track whether this element can be inlined (no spread props, no ref prop)
|
||||
var can_be_inlined = false;
|
||||
|
||||
// Fragments don't have props
|
||||
// Fragments of the form "React.Fragment" are not parsed as fragments.
|
||||
if (@as(JSXTag.TagType, tag.data) == .tag) {
|
||||
start_tag = tag.data.tag;
|
||||
can_be_inlined = p.options.jsx_optimization_inline.isEnabled();
|
||||
|
||||
var spread_loc: logger.Loc = logger.Loc.Empty;
|
||||
var props = ListManaged(G.Property).init(p.allocator);
|
||||
@@ -46,6 +49,11 @@ pub fn ParseJSXElement(
|
||||
const special_prop = E.JSXElement.SpecialProp.Map.get(prop_name_literal) orelse E.JSXElement.SpecialProp.any;
|
||||
try p.lexer.nextInsideJSXElement();
|
||||
|
||||
// ref prop prevents inlining
|
||||
if (special_prop == .ref) {
|
||||
can_be_inlined = false;
|
||||
}
|
||||
|
||||
if (special_prop == .key) {
|
||||
// <ListItem key>
|
||||
if (p.lexer.token != .t_equals) {
|
||||
@@ -80,6 +88,8 @@ pub fn ParseJSXElement(
|
||||
switch (p.lexer.token) {
|
||||
.t_dot_dot_dot => {
|
||||
try p.lexer.next();
|
||||
// Spread props prevent inlining
|
||||
can_be_inlined = false;
|
||||
|
||||
if (first_spread_prop_i == -1) first_spread_prop_i = i;
|
||||
spread_loc = p.lexer.loc();
|
||||
@@ -148,6 +158,7 @@ pub fn ParseJSXElement(
|
||||
|
||||
const is_key_after_spread = key_prop_i > -1 and first_spread_prop_i > -1 and key_prop_i > first_spread_prop_i;
|
||||
flags.setPresent(.is_key_after_spread, is_key_after_spread);
|
||||
flags.setPresent(.can_be_inlined, can_be_inlined);
|
||||
properties = G.Property.List.moveFromList(&props);
|
||||
if (is_key_after_spread and p.options.jsx.runtime == .automatic and !p.has_classic_runtime_warned) {
|
||||
try p.log.addWarning(p.source, spread_loc, "\"key\" prop after a {...spread} is deprecated in JSX. Falling back to classic runtime.");
|
||||
|
||||
@@ -323,6 +323,84 @@ pub fn VisitExpr(
|
||||
}) catch |err| bun.handleOom(err);
|
||||
}
|
||||
|
||||
// JSX inlining optimization: transform jsx() calls into inline object literals
|
||||
// This avoids the overhead of calling jsx() and merging props at runtime
|
||||
// The output object looks like:
|
||||
// { $$typeof: Symbol.for("react.element"), type: "div", key: null, ref: null, props: {}, _owner: null }
|
||||
if (p.options.jsx_optimization_inline.isEnabled() and e_.flags.contains(.can_be_inlined)) {
|
||||
const key_expr = if (maybe_key_value) |key_value| brk: {
|
||||
// key: void 0 === key ? null : "" + key
|
||||
break :brk switch (key_value.data) {
|
||||
.e_string => key_value,
|
||||
.e_undefined, .e_null => p.newExpr(E.Null{}, key_value.loc),
|
||||
else => p.newExpr(E.If{
|
||||
.test_ = p.newExpr(E.Binary{
|
||||
.left = p.newExpr(E.Undefined{}, key_value.loc),
|
||||
.op = Op.Code.bin_strict_eq,
|
||||
.right = key_value,
|
||||
}, key_value.loc),
|
||||
.yes = p.newExpr(E.Null{}, key_value.loc),
|
||||
.no = p.newExpr(E.Binary{
|
||||
.op = Op.Code.bin_add,
|
||||
.left = p.newExpr(&E.String.empty, key_value.loc),
|
||||
.right = key_value,
|
||||
}, key_value.loc),
|
||||
}, key_value.loc),
|
||||
};
|
||||
} else p.newExpr(E.Null{}, expr.loc);
|
||||
|
||||
const props_object = p.newExpr(E.Object{
|
||||
.properties = props.*,
|
||||
.close_brace_loc = e_.close_tag_loc,
|
||||
}, expr.loc);
|
||||
|
||||
// For component tags (not strings), we need to handle defaultProps
|
||||
const props_expression = brk: {
|
||||
if (tag.data != .e_string) {
|
||||
// We assume defaultProps is supposed to _not_ have side effects
|
||||
const defaultProps = p.newExpr(E.Dot{
|
||||
.name = "defaultProps",
|
||||
.name_loc = tag.loc,
|
||||
.target = tag,
|
||||
.can_be_removed_if_unused = true,
|
||||
.call_can_be_unwrapped_if_unused = .if_unused,
|
||||
}, tag.loc);
|
||||
// props: MyComponent.defaultProps || {}
|
||||
if (props.len == 0) {
|
||||
break :brk p.newExpr(E.Binary{ .op = Op.Code.bin_logical_or, .left = defaultProps, .right = props_object }, defaultProps.loc);
|
||||
} else {
|
||||
var call_args = p.allocator.alloc(Expr, 2) catch bun.outOfMemory();
|
||||
call_args[0..2].* = .{ props_object, defaultProps };
|
||||
// __merge(props, MyComponent.defaultProps)
|
||||
break :brk p.callRuntime(tag.loc, "__merge", call_args);
|
||||
}
|
||||
}
|
||||
break :brk props_object;
|
||||
};
|
||||
|
||||
// Select the right $$typeof based on React version
|
||||
const typeof_expr = switch (p.options.jsx_optimization_inline) {
|
||||
.react_18 => p.runtimeIdentifier(tag.loc, "$$typeof_18"),
|
||||
.react_19 => p.runtimeIdentifier(tag.loc, "$$typeof_19"),
|
||||
.none => unreachable,
|
||||
};
|
||||
|
||||
var jsx_element = p.allocator.alloc(G.Property, 6) catch bun.outOfMemory();
|
||||
jsx_element[0..6].* = .{
|
||||
G.Property{ .key = Expr{ .data = Prefill.Data.@"$$typeof", .loc = tag.loc }, .value = typeof_expr },
|
||||
G.Property{ .key = Expr{ .data = Prefill.Data.type, .loc = tag.loc }, .value = tag },
|
||||
G.Property{ .key = Expr{ .data = Prefill.Data.key, .loc = key_expr.loc }, .value = key_expr },
|
||||
G.Property{ .key = Expr{ .data = Prefill.Data.ref, .loc = expr.loc }, .value = p.newExpr(E.Null{}, expr.loc) },
|
||||
G.Property{ .key = Expr{ .data = Prefill.Data.props, .loc = expr.loc }, .value = props_expression },
|
||||
G.Property{ .key = Expr{ .data = Prefill.Data._owner, .loc = key_expr.loc }, .value = p.newExpr(E.Null{}, expr.loc) },
|
||||
};
|
||||
|
||||
return p.newExpr(E.Object{
|
||||
.properties = G.Property.List.fromOwnedSlice(jsx_element),
|
||||
.close_brace_loc = e_.close_tag_loc,
|
||||
}, expr.loc);
|
||||
}
|
||||
|
||||
// Either:
|
||||
// jsxDEV(type, arguments, key, isStaticChildren, source, self)
|
||||
// jsx(type, arguments, key)
|
||||
@@ -1725,6 +1803,7 @@ const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeIndex = js_ast.ExprNodeIndex;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const Op = js_ast.Op;
|
||||
const Scope = js_ast.Scope;
|
||||
const Stmt = js_ast.Stmt;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
@@ -233,6 +233,29 @@ pub const Config = struct {
|
||||
this.runtime.allow_runtime = flag;
|
||||
}
|
||||
|
||||
if (try object.getTruthy(globalThis, "jsxOptimizationInline")) |jsx_inline| {
|
||||
if (jsx_inline.isString()) {
|
||||
const str = try jsx_inline.toSlice(globalThis, bun.default_allocator);
|
||||
defer str.deinit();
|
||||
if (bun.strings.eqlComptime(str.slice(), "react-18")) {
|
||||
this.runtime.jsx_optimization_inline = .react_18;
|
||||
} else if (bun.strings.eqlComptime(str.slice(), "react-19")) {
|
||||
this.runtime.jsx_optimization_inline = .react_19;
|
||||
} else {
|
||||
return globalThis.throwInvalidArguments("jsxOptimizationInline must be \"react-18\" or \"react-19\"", .{});
|
||||
}
|
||||
} else if (jsx_inline.isBoolean()) {
|
||||
// For backwards compatibility, true means react-18
|
||||
if (jsx_inline.toBoolean()) {
|
||||
this.runtime.jsx_optimization_inline = .react_18;
|
||||
} else {
|
||||
this.runtime.jsx_optimization_inline = .none;
|
||||
}
|
||||
} else {
|
||||
return globalThis.throwInvalidArguments("jsxOptimizationInline must be a string (\"react-18\" or \"react-19\") or boolean", .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (try object.getBooleanLoose(globalThis, "inline")) |flag| {
|
||||
this.runtime.inlining = flag;
|
||||
}
|
||||
@@ -716,6 +739,7 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
|
||||
transpiler.options.auto_import_jsx = config.runtime.auto_import_jsx;
|
||||
transpiler.options.inlining = config.runtime.inlining;
|
||||
transpiler.options.hot_module_reloading = config.runtime.hot_module_reloading;
|
||||
transpiler.options.jsx_optimization_inline = config.runtime.jsx_optimization_inline;
|
||||
transpiler.options.react_fast_refresh = false;
|
||||
|
||||
return this;
|
||||
|
||||
@@ -1207,6 +1207,7 @@ fn runWithSourceCode(
|
||||
} else .none;
|
||||
|
||||
opts.framework = transpiler.options.framework;
|
||||
opts.jsx_optimization_inline = transpiler.options.jsx_optimization_inline;
|
||||
|
||||
opts.ignore_dce_annotations = transpiler.options.ignore_dce_annotations and !source.index.isRuntime();
|
||||
|
||||
|
||||
@@ -441,6 +441,7 @@ pub const Command = struct {
|
||||
keep_names: bool = false,
|
||||
ignore_dce_annotations: bool = false,
|
||||
emit_dce_annotations: bool = true,
|
||||
jsx_inline: Runtime.Features.JsxInlineMode = .none,
|
||||
output_format: options.Format = .esm,
|
||||
bytecode: bool = false,
|
||||
banner: []const u8 = "",
|
||||
@@ -1745,6 +1746,7 @@ const Bunfig = @import("./bunfig.zig").Bunfig;
|
||||
const ColonListType = @import("./cli/colon_list_type.zig").ColonListType;
|
||||
const MacroMap = @import("./resolver/package_json.zig").MacroMap;
|
||||
const RunCommand_ = @import("./cli/run_command.zig").RunCommand;
|
||||
const Runtime = @import("./runtime.zig").Runtime;
|
||||
const TestCommand = @import("./cli/test_command.zig").TestCommand;
|
||||
|
||||
const Install = @import("./install/install.zig");
|
||||
|
||||
@@ -74,6 +74,7 @@ pub const transpiler_params_ = [_]ParamType{
|
||||
clap.parseParam("--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable,
|
||||
clap.parseParam("--jsx-runtime <STR> \"automatic\" (default) or \"classic\"") catch unreachable,
|
||||
clap.parseParam("--jsx-side-effects Treat JSX elements as having side effects (disable pure annotations)") catch unreachable,
|
||||
clap.parseParam("--jsx-inline <STR> Inline JSX elements to object literals: \"react-18\" or \"react-19\"") catch unreachable,
|
||||
clap.parseParam("--ignore-dce-annotations Ignore tree-shaking annotations such as @__PURE__") catch unreachable,
|
||||
};
|
||||
pub const runtime_params_ = [_]ParamType{
|
||||
@@ -935,6 +936,17 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
|
||||
ctx.bundler_options.css_chunking = args.flag("--css-chunking");
|
||||
|
||||
if (args.option("--jsx-inline")) |jsx_inline| {
|
||||
if (strings.eqlComptime(jsx_inline, "react-18")) {
|
||||
ctx.bundler_options.jsx_inline = .react_18;
|
||||
} else if (strings.eqlComptime(jsx_inline, "react-19")) {
|
||||
ctx.bundler_options.jsx_inline = .react_19;
|
||||
} else {
|
||||
Output.prettyErrorln("<r><red>error<r>: Invalid --jsx-inline value: \"{s}\". Expected \"react-18\" or \"react-19\"", .{jsx_inline});
|
||||
Global.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.bundler_options.emit_dce_annotations = args.flag("--emit-dce-annotations") or
|
||||
!ctx.bundler_options.minify_whitespace;
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ pub const BuildCommand = struct {
|
||||
this_transpiler.options.keep_names = ctx.bundler_options.keep_names;
|
||||
this_transpiler.options.emit_dce_annotations = ctx.bundler_options.emit_dce_annotations;
|
||||
this_transpiler.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
|
||||
this_transpiler.options.jsx_optimization_inline = ctx.bundler_options.jsx_inline;
|
||||
|
||||
this_transpiler.options.banner = ctx.bundler_options.banner;
|
||||
this_transpiler.options.footer = ctx.bundler_options.footer;
|
||||
|
||||
@@ -1806,6 +1806,7 @@ pub const BundleOptions = struct {
|
||||
|
||||
ignore_dce_annotations: bool = false,
|
||||
emit_dce_annotations: bool = false,
|
||||
jsx_optimization_inline: Runtime.Features.JsxInlineMode = .none,
|
||||
bytecode: bool = false,
|
||||
|
||||
code_coverage: bool = false,
|
||||
|
||||
@@ -171,8 +171,13 @@ export var __legacyMetadataTS = (k, v) => {
|
||||
|
||||
export var __esm = (fn, res) => () => (fn && (res = fn((fn = 0))), res);
|
||||
|
||||
// This is used for JSX inlining with React.
|
||||
// These are used for JSX inlining with React.
|
||||
// $$typeof is kept for backwards compatibility
|
||||
export var $$typeof = /* @__PURE__ */ Symbol.for("react.element");
|
||||
// $$typeof_18 is for React 18 and earlier (uses "react.element")
|
||||
export var $$typeof_18 = /* @__PURE__ */ Symbol.for("react.element");
|
||||
// $$typeof_19 is for React 19+ (uses "react.transitional.element")
|
||||
export var $$typeof_19 = /* @__PURE__ */ Symbol.for("react.transitional.element");
|
||||
|
||||
export var __jsonParse = /* @__PURE__ */ a => JSON.parse(a);
|
||||
|
||||
|
||||
@@ -175,6 +175,11 @@ pub const Runtime = struct {
|
||||
|
||||
set_breakpoint_on_first_line: bool = false,
|
||||
|
||||
/// JSX element inlining optimization mode.
|
||||
/// Instead of jsx("div", {}, void 0), transform to an inline object literal:
|
||||
/// { $$typeof: Symbol.for("react.element"), type: "div", ... }
|
||||
jsx_optimization_inline: JsxInlineMode = .none,
|
||||
|
||||
trim_unused_imports: bool = false,
|
||||
|
||||
/// Allow runtime usage of require(), converting `require` into `__require`
|
||||
@@ -261,6 +266,8 @@ pub const Runtime = struct {
|
||||
}
|
||||
|
||||
hasher.update(std.mem.asBytes(&bools));
|
||||
// Include jsx_optimization_inline mode in hash
|
||||
hasher.update(std.mem.asBytes(&@intFromEnum(this.jsx_optimization_inline)));
|
||||
}
|
||||
|
||||
pub fn shouldUnwrapRequire(this: *const Features, package_name: string) bool {
|
||||
@@ -278,6 +285,21 @@ pub const Runtime = struct {
|
||||
pub const Map = bun.StringArrayHashMapUnmanaged(ReplaceableExport);
|
||||
};
|
||||
|
||||
/// JSX element inlining optimization mode.
|
||||
/// This transforms jsx() calls into inline object literals for better performance.
|
||||
pub const JsxInlineMode = enum {
|
||||
/// Disabled - no JSX inlining (default)
|
||||
none,
|
||||
/// React 18 compatible - uses Symbol.for("react.element")
|
||||
react_18,
|
||||
/// React 19 compatible - uses Symbol.for("react.transitional.element")
|
||||
react_19,
|
||||
|
||||
pub fn isEnabled(mode: JsxInlineMode) bool {
|
||||
return mode != .none;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ServerComponentsMode = enum {
|
||||
/// Server components is disabled, strings "use client" and "use server" mean nothing.
|
||||
none,
|
||||
@@ -335,6 +357,8 @@ pub const Runtime = struct {
|
||||
__legacyDecorateParamTS: ?Ref = null,
|
||||
__legacyMetadataTS: ?Ref = null,
|
||||
@"$$typeof": ?Ref = null,
|
||||
@"$$typeof_18": ?Ref = null,
|
||||
@"$$typeof_19": ?Ref = null,
|
||||
__using: ?Ref = null,
|
||||
__callDispose: ?Ref = null,
|
||||
__jsonParse: ?Ref = null,
|
||||
@@ -352,6 +376,8 @@ pub const Runtime = struct {
|
||||
"__legacyDecorateParamTS",
|
||||
"__legacyMetadataTS",
|
||||
"$$typeof",
|
||||
"$$typeof_18",
|
||||
"$$typeof_19",
|
||||
"__using",
|
||||
"__callDispose",
|
||||
"__jsonParse",
|
||||
|
||||
@@ -1107,6 +1107,7 @@ pub const Transpiler = struct {
|
||||
|
||||
opts.filepath_hash_for_hmr = file_hash orelse 0;
|
||||
opts.features.auto_import_jsx = transpiler.options.auto_import_jsx;
|
||||
opts.jsx_optimization_inline = transpiler.options.jsx_optimization_inline;
|
||||
opts.warn_about_unbundled_modules = !target.isBun();
|
||||
|
||||
opts.features.inject_jest_globals = this_parse.inject_jest_globals;
|
||||
|
||||
@@ -1519,6 +1519,84 @@ console.log(<div {...obj} key="after" />);`),
|
||||
);
|
||||
});
|
||||
|
||||
describe("JSX inlining optimization", () => {
|
||||
it("inlines JSX elements with react-19", () => {
|
||||
const bun = new Bun.Transpiler({
|
||||
loader: "jsx",
|
||||
jsxOptimizationInline: "react-19",
|
||||
});
|
||||
const result = bun.transformSync("export var foo = <div>hello</div>");
|
||||
expect(result).toContain("$$typeof:");
|
||||
expect(result).toContain("$$typeof_19");
|
||||
expect(result).toContain('type: "div"');
|
||||
expect(result).toContain("props:");
|
||||
});
|
||||
|
||||
it("inlines JSX elements with react-18", () => {
|
||||
const bun = new Bun.Transpiler({
|
||||
loader: "jsx",
|
||||
jsxOptimizationInline: "react-18",
|
||||
});
|
||||
const result = bun.transformSync("export var foo = <div>hello</div>");
|
||||
expect(result).toContain("$$typeof:");
|
||||
expect(result).toContain("$$typeof_18");
|
||||
expect(result).toContain('type: "div"');
|
||||
});
|
||||
|
||||
it("does not inline when spread props are present", () => {
|
||||
const bun = new Bun.Transpiler({
|
||||
loader: "jsx",
|
||||
jsxOptimizationInline: "react-19",
|
||||
});
|
||||
// With spread, it should fall back to jsx call
|
||||
const result = bun.transformSync("export var foo = <div {...props}>hello</div>");
|
||||
expect(result).not.toContain("$$typeof:");
|
||||
expect(result).toContain("jsx");
|
||||
});
|
||||
|
||||
it("does not inline when ref prop is present", () => {
|
||||
const bun = new Bun.Transpiler({
|
||||
loader: "jsx",
|
||||
jsxOptimizationInline: "react-19",
|
||||
});
|
||||
// With ref, it should fall back to jsx call
|
||||
const result = bun.transformSync("export var foo = <div ref={myRef}>hello</div>");
|
||||
expect(result).not.toContain("$$typeof:");
|
||||
expect(result).toContain("jsx");
|
||||
});
|
||||
|
||||
it("does not inline by default (no jsxOptimizationInline)", () => {
|
||||
const bun = new Bun.Transpiler({
|
||||
loader: "jsx",
|
||||
});
|
||||
// Should use jsx/jsxDEV call by default
|
||||
const result = bun.transformSync("export var foo = <div>hello</div>");
|
||||
expect(result).not.toContain("$$typeof:");
|
||||
expect(result).toContain("jsx");
|
||||
});
|
||||
|
||||
it("handles component tags with defaultProps", () => {
|
||||
const bun = new Bun.Transpiler({
|
||||
loader: "jsx",
|
||||
jsxOptimizationInline: "react-19",
|
||||
});
|
||||
const result = bun.transformSync("export var foo = <MyComponent a={1} />");
|
||||
expect(result).toContain("$$typeof:");
|
||||
// Should include merge logic for defaultProps
|
||||
expect(result).toContain("defaultProps");
|
||||
});
|
||||
|
||||
it("handles key prop", () => {
|
||||
const bun = new Bun.Transpiler({
|
||||
loader: "jsx",
|
||||
jsxOptimizationInline: "react-19",
|
||||
});
|
||||
const result = bun.transformSync('export var foo = <div key="my-key">hello</div>');
|
||||
expect(result).toContain("$$typeof:");
|
||||
expect(result).toContain('key: "my-key"');
|
||||
});
|
||||
});
|
||||
|
||||
it("require with a dynamic non-string expression", () => {
|
||||
var nodeTranspiler = new Bun.Transpiler({ platform: "node" });
|
||||
expect(nodeTranspiler.transformSync("require('hi' + bar)")).toBe('require("hi" + bar);\n');
|
||||
|
||||
Reference in New Issue
Block a user