diff options
Diffstat (limited to 'src/js_parser.zig')
-rw-r--r-- | src/js_parser.zig | 475 |
1 files changed, 393 insertions, 82 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig index 89a3e82ca..4ef43865e 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -995,6 +995,8 @@ const StaticSymbolName = struct { pub const ImportSource = NewStaticSymbol("JSX"); pub const ClassicImportSource = NewStaticSymbol("JSXClassic"); pub const jsxFilename = NewStaticSymbolWithBackup("fileName", "jsxFileName"); + pub const REACT_ELEMENT_TYPE = NewStaticSymbolWithBackup("$$typeof", "$$reactEl"); + pub const Symbol = NewStaticSymbolWithBackup("Symbol", "Symbol"); pub const Factory = NewStaticSymbol("jsxEl"); pub const Refresher = NewStaticSymbol("FastRefresh"); pub const Fragment = NewStaticSymbol("JSXFrag"); @@ -2548,7 +2550,10 @@ pub const Parser = struct { // Auto-import & post-process JSX switch (comptime ParserType.jsx_transform_type) { .react => { - const jsx_filename_symbol = p.symbols.items[p.jsx_filename.ref.innerIndex()]; + const jsx_filename_symbol = if (p.options.jsx.development) + p.symbols.items[p.jsx_filename.ref.innerIndex()] + else + Symbol{ .original_name = "" }; { const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; @@ -2582,6 +2587,9 @@ pub const Parser = struct { if (p.options.features.auto_import_jsx) { const jsx_classic_symbol = p.symbols.items[p.jsx_classic.ref.innerIndex()]; const jsx_automatic_symbol = p.symbols.items[p.jsx_automatic.ref.innerIndex()]; + const react_element_symbol = if (p.options.features.jsx_optimization_inline) p.symbols.items[p.react_element_type.ref.innerIndex()] else Symbol{ + .original_name = "IF_YOU_SEE_THIS_ITS_A_BUG_IN_BUN_WHERE_REACT_ELEMENT_SYMBOL_IS_BEING_ADDED_WHEN_IT_SHOULDNT_BE_PLEASE_REPORT_IT", + }; // JSX auto-imports // The classic runtime is a different import than the main import @@ -2589,7 +2597,7 @@ pub const Parser = struct { // 1. If you use a spread operator like this: <div foo bar key="foo" {...props} baz /> // 2. If you use a React.Fragment // So we have to support both. - if (jsx_classic_symbol.use_count_estimate > 0 or jsx_automatic_symbol.use_count_estimate > 0) { + if (jsx_classic_symbol.use_count_estimate > 0 or jsx_automatic_symbol.use_count_estimate > 0 or react_element_symbol.use_count_estimate > 0) { // These must unfortunately be copied // p.symbols may grow during this scope // if it grows, the previous pointers are invalidated @@ -2602,6 +2610,11 @@ pub const Parser = struct { const automatic_namespace_ref = p.jsx_automatic.ref; const decls_count: u32 = + // "REACT_ELEMENT_TYPE" + // "Symbol.for('react.element')" + @intCast(u32, @boolToInt(react_element_symbol.use_count_estimate > 0)) * 2 + + + // "JSX" @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) * 2 + @intCast(u32, @boolToInt(jsx_static_symbol.use_count_estimate > 0)) * 2 + @intCast(u32, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)) + @@ -2632,6 +2645,50 @@ pub const Parser = struct { var require_call_args_i: usize = 0; var stmt_i: usize = 0; + if (react_element_symbol.use_count_estimate > 0) { + declared_symbols[declared_symbols_i] = .{ .ref = p.react_element_type.ref, .is_top_level = true }; + declared_symbols_i += 1; + p.recordUsage(p.es6_symbol_global.ref); + var call_args = p.allocator.alloc(Expr, 1) catch unreachable; + call_args[0] = Expr{ .data = Prefill.Data.REACT_ELEMENT_TYPE, .loc = logger.Loc.Empty }; + + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.react_element_type.ref, + }, + loc, + ), + .value = p.e( + E.Call{ + // Symbol.for + .target = p.e( + E.Dot{ + .name = "for", + .name_loc = logger.Loc.Empty, + .target = p.e( + E.Identifier{ + .ref = p.es6_symbol_global.ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .args = ExprNodeList.init(call_args), + .close_paren_loc = logger.Loc.Empty, + .can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + }; + decl_i += 1; + } + if (jsx_symbol.use_count_estimate > 0 or jsx_static_symbol.use_count_estimate > 0) { declared_symbols[declared_symbols_i] = .{ .ref = automatic_namespace_ref, .is_top_level = true }; declared_symbols_i += 1; @@ -2872,6 +2929,60 @@ pub const Parser = struct { .tag = .jsx_import, }) catch unreachable; } + } else if (p.options.features.jsx_optimization_inline) { + const react_element_symbol = p.symbols.items[p.react_element_type.ref.innerIndex()]; + + if (react_element_symbol.use_count_estimate > 0) { + var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1); + var decls = try p.allocator.alloc(G.Decl, 1); + var part_stmts = try p.allocator.alloc(Stmt, 1); + + declared_symbols[0] = .{ .ref = p.react_element_type.ref, .is_top_level = true }; + p.recordUsage(p.es6_symbol_global.ref); + var call_args = p.allocator.alloc(Expr, 1) catch unreachable; + call_args[0] = Expr{ .data = Prefill.Data.REACT_ELEMENT_TYPE, .loc = logger.Loc.Empty }; + + decls[0] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.react_element_type.ref, + }, + logger.Loc.Empty, + ), + .value = p.e( + E.Call{ + // Symbol.for + .target = p.e( + E.Dot{ + .name = "for", + .name_loc = logger.Loc.Empty, + .target = p.e( + E.Identifier{ + .ref = p.es6_symbol_global.ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + .args = ExprNodeList.init(call_args), + .close_paren_loc = logger.Loc.Empty, + .can_be_unwrapped_if_unused = true, + }, + logger.Loc.Empty, + ), + }; + part_stmts[0] = p.s(S.Local{ .kind = .k_var, .decls = decls }, logger.Loc.Empty); + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols, + .tag = .jsx_import, + }) catch unreachable; + } } if (!did_import_fast_refresh and p.options.features.react_fast_refresh) { @@ -3261,11 +3372,11 @@ pub const Prefill = struct { }; }; pub const StringLiteral = struct { - pub var Key = [3]u8{ 'k', 'e', 'y' }; - pub var Children = [_]u8{ 'c', 'h', 'i', 'l', 'd', 'r', 'e', 'n' }; - pub var Filename = [_]u8{ 'f', 'i', 'l', 'e', 'N', 'a', 'm', 'e' }; - pub var LineNumber = [_]u8{ 'l', 'i', 'n', 'e', 'N', 'u', 'm', 'b', 'e', 'r' }; - pub var ColumnNumber = [_]u8{ 'c', 'o', 'l', 'u', 'm', 'n', 'N', 'u', 'm', 'b', 'e', 'r' }; + pub const Key = [3]u8{ 'k', 'e', 'y' }; + pub const Children = [_]u8{ 'c', 'h', 'i', 'l', 'd', 'r', 'e', 'n' }; + pub const Filename = [_]u8{ 'f', 'i', 'l', 'e', 'N', 'a', 'm', 'e' }; + pub const LineNumber = [_]u8{ 'l', 'i', 'n', 'e', 'N', 'u', 'm', 'b', 'e', 'r' }; + pub const ColumnNumber = [_]u8{ 'c', 'o', 'l', 'u', 'm', 'n', 'N', 'u', 'm', 'b', 'e', 'r' }; }; pub const Value = struct { pub const EThis = E.This{}; @@ -3277,6 +3388,13 @@ pub const Prefill = struct { pub var Filename = E.String{ .data = &Prefill.StringLiteral.Filename }; pub var LineNumber = E.String{ .data = &Prefill.StringLiteral.LineNumber }; pub var ColumnNumber = E.String{ .data = &Prefill.StringLiteral.ColumnNumber }; + + pub var @"$$typeof" = E.String{ .data = "$$typeof" }; + pub var @"type" = E.String{ .data = "type" }; + pub var @"ref" = E.String{ .data = "ref" }; + pub var @"props" = E.String{ .data = "props" }; + pub var @"_owner" = E.String{ .data = "_owner" }; + pub var @"REACT_ELEMENT_TYPE" = E.String{ .data = "react.element" }; }; pub const Data = struct { pub var BMissing = B{ .b_missing = BMissing_ }; @@ -3291,6 +3409,13 @@ pub const Prefill = struct { pub var Filename = Expr.Data{ .e_string = &Prefill.String.Filename }; pub var LineNumber = Expr.Data{ .e_string = &Prefill.String.LineNumber }; pub var ColumnNumber = Expr.Data{ .e_string = &Prefill.String.ColumnNumber }; + pub var @"$$typeof" = Expr.Data{ .e_string = &Prefill.String.@"$$typeof" }; + pub var @"key" = Expr.Data{ .e_string = &Prefill.String.@"Key" }; + pub var @"type" = Expr.Data{ .e_string = &Prefill.String.@"type" }; + pub var @"ref" = Expr.Data{ .e_string = &Prefill.String.@"ref" }; + pub var @"props" = Expr.Data{ .e_string = &Prefill.String.@"props" }; + pub var @"_owner" = Expr.Data{ .e_string = &Prefill.String.@"_owner" }; + pub var @"REACT_ELEMENT_TYPE" = Expr.Data{ .e_string = &Prefill.String.@"REACT_ELEMENT_TYPE" }; pub const This = Expr.Data{ .e_this = E.This{} }; pub const Zero = Expr.Data{ .e_number = Value.Zero }; }; @@ -3304,6 +3429,10 @@ pub const Prefill = struct { }; }; +const ReactJSX = struct { + hoisted_elements: std.ArrayHashMapUnmanaged(Ref, G.Decl, bun.ArrayIdentityContext, false) = .{}, +}; + var keyExprData = Expr.Data{ .e_string = &Prefill.String.Key }; var jsxChildrenKeyData = Expr.Data{ .e_string = &Prefill.String.Children }; var nullExprValueData = E.Null{}; @@ -3853,6 +3982,9 @@ fn NewParser_( // "visit" pass. enclosing_namespace_arg_ref: ?Ref = null, + react_element_type: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + /// Symbol object + es6_symbol_global: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, jsx_filename: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, jsx_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, jsx_factory: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, @@ -4876,6 +5008,11 @@ fn NewParser_( if (p.options.jsx.development) { p.jsx_filename = p.declareGeneratedSymbol(.other, "jsxFilename") catch unreachable; } + + if (p.options.features.jsx_optimization_inline) { + p.react_element_type = p.declareGeneratedSymbol(.other, "REACT_ELEMENT_TYPE") catch unreachable; + p.es6_symbol_global = p.declareGeneratedSymbol(.unbound, "Symbol") catch unreachable; + } p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; p.jsx_runtime = p.declareGeneratedSymbol(.other, "jsx") catch unreachable; p.jsxs_runtime = p.declareGeneratedSymbol(.other, "jsxs") catch unreachable; @@ -4962,6 +5099,10 @@ fn NewParser_( } pub fn resolveStaticJSXSymbols(p: *P) void { + if (p.options.features.jsx_optimization_inline) { + p.resolveGeneratedSymbol(&p.react_element_type); + p.resolveGeneratedSymbol(&p.es6_symbol_global); + } p.resolveGeneratedSymbol(&p.jsx_runtime); p.resolveGeneratedSymbol(&p.jsxs_runtime); p.resolveGeneratedSymbol(&p.jsx_factory); @@ -11969,11 +12110,14 @@ fn NewParser_( var key_prop: ?ExprNodeIndex = null; var flags = Flags.JSXElement.Bitset{}; var start_tag: ?ExprNodeIndex = null; + 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.features.jsx_optimization_inline; + var spread_loc: logger.Loc = logger.Loc.Empty; var props = ListManaged(G.Property).init(p.allocator); var key_prop_i: i32 = -1; @@ -11990,7 +12134,6 @@ fn NewParser_( try p.lexer.nextInsideJSXElement(); if (special_prop == .key) { - // <ListItem key> if (p.lexer.token != .t_equals) { // Unlike Babel, we're going to just warn here and move on. @@ -12003,6 +12146,8 @@ fn NewParser_( continue; } + can_be_inlined = can_be_inlined and special_prop != .ref; + const prop_name = p.e(E.String{ .data = prop_name_literal }, key_range.loc); // Parse the value @@ -12034,6 +12179,7 @@ fn NewParser_( switch (p.lexer.token) { .t_dot_dot_dot => { try p.lexer.next(); + can_be_inlined = false; spread_prop_i = i; spread_loc = p.lexer.loc(); @@ -12146,6 +12292,10 @@ fn NewParser_( try p.lexer.expected(.t_greater_than); } + if (can_be_inlined) { + flags.insert(.can_be_inlined); + } + return p.e(E.JSXElement{ .tag = start_tag, .properties = properties, @@ -12263,6 +12413,10 @@ fn NewParser_( try p.lexer.expected(.t_greater_than); } + if (can_be_inlined) { + flags.insert(.can_be_inlined); + } + return p.e(E.JSXElement{ .tag = end_tag.data.asExpr(), .children = ExprNodeList.fromList(children), @@ -13377,14 +13531,21 @@ fn NewParser_( const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.contains(.is_key_before_rest)) options.JSX.Runtime.automatic else options.JSX.Runtime.classic; var children_count = e_.children.len; - const is_childless_tag = FeatureFlags.react_specific_warnings and children_count > 0 and tag.data == .e_string and tag.data.e_string.isUTF8() and js_lexer.ChildlessJSXTags.has(tag.data.e_string.slice(p.allocator)); + const is_childless_tag = FeatureFlags.react_specific_warnings and children_count > 0 and + tag.data == .e_string and tag.data.e_string.isUTF8() and js_lexer.ChildlessJSXTags.has(tag.data.e_string.slice(p.allocator)); children_count = if (is_childless_tag) 0 else children_count; if (children_count != e_.children.len) { // Error: meta is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. // ^ from react-dom - p.log.addWarningFmt(p.source, tag.loc, p.allocator, "<{s} /> is a void element and must not have \"children\"", .{tag.data.e_string.slice(p.allocator)}) catch {}; + p.log.addWarningFmt( + p.source, + tag.loc, + p.allocator, + "<{s} /> is a void element and must not have \"children\"", + .{tag.data.e_string.slice(p.allocator)}, + ) catch {}; } // TODO: maybe we should split these into two different AST Nodes @@ -13430,12 +13591,7 @@ fn NewParser_( }, // function jsxDEV(type, config, maybeKey, source, self) { .automatic => { - // Either: - // jsxDEV(type, arguments, key, isStaticChildren, source, self) - // jsx(type, arguments, key) - const include_filename = FeatureFlags.include_filename_in_jsx and p.options.jsx.development; - const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable; - args[0] = tag; + // --- These must be done in all cases -- const allocator = p.allocator; var props = e_.properties.list(); // arguments needs to be like @@ -13457,9 +13613,6 @@ fn NewParser_( const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; - // Babel defines static jsx as children.len > 1 - const is_static_jsx = e_.children.len > 1; - // Optimization: if the only non-child prop is a spread object // we can just pass the object as the first argument // this goes as deep as there are spreads @@ -13471,6 +13624,9 @@ fn NewParser_( props = props.items[0].value.?.data.e_object.properties.list(); } + // Babel defines static jsx as children.len > 1 + const is_static_jsx = e_.children.len > 1; + // if (p.options.jsx.development) { switch (e_.children.len) { 0 => {}, @@ -13490,84 +13646,239 @@ fn NewParser_( }) catch unreachable; }, } + // --- These must be done in all cases -- + + // Trivial elements can be inlined, removing the call to createElement or jsx() + if (p.options.features.jsx_optimization_inline and e_.flags.contains(.can_be_inlined)) { + // The output object should look like this: + // https://babeljs.io/repl/#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=false&spec=false&loose=false&code_lz=FAMwrgdgxgLglgewgAgLIE8DCCC2AHJAUwhgAoBvAIwEMAvAXwEplzhl3kAnQmMTlADwAxBAmQA-AIwAmAMxsOAFgCsANgEB6EQnEBuYPWBA&debug=false&forceAllTransforms=false&shippedProposals=true&circleciRepo=&evaluate=false&fileSize=true&timeTravel=false&sourceType=module&lineWrap=true&presets=react%2Ctypescript&prettier=true&targets=&version=7.18.4&externalPlugins=%40babel%2Fplugin-transform-flow-strip-types%407.16.7%2C%40babel%2Fplugin-transform-react-inline-elements%407.16.7&assumptions=%7B%22arrayLikeIsIterable%22%3Atrue%2C%22constantReexports%22%3Atrue%2C%22constantSuper%22%3Atrue%2C%22enumerableModuleMeta%22%3Atrue%2C%22ignoreFunctionLength%22%3Atrue%2C%22ignoreToPrimitiveHint%22%3Atrue%2C%22mutableTemplateObject%22%3Atrue%2C%22iterableIsArray%22%3Atrue%2C%22noClassCalls%22%3Atrue%2C%22noNewArrows%22%3Atrue%2C%22noDocumentAll%22%3Atrue%2C%22objectRestNoSymbols%22%3Atrue%2C%22privateFieldsAsProperties%22%3Atrue%2C%22pureGetters%22%3Atrue%2C%22setComputedProperties%22%3Atrue%2C%22setClassMethods%22%3Atrue%2C%22setSpreadProperties%22%3Atrue%2C%22setPublicClassFields%22%3Atrue%2C%22skipForOfIteratorClosing%22%3Atrue%2C%22superIsCallableConstructor%22%3Atrue%7D + // return { + // $$typeof: REACT_ELEMENT_TYPE, + // type: type, + // key: void 0 === key ? null : "" + key, + // ref: null, + // props: props, + // _owner: null + // }; + // + p.recordUsage(p.react_element_type.ref); + const key = if (e_.key) |key_| brk: { + // key: void 0 === key ? null : "" + key, + break :brk switch (key_.data) { + .e_string => break :brk key_, + .e_undefined, .e_null => p.e(E.Null{}, key_.loc), + else => p.e(E.If{ + .test_ = p.e(E.Binary{ + .left = p.e(E.Undefined{}, key_.loc), + .op = Op.Code.bin_strict_eq, + .right = key_, + }, key_.loc), + .yes = p.e(E.Null{}, key_.loc), + .no = p.e( + E.Binary{ + .op = Op.Code.bin_add, + .left = p.e(&E.String.empty, key_.loc), + .right = key_, + }, + key_.loc, + ), + }, key_.loc), + }; + } else p.e(E.Null{}, expr.loc); + var jsx_element = p.allocator.alloc(G.Property, 6) catch unreachable; + const props_object = p.e( + E.Object{ + .properties = G.Property.List.fromList(props), + .close_brace_loc = e_.close_tag_loc, + }, + expr.loc, + ); + var props_expression = props_object; + + // we must check for default props + if (tag.data != .e_string) { + // We assume defaultProps is supposed to _not_ have side effects + // We do not support "key" or "ref" in defaultProps. + const defaultProps = p.e(E.Dot{ + .name = "defaultProps", + .name_loc = tag.loc, + .target = tag, + .can_be_removed_if_unused = true, + }, tag.loc); + // props: MyComponent.defaultProps || {} + if (props.items.len == 0) { + props_expression = p.e(E.Binary{ .op = Op.Code.bin_logical_or, .left = defaultProps, .right = props_object }, defaultProps.loc); + } else { + // we assume that most components don't use defaultProps + // props: !MyComponent.defaultProps ? {myProp: 123} : { ...MyComponent.defaultProps, myProp: 123} + var with_default_props = p.allocator.alloc(G.Property, props_object.data.e_object.properties.len + 1) catch unreachable; + with_default_props[0] = G.Property{ + .key = null, + .value = defaultProps, + .kind = Property.Kind.spread, + .flags = Flags.Property.init( + .{ + .is_spread = true, + }, + ), + }; - args[1] = p.e(E.Object{ - .properties = G.Property.List.fromList(props), - }, expr.loc); + std.mem.copy(G.Property, with_default_props[1..], props_object.data.e_object.properties.slice()); + props_expression = p.e( + E.If{ + .test_ = p.e(E.Unary{ .op = .un_not, .value = defaultProps }, defaultProps.loc), + .no = p.e( + E.Object{ .properties = G.Property.List.init(with_default_props), .close_brace_loc = e_.close_tag_loc }, + tag.loc, + ), + .yes = props_object, + }, + props_object.loc, + ); + } + } - if (e_.key) |key| { - args[2] = key; - } else { - // if (maybeKey !== undefined) - args[2] = Expr{ - .loc = expr.loc, - .data = .{ - .e_undefined = E.Undefined{}, + jsx_element[0..6].* = + [_]G.Property{ + G.Property{ + .key = Expr{ .data = Prefill.Data.@"$$typeof", .loc = tag.loc }, + .value = p.e( + E.Identifier{ + .ref = p.react_element_type.ref, + .can_be_removed_if_unused = true, + }, + tag.loc, + ), + }, + G.Property{ + .key = Expr{ .data = Prefill.Data.@"type", .loc = tag.loc }, + .value = tag, + }, + G.Property{ + .key = Expr{ .data = Prefill.Data.@"key", .loc = key.loc }, + .value = key, + }, + // this is a de-opt + // any usage of ref should make it impossible for this code to be reached + G.Property{ + .key = Expr{ .data = Prefill.Data.@"ref", .loc = expr.loc }, + .value = p.e(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.loc }, + .value = p.e( + E.Null{}, + expr.loc, + ), }, }; - } - if (p.options.jsx.development) { - // is the return type of the first child an array? - // It's dynamic - // Else, it's static - args[3] = Expr{ - .loc = expr.loc, - .data = .{ - .e_boolean = .{ - .value = is_static_jsx, - }, + const output = p.e( + E.Object{ + .properties = G.Property.List.init(jsx_element), + .close_brace_loc = e_.close_tag_loc, }, - }; + expr.loc, + ); - if (include_filename) { - var source = p.allocator.alloc(G.Property, 2) catch unreachable; - p.recordUsage(p.jsx_filename.ref); - source[0] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, - .value = p.e(E.Identifier{ - .ref = p.jsx_filename.ref, - .can_be_removed_if_unused = true, - }, expr.loc), + if (p.options.features.jsx_optimization_hoist) { + // if the expression is free of side effects + if (p.exprCanBeRemovedIfUnused(&props_object)) {} + } + + return output; + } else { + // -- The typical jsx automatic transform happens here -- + + // Either: + // jsxDEV(type, arguments, key, isStaticChildren, source, self) + // jsx(type, arguments, key) + const include_filename = FeatureFlags.include_filename_in_jsx and p.options.jsx.development; + const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 2) + @as(usize, @boolToInt(e_.key != null))) catch unreachable; + args[0] = tag; + + args[1] = p.e(E.Object{ + .properties = G.Property.List.fromList(props), + }, expr.loc); + + if (e_.key) |key| { + args[2] = key; + } else if (p.options.jsx.development) { + // if (maybeKey !== undefined) + args[2] = Expr{ + .loc = expr.loc, + .data = .{ + .e_undefined = E.Undefined{}, + }, }; + } - source[1] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, - .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + if (p.options.jsx.development) { + // is the return type of the first child an array? + // It's dynamic + // Else, it's static + args[3] = Expr{ + .loc = expr.loc, + .data = .{ + .e_boolean = .{ + .value = is_static_jsx, + }, + }, }; - // Officially, they ask for columnNumber. But I don't see any usages of it in the code! - // source[2] = G.Property{ - // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, - // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), - // }; - args[4] = p.e(E.Object{ - .properties = G.Property.List.init(source), - }, expr.loc); + if (include_filename) { + var source = p.allocator.alloc(G.Property, 2) catch unreachable; + p.recordUsage(p.jsx_filename.ref); + source[0] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, + .value = p.e(E.Identifier{ + .ref = p.jsx_filename.ref, + .can_be_removed_if_unused = true, + }, expr.loc), + }; - // When disabled, this must specifically be undefined - // Not an empty object - // See this code from react: - // > if (source !== undefined) { - // > var fileName = source.fileName.replace(/^.*[\\\/]/, ""); - // > var lineNumber = source.lineNumber; - // > return "\n\nCheck your code at " + fileName + ":" + lineNumber + "."; - // > } - } else { - args[4] = p.e(E.Undefined{}, expr.loc); + source[1] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, + .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + }; + + // Officially, they ask for columnNumber. But I don't see any usages of it in the code! + // source[2] = G.Property{ + // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, + // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + // }; + args[4] = p.e(E.Object{ + .properties = G.Property.List.init(source), + }, expr.loc); + + // When disabled, this must specifically be undefined + // Not an empty object + // See this code from react: + // > if (source !== undefined) { + // > var fileName = source.fileName.replace(/^.*[\\\/]/, ""); + // > var lineNumber = source.lineNumber; + // > return "\n\nCheck your code at " + fileName + ":" + lineNumber + "."; + // > } + } else { + args[4] = p.e(E.Undefined{}, expr.loc); + } + + args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; } - args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; + return p.e(E.Call{ + .target = p.jsxStringsToMemberExpressionAutomatic(expr.loc, is_static_jsx), + .args = ExprNodeList.init(args), + // Enable tree shaking + .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, + .was_jsx_element = true, + .close_paren_loc = e_.close_tag_loc, + }, expr.loc); } - - return p.e(E.Call{ - .target = p.jsxStringsToMemberExpressionAutomatic(expr.loc, is_static_jsx), - .args = ExprNodeList.init(args), - // Enable tree shaking - .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, - .was_jsx_element = true, - .close_paren_loc = e_.close_tag_loc, - }, expr.loc); }, else => unreachable, } |