aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bundler.zig13
-rw-r--r--src/defines.zig4
-rw-r--r--src/feature_flags.zig2
-rw-r--r--src/javascript/jsc/base.zig2
-rw-r--r--src/javascript/jsc/javascript.zig116
-rw-r--r--src/js_parser/js_parser.zig554
-rw-r--r--src/linker.zig6
-rw-r--r--src/options.zig21
-rw-r--r--src/runtime.zig1
9 files changed, 500 insertions, 219 deletions
diff --git a/src/bundler.zig b/src/bundler.zig
index ef38f7d96..d36795d52 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -123,7 +123,7 @@ pub const Bundler = struct {
// must be pointer array because we can't we don't want the source to point to invalid memory if the array size is reallocated
virtual_modules: std.ArrayList(*ClientEntryPoint),
- macro_context: ?*js_ast.Macro.MacroContext = null,
+ macro_context: ?js_ast.Macro.MacroContext = null,
pub const isCacheEnabled = cache_files;
@@ -826,7 +826,7 @@ pub const Bundler = struct {
} else {}
for (bundler.options.entry_points) |entry_point| {
- if (bundler.options.platform == .bun) continue;
+ if (bundler.options.platform.isBun()) continue;
defer this.bundler.resetStore();
const entry_point_path = bundler.normalizeEntryPointPath(entry_point);
@@ -846,7 +846,7 @@ pub const Bundler = struct {
bundler.options.framework.?.override_modules_hashes[i] = std.hash.Wyhash.hash(0, key);
}
}
- if (bundler.options.platform == .bun) {
+ if (bundler.options.platform.isBun()) {
if (framework.server.isEnabled()) {
const resolved = try bundler.linker.resolver.resolve(
bundler.fs.top_level_dir,
@@ -1047,7 +1047,7 @@ pub const Bundler = struct {
const basename = std.fs.path.basename(std.mem.span(destination));
const extname = std.fs.path.extension(basename);
- javascript_bundle.import_from_name = if (bundler.options.platform == .bun)
+ javascript_bundle.import_from_name = if (bundler.options.platform.isBun())
"/node_modules.server.bun"
else
try std.fmt.allocPrint(
@@ -2406,20 +2406,21 @@ pub const Bundler = struct {
// or you're running in SSR
// or the file is a node_module
opts.features.hot_module_reloading = bundler.options.hot_module_reloading and
- bundler.options.platform != .bun and
+ bundler.options.platform.isNotBun() and
(!opts.can_import_from_bundle or
(opts.can_import_from_bundle and !path.isNodeModule()));
opts.features.react_fast_refresh = opts.features.hot_module_reloading and
jsx.parse and
bundler.options.jsx.supports_fast_refresh;
opts.filepath_hash_for_hmr = file_hash orelse 0;
- opts.warn_about_unbundled_modules = bundler.options.platform != .bun;
+ opts.warn_about_unbundled_modules = bundler.options.platform.isNotBun();
if (bundler.macro_context == null) {
bundler.macro_context = js_ast.Macro.MacroContext.init(bundler);
}
opts.macro_context = &bundler.macro_context.?;
+ opts.features.is_macro_runtime = bundler.options.platform == .bun_macro;
const value = (bundler.resolver.caches.js.parse(
allocator,
diff --git a/src/defines.zig b/src/defines.zig
index 3d08c936e..bb5ccae3b 100644
--- a/src/defines.zig
+++ b/src/defines.zig
@@ -268,10 +268,10 @@ pub const Define = struct {
// Step 2. Swap in certain literal values because those can be constant folded
define.identifiers.putAssumeCapacity("undefined", value_define);
define.identifiers.putAssumeCapacity("NaN", DefineData{
- .value = js_ast.Expr.Data{ .e_number = &nan_val },
+ .value = js_ast.Expr.Data{ .e_number = nan_val },
});
define.identifiers.putAssumeCapacity("Infinity", DefineData{
- .value = js_ast.Expr.Data{ .e_number = &inf_val },
+ .value = js_ast.Expr.Data{ .e_number = inf_val },
});
// Step 3. Load user data into hash tables
diff --git a/src/feature_flags.zig b/src/feature_flags.zig
index a79e1b9b6..9aa809879 100644
--- a/src/feature_flags.zig
+++ b/src/feature_flags.zig
@@ -67,3 +67,5 @@ pub const CSSInJSImportBehavior = enum {
// having issues compiling WebKit with this enabled
pub const remote_inspector = false;
pub const auto_import_buffer = false;
+
+pub const is_macro_enabled = env.isDebug;
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index 65300f07c..36f00f59e 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -1539,6 +1539,7 @@ export fn MarkedArrayBuffer_deallocator(bytes_: *c_void, ctx_: *c_void) void {
pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type {
return JSPrivateDataPtr.from(js.JSObjectGetPrivate(obj)).as(Type);
}
+const JSExpr = @import("../../js_ast.zig").Macro.JSExpr;
pub const JSPrivateDataPtr = TaggedPointerUnion(.{
ResolveError,
@@ -1549,6 +1550,7 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
Headers,
Body,
Router,
+ JSExpr,
});
pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type {
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 352b9caac..8d7626afb 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -4,6 +4,7 @@ const Fs = @import("../../fs.zig");
const Resolver = @import("../../resolver/resolver.zig");
const ast = @import("../../import_record.zig");
const NodeModuleBundle = @import("../../node_module_bundle.zig").NodeModuleBundle;
+const MacroEntryPoint = @import("../../bundler.zig").MacroEntryPoint;
const logger = @import("../../logger.zig");
const Api = @import("../../api/schema.zig").Api;
const options = @import("../../options.zig");
@@ -11,6 +12,7 @@ const Bundler = @import("../../bundler.zig").Bundler;
const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
const js_printer = @import("../../js_printer.zig");
const js_parser = @import("../../js_parser.zig");
+const js_ast = @import("../../js_ast.zig");
const hash_map = @import("../../hash_map.zig");
const http = @import("../../http.zig");
const ImportKind = ast.ImportKind;
@@ -87,6 +89,41 @@ pub const Bun = struct {
return css_imports_list_strings[0..tail];
}
+ pub fn registerMacro(
+ this: void,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (arguments.len != 2 or !js.JSValueIsNumber(ctx, arguments[0])) {
+ JSError(getAllocator(ctx), "Internal error registering macros: invalid args", .{}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+ // TODO: make this faster
+ const id = @truncate(i32, @floatToInt(i64, js.JSValueToNumber(ctx, arguments[0], exception)));
+ if (id == -1 or id == 0) {
+ JSError(getAllocator(ctx), "Internal error registering macros: invalid id", .{}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ if (!js.JSValueIsObject(ctx, arguments[1]) or !js.JSObjectIsFunction(ctx, arguments[1])) {
+ JSError(getAllocator(ctx), "Macro must be a function. Received: {s}", .{@tagName(js.JSValueGetType(ctx, arguments[1]))}, ctx, exception);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ var get_or_put_result = VirtualMachine.vm.macros.getOrPut(id) catch unreachable;
+ if (get_or_put_result.found_existing) {
+ js.JSValueUnprotect(ctx, get_or_put_result.value_ptr.*);
+ }
+
+ js.JSValueProtect(ctx, arguments[1]);
+ get_or_put_result.value_ptr.* = arguments[1];
+
+ return js.JSValueMakeUndefined(ctx);
+ }
+
pub fn getCWD(
this: void,
ctx: js.JSContextRef,
@@ -403,6 +440,13 @@ pub const Bun = struct {
.@"return" = "string",
},
},
+ .registerMacro = .{
+ .rfn = Bun.registerMacro,
+ .ts = d.ts{
+ .name = "registerMacro",
+ .@"return" = "undefined",
+ },
+ },
},
.{
.main = .{
@@ -462,8 +506,25 @@ pub const VirtualMachine = struct {
transpiled_count: usize = 0,
resolved_count: usize = 0,
had_errors: bool = false,
- pub var vm_loaded = false;
- pub var vm: *VirtualMachine = undefined;
+
+ macros: MacroMap,
+ macro_entry_points: std.AutoArrayHashMap(i32, *MacroEntryPoint),
+ macro_mode: bool = false,
+
+ pub const MacroMap = std.AutoArrayHashMap(i32, js.JSObjectRef);
+
+ pub threadlocal var vm_loaded = false;
+ pub threadlocal var vm: *VirtualMachine = undefined;
+
+ pub fn enableMacroMode(this: *VirtualMachine) void {
+ this.bundler.options.platform = .bun_macro;
+ this.macro_mode = true;
+ }
+
+ pub fn disableMacroMode(this: *VirtualMachine) void {
+ this.bundler.options.platform = .bun;
+ this.macro_mode = false;
+ }
pub fn init(
allocator: *std.mem.Allocator,
@@ -502,6 +563,9 @@ pub const VirtualMachine = struct {
.log = log,
.flush_list = std.ArrayList(string).init(allocator),
.blobs = try Blob.Group.init(allocator),
+
+ .macros = MacroMap.init(allocator),
+ .macro_entry_points = @TypeOf(VirtualMachine.vm.macro_entry_points).init(allocator),
};
VirtualMachine.vm.bundler.configureLinker();
@@ -771,10 +835,16 @@ pub const VirtualMachine = struct {
ret.result = null;
ret.path = vm.entry_point.source.path.text;
return;
+ } else if (specifier.len > js_ast.Macro.namespaceWithColon.len and strings.eqlComptimeIgnoreLen(specifier[0..js_ast.Macro.namespaceWithColon.len], js_ast.Macro.namespaceWithColon)) {
+ ret.result = null;
+ ret.path = specifier;
+ return;
}
+ const is_special_source = strings.eqlComptime(source, main_file_name) or js_ast.Macro.isMacroPath(source);
+
const result = try vm.bundler.resolver.resolve(
- if (!strings.eqlComptime(source, main_file_name)) Fs.PathName.init(source).dirWithTrailingSlash() else VirtualMachine.vm.bundler.fs.top_level_dir,
+ if (!is_special_source) Fs.PathName.init(source).dirWithTrailingSlash() else VirtualMachine.vm.bundler.fs.top_level_dir,
specifier,
.stmt,
);
@@ -1050,6 +1120,46 @@ pub const VirtualMachine = struct {
return promise;
}
+ pub fn loadMacroEntryPoint(this: *VirtualMachine, entry_path: string, function_name: string, specifier: string, hash: i32) !*JSInternalPromise {
+ var entry_point_entry = try this.macro_entry_points.getOrPut(hash);
+
+ if (!entry_point_entry.found_existing) {
+ var macro_entry_pointer: *MacroEntryPoint = this.allocator.create(MacroEntryPoint) catch unreachable;
+ entry_point_entry.value_ptr.* = macro_entry_pointer;
+ try macro_entry_pointer.generate(&this.bundler, Fs.PathName.init(entry_path), function_name, hash, specifier);
+ }
+ var entry_point = entry_point_entry.value_ptr.*;
+
+ var promise: *JSInternalPromise = undefined;
+ // We first import the node_modules bundle. This prevents any potential TDZ issues.
+ // The contents of the node_modules bundle are lazy, so hopefully this should be pretty quick.
+ if (this.node_modules != null) {
+ promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(bun_file_import_path)));
+
+ this.global.vm().drainMicrotasks();
+
+ while (promise.status(this.global.vm()) == JSPromise.Status.Pending) {
+ this.global.vm().drainMicrotasks();
+ }
+
+ if (promise.status(this.global.vm()) == JSPromise.Status.Rejected) {
+ return promise;
+ }
+
+ _ = promise.result(this.global.vm());
+ }
+
+ promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(entry_point.source.path.text));
+
+ this.global.vm().drainMicrotasks();
+
+ while (promise.status(this.global.vm()) == JSPromise.Status.Pending) {
+ this.global.vm().drainMicrotasks();
+ }
+
+ return promise;
+ }
+
// When the Error-like object is one of our own, it's best to rely on the object directly instead of serializing it to a ZigException.
// This is for:
// - BuildError
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 1de8c4656..dcccea8a6 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -20,6 +20,7 @@ const ScopeOrderList = std.ArrayListUnmanaged(?ScopeOrder);
const JSXFactoryName = "JSX";
const JSXAutomaticName = "jsx_module";
+const MacroRefs = std.AutoArrayHashMap(Ref, u32);
pub fn ExpressionTransposer(
comptime Kontext: type,
@@ -1773,6 +1774,8 @@ pub const Parser = struct {
filepath_hash_for_hmr: u32 = 0,
features: RuntimeFeatures = RuntimeFeatures{},
+ macro_context: *js_ast.Macro.MacroContext = undefined,
+
warn_about_unbundled_modules: bool = true,
// Used when bundling node_modules
@@ -1881,6 +1884,9 @@ pub const Parser = struct {
}
pub fn parse(self: *Parser) !js_ast.Result {
+ if (self.options.ts and self.options.features.is_macro_runtime) return try self._parse(TSParserMacro);
+ if (!self.options.ts and self.options.features.is_macro_runtime) return try self._parse(JSParserMacro);
+
if (self.options.ts and self.options.jsx.parse) {
if (self.options.features.react_fast_refresh) {
return try self._parse(TSXParserFastRefresh);
@@ -2556,8 +2562,8 @@ pub const Prefill = struct {
pub var ColumnNumber = [_]u8{ 'c', 'o', 'l', 'u', 'm', 'n', 'N', 'u', 'm', 'b', 'e', 'r' };
};
pub const Value = struct {
- pub var EThis = E.This{};
- pub var Zero = E.Number{ .value = 0.0 };
+ pub const EThis = E.This{};
+ pub const Zero = E.Number{ .value = 0.0 };
};
pub const String = struct {
pub var Key = E.String{ .utf8 = &Prefill.StringLiteral.Key };
@@ -2579,8 +2585,8 @@ 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 This = Expr.Data{ .e_this = E.This{} };
- pub var Zero = Expr.Data{ .e_number = &Value.Zero };
+ pub const This = Expr.Data{ .e_this = E.This{} };
+ pub const Zero = Expr.Data{ .e_number = Value.Zero };
};
pub const Runtime = struct {
pub var JSXFilename = "__jsxFilename";
@@ -2603,9 +2609,15 @@ pub const ImportOrRequireScanResults = struct {
import_records: List(ImportRecord),
};
+const JSXTransformType = enum {
+ none,
+ react,
+ macro,
+};
+
const ParserFeatures = struct {
typescript: bool = false,
- jsx: bool = false,
+ jsx: JSXTransformType = JSXTransformType.none,
scan_only: bool = false,
// *** How React Fast Refresh works ***
@@ -2667,7 +2679,7 @@ pub fn NewParser(
comptime js_parser_features: ParserFeatures,
) type {
const is_typescript_enabled = js_parser_features.typescript;
- const is_jsx_enabled = js_parser_features.jsx;
+ const is_jsx_enabled = js_parser_features.jsx != .none;
const only_scan_imports_and_do_not_visit = js_parser_features.scan_only;
const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh;
@@ -2679,6 +2691,8 @@ pub fn NewParser(
// public only because of Binding.ToExpr
return struct {
const P = @This();
+ pub const jsx_transform_type: JSXTransformType = js_parser_features.jsx;
+ macro_refs: MacroRefs = undefined,
allocator: *std.mem.Allocator,
options: Parser.Options,
log: *logger.Log,
@@ -3579,22 +3593,28 @@ pub fn NewParser(
p.recordUsage(p.runtime_imports.__HMRClient.?.ref);
}
- if (is_jsx_enabled) {
- if (p.options.jsx.development) {
- p.jsx_filename = p.declareGeneratedSymbol(.other, "jsxFilename") 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;
- p.jsx_factory = p.declareGeneratedSymbol(.other, "Factory") catch unreachable;
+ switch (comptime jsx_transform_type) {
+ .react => {
+ if (p.options.jsx.development) {
+ p.jsx_filename = p.declareGeneratedSymbol(.other, "jsxFilename") 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;
+ p.jsx_factory = p.declareGeneratedSymbol(.other, "Factory") catch unreachable;
- if (p.options.jsx.factory.len > 1 or FeatureFlags.jsx_runtime_is_cjs) {
- p.jsx_classic = p.declareGeneratedSymbol(.other, "ClassicImportSource") catch unreachable;
- }
+ if (p.options.jsx.factory.len > 1 or FeatureFlags.jsx_runtime_is_cjs) {
+ p.jsx_classic = p.declareGeneratedSymbol(.other, "ClassicImportSource") catch unreachable;
+ }
- if (p.options.jsx.import_source.len > 0) {
- p.jsx_automatic = p.declareGeneratedSymbol(.other, "ImportSource") catch unreachable;
- }
+ if (p.options.jsx.import_source.len > 0) {
+ p.jsx_automatic = p.declareGeneratedSymbol(.other, "ImportSource") catch unreachable;
+ }
+ },
+ .macro => {
+ p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable;
+ },
+ else => {},
}
}
@@ -6109,6 +6129,15 @@ pub fn NewParser(
p.import_records.items[stmt.import_record_index].was_originally_bare_import = was_originally_bare_import;
try p.lexer.expectOrInsertSemicolon();
+ const is_macro = FeatureFlags.is_macro_enabled and js_ast.Macro.isMacroPath(path.text);
+
+ if (is_macro) {
+ p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace;
+ if (comptime only_scan_imports_and_do_not_visit) {
+ p.import_records.items[stmt.import_record_index].path.is_disabled = true;
+ }
+ }
+
if (stmt.star_name_loc) |star| {
const name = p.loadNameFromRef(stmt.namespace_ref);
stmt.namespace_ref = try p.declareSymbol(.import, star, name);
@@ -6118,6 +6147,17 @@ pub fn NewParser(
.import_record_index = stmt.import_record_index,
}) catch unreachable;
}
+
+ if (is_macro) {
+ p.log.addErrorFmt(
+ p.source,
+ star,
+ p.allocator,
+ "Macro cannot be a * import, must be default or an {{item}}",
+ .{},
+ ) catch unreachable;
+ return error.SyntaxError;
+ }
} else {
var path_name = fs.PathName.init(strings.append(p.allocator, "import_", path.text) catch unreachable);
const name = try path_name.nonUniqueNameString(p.allocator);
@@ -6140,6 +6180,10 @@ pub fn NewParser(
.import_record_index = stmt.import_record_index,
}) catch unreachable;
}
+
+ if (is_macro) {
+ try p.macro_refs.put(ref, stmt.import_record_index);
+ }
}
if (stmt.items.len > 0) {
@@ -6158,6 +6202,10 @@ pub fn NewParser(
.import_record_index = stmt.import_record_index,
}) catch unreachable;
}
+
+ if (is_macro) {
+ try p.macro_refs.put(ref, stmt.import_record_index);
+ }
}
}
@@ -10860,216 +10908,289 @@ pub fn NewParser(
p.panic("Unexpected private identifier. This is an internal error - not your fault.", .{});
},
.e_jsx_element => |e_| {
- const tag: Expr = tagger: {
- if (e_.tag) |_tag| {
- break :tagger p.visitExpr(_tag);
- } else {
- break :tagger p.jsxStringsToMemberExpression(expr.loc, p.jsx_fragment.ref);
- }
- };
-
- for (e_.properties) |property, i| {
- if (property.kind != .spread) {
- e_.properties[i].key = p.visitExpr(e_.properties[i].key.?);
- }
-
- if (property.value != null) {
- e_.properties[i].value = p.visitExpr(e_.properties[i].value.?);
- }
+ switch (comptime jsx_transform_type) {
+ .macro => {
+ const IdentifierOrNodeType = union(Tag) {
+ identifier: Expr,
+ expression: Expr.Tag,
+ pub const Tag = enum { identifier, expression };
+ };
+ const tag: IdentifierOrNodeType = tagger: {
+ if (e_.tag) |_tag| {
+ switch (_tag.data) {
+ .e_string => |str| {
+ if (Expr.Tag.find(str.utf8)) |tagname| {
+ break :tagger IdentifierOrNodeType{ .expression = tagname };
+ }
- if (property.initializer != null) {
- e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?);
- }
- }
+ p.log.addErrorFmt(
+ p.source,
+ expr.loc,
+ p.allocator,
+ "Invalid expression tag: \"<{s}>\". Valid tags are:\n" ++ Expr.Tag.valid_names_list ++ "\n",
+ .{str.utf8},
+ ) catch unreachable;
+ break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) };
+ },
+ else => {
+ break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) };
+ },
+ }
+ } else {
+ break :tagger IdentifierOrNodeType{ .expression = Expr.Tag.e_array };
+ }
+ };
- const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.is_key_before_rest) options.JSX.Runtime.automatic else options.JSX.Runtime.classic;
- var children_count = e_.children.len;
+ for (e_.properties) |property, i| {
+ if (property.kind != .spread) {
+ e_.properties[i].key = p.visitExpr(e_.properties[i].key.?);
+ }
- 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.utf8);
+ if (property.value != null) {
+ e_.properties[i].value = p.visitExpr(e_.properties[i].value.?);
+ }
- children_count = if (is_childless_tag) 0 else children_count;
+ if (property.initializer != null) {
+ e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?);
+ }
+ }
- 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.utf8}) catch {};
- }
+ return p.e(E.Missing{}, expr.loc);
+ },
+ .react => {
+ const tag: Expr = tagger: {
+ if (e_.tag) |_tag| {
+ break :tagger p.visitExpr(_tag);
+ } else {
+ break :tagger p.jsxStringsToMemberExpression(expr.loc, p.jsx_fragment.ref);
+ }
+ };
- // TODO: maybe we should split these into two different AST Nodes
- // That would reduce the amount of allocations a little
- switch (runtime) {
- .classic => {
- // Arguments to createElement()
- const args = p.allocator.alloc(Expr, 2 + children_count) catch unreachable;
- // There are at least two args:
- // - name of the tag
- // - props
- var i: usize = 1;
- args[0] = tag;
- if (e_.properties.len > 0) {
- for (e_.properties) |prop, prop_i| {
- if (prop.key) |key| {
- e_.properties[prop_i].key = p.visitExpr(key);
- }
+ for (e_.properties) |property, i| {
+ if (property.kind != .spread) {
+ e_.properties[i].key = p.visitExpr(e_.properties[i].key.?);
+ }
- if (prop.value) |val| {
- e_.properties[prop_i].value = p.visitExpr(val);
- }
+ if (property.value != null) {
+ e_.properties[i].value = p.visitExpr(e_.properties[i].value.?);
}
- if (e_.key) |key| {
- var props = p.allocator.alloc(G.Property, e_.properties.len + 1) catch unreachable;
- std.mem.copy(G.Property, props, e_.properties);
- props[props.len - 1] = G.Property{ .key = Expr{ .loc = key.loc, .data = keyExprData }, .value = key };
- args[1] = p.e(E.Object{ .properties = props }, expr.loc);
- } else {
- args[1] = p.e(E.Object{ .properties = e_.properties }, expr.loc);
+ if (property.initializer != null) {
+ e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?);
}
- i = 2;
- } else {
- args[1] = p.e(E.Null{}, expr.loc);
- i = 2;
}
- for (e_.children[0..children_count]) |child| {
- args[i] = p.visitExpr(child);
- i += @intCast(usize, @boolToInt(args[i].data != .e_missing));
+ const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.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.utf8);
+
+ 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.utf8}) catch {};
}
- // Call createElement()
- return p.e(E.Call{
- .target = p.jsxStringsToMemberExpression(expr.loc, p.jsx_factory.ref),
- .args = args[0..i],
- // Enable tree shaking
- .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations,
- }, expr.loc);
- },
- // function jsxDEV(type, config, maybeKey, source, self) {
- .automatic => {
- // Either:
- // jsxDEV(type, arguments, key, isStaticChildren, source, self)
- // jsx(type, arguments, key)
- const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable;
- args[0] = tag;
- var props = List(G.Property).fromOwnedSlice(p.allocator, e_.properties);
- // arguments needs to be like
- // {
- // ...props,
- // children: [el1, el2]
- // }
+ // TODO: maybe we should split these into two different AST Nodes
+ // That would reduce the amount of allocations a little
+ switch (runtime) {
+ .classic => {
+ // Arguments to createElement()
+ const args = p.allocator.alloc(Expr, 2 + children_count) catch unreachable;
+ // There are at least two args:
+ // - name of the tag
+ // - props
+ var i: usize = 1;
+ args[0] = tag;
+ if (e_.properties.len > 0) {
+ for (e_.properties) |prop, prop_i| {
+ if (prop.key) |key| {
+ e_.properties[prop_i].key = p.visitExpr(key);
+ }
+
+ if (prop.value) |val| {
+ e_.properties[prop_i].value = p.visitExpr(val);
+ }
+ }
- const is_static_jsx = e_.children.len == 0 or e_.children.len > 1 or e_.children[0].data != .e_array;
-
- // if (p.options.jsx.development) {
- switch (children_count) {
- 0 => {},
- 1 => {
- // static jsx must always be an array
- if (is_static_jsx) {
- const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc };
- e_.children[0] = p.visitExpr(e_.children[0]);
- props.append(G.Property{
- .key = children_key,
- .value = p.e(E.Array{
- .items = e_.children[0..children_count],
- .is_single_line = e_.children.len < 2,
- }, expr.loc),
- }) catch unreachable;
+ if (e_.key) |key| {
+ var props = p.allocator.alloc(G.Property, e_.properties.len + 1) catch unreachable;
+ std.mem.copy(G.Property, props, e_.properties);
+ props[props.len - 1] = G.Property{ .key = Expr{ .loc = key.loc, .data = keyExprData }, .value = key };
+ args[1] = p.e(E.Object{ .properties = props }, expr.loc);
+ } else {
+ args[1] = p.e(E.Object{ .properties = e_.properties }, expr.loc);
+ }
+ i = 2;
} else {
- const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc };
- props.append(G.Property{
- .key = children_key,
- .value = p.visitExpr(e_.children[0]),
- }) catch unreachable;
+ args[1] = p.e(E.Null{}, expr.loc);
+ i = 2;
}
- },
- else => {
- for (e_.children[0..children_count]) |child, i| {
- e_.children[i] = p.visitExpr(child);
+
+ for (e_.children[0..children_count]) |child| {
+ args[i] = p.visitExpr(child);
+ i += @intCast(usize, @boolToInt(args[i].data != .e_missing));
}
- const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc };
- props.append(G.Property{
- .key = children_key,
- .value = p.e(E.Array{
- .items = e_.children[0..children_count],
- .is_single_line = e_.children.len < 2,
- }, expr.loc),
- }) catch unreachable;
+
+ // Call createElement()
+ return p.e(E.Call{
+ .target = p.jsxStringsToMemberExpression(expr.loc, p.jsx_factory.ref),
+ .args = args[0..i],
+ // Enable tree shaking
+ .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations,
+ }, expr.loc);
},
- }
+ // function jsxDEV(type, config, maybeKey, source, self) {
+ .automatic => {
+ // Either:
+ // jsxDEV(type, arguments, key, isStaticChildren, source, self)
+ // jsx(type, arguments, key)
+ const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable;
+ args[0] = tag;
+ var props = List(G.Property).fromOwnedSlice(p.allocator, e_.properties);
+ // arguments needs to be like
+ // {
+ // ...props,
+ // children: [el1, el2]
+ // }
- args[1] = p.e(E.Object{
- .properties = props.toOwnedSlice(),
- }, expr.loc);
+ const is_static_jsx = e_.children.len == 0 or e_.children.len > 1 or e_.children[0].data != .e_array;
+
+ // if (p.options.jsx.development) {
+ switch (children_count) {
+ 0 => {},
+ 1 => {
+ // static jsx must always be an array
+ if (is_static_jsx) {
+ const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc };
+ e_.children[0] = p.visitExpr(e_.children[0]);
+ props.append(G.Property{
+ .key = children_key,
+ .value = p.e(E.Array{
+ .items = e_.children[0..children_count],
+ .is_single_line = e_.children.len < 2,
+ }, expr.loc),
+ }) catch unreachable;
+ } else {
+ const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc };
+ props.append(G.Property{
+ .key = children_key,
+ .value = p.visitExpr(e_.children[0]),
+ }) catch unreachable;
+ }
+ },
+ else => {
+ for (e_.children[0..children_count]) |child, i| {
+ e_.children[i] = p.visitExpr(child);
+ }
+ const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc };
+ props.append(G.Property{
+ .key = children_key,
+ .value = p.e(E.Array{
+ .items = e_.children[0..children_count],
+ .is_single_line = e_.children.len < 2,
+ }, expr.loc),
+ }) catch unreachable;
+ },
+ }
- if (e_.key) |key| {
- args[2] = key;
- } else {
- // if (maybeKey !== undefined)
- args[2] = Expr{
- .loc = expr.loc,
- .data = .{
- .e_undefined = E.Undefined{},
- },
- };
- }
+ args[1] = p.e(E.Object{
+ .properties = props.toOwnedSlice(),
+ }, 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,
- },
- },
- };
+ if (e_.key) |key| {
+ args[2] = key;
+ } else {
+ // if (maybeKey !== undefined)
+ args[2] = Expr{
+ .loc = expr.loc,
+ .data = .{
+ .e_undefined = E.Undefined{},
+ },
+ };
+ }
- 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 }, 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,
+ },
+ },
+ };
- 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),
- };
+ 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 }, 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 = source,
- }, 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 = args,
- // Enable tree shaking
- .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations,
- .was_jsx_element = true,
- }, 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 = source,
+ }, 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 = args,
+ // Enable tree shaking
+ .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations,
+ .was_jsx_element = true,
+ }, expr.loc);
+ },
+ else => unreachable,
+ }
},
else => unreachable,
}
},
.e_template => |e_| {
+ for (e_.parts) |*part| {
+ part.value = p.visitExpr(part.value);
+ }
+
if (e_.tag) |tag| {
e_.tag = p.visitExpr(tag);
- }
- for (e_.parts) |*part| {
- part.value = p.visitExpr(part.value);
+ if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) {
+ if (e_.tag.?.data == .e_import_identifier) {
+ const ref = e_.tag.?.data.e_import_identifier.ref;
+ if (p.macro_refs.get(ref)) |import_record_id| {
+ const name = p.symbols.items[ref.inner_index].original_name;
+ const record = &p.import_records.items[import_record_id];
+ return p.options.macro_context.call(
+ record.path.text,
+ p.source.path.sourceDir(),
+ p.log,
+ p.source,
+ record.range,
+ expr,
+ &.{},
+ name,
+ ) catch return expr;
+ }
+ }
+ }
}
},
@@ -11818,6 +11939,7 @@ pub fn NewParser(
e_.target = p.visitExprInOut(e_.target, ExprIn{
.has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == .ccontinue,
});
+
// TODO: wan about import namespace call
var has_spread = false;
for (e_.args) |*arg| {
@@ -11837,6 +11959,26 @@ pub fn NewParser(
}
}
+ if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) {
+ if (e_.target.data == .e_import_identifier) {
+ const ref = e_.target.data.e_import_identifier.ref;
+ if (p.macro_refs.get(ref)) |import_record_id| {
+ const name = p.symbols.items[ref.inner_index].original_name;
+ const record = &p.import_records.items[import_record_id];
+ return p.options.macro_context.call(
+ record.path.text,
+ p.source.path.sourceDir(),
+ p.log,
+ p.source,
+ record.range,
+ expr,
+ &.{},
+ name,
+ ) catch return expr;
+ }
+ }
+ }
+
return expr;
},
.e_new => |e_| {
@@ -15090,18 +15232,26 @@ pub fn NewParser(
// '../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable' ran
// 1.02 ± 0.07 times faster than '../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable'
const JavaScriptParser = NewParser(.{});
-const JSXParser = NewParser(.{ .jsx = true });
-const TSXParser = NewParser(.{ .jsx = true, .typescript = true });
+const JSXParser = NewParser(.{ .jsx = .react });
+const TSXParser = NewParser(.{ .jsx = .react, .typescript = true });
const TypeScriptParser = NewParser(.{ .typescript = true });
+const JSParserMacro = NewParser(.{
+ .jsx = .macro,
+});
+const TSParserMacro = NewParser(.{
+ .jsx = .react,
+ .typescript = true,
+});
+
const JavaScriptParserFastRefresh = NewParser(.{ .react_fast_refresh = true });
-const JSXParserFastRefresh = NewParser(.{ .jsx = true, .react_fast_refresh = true });
-const TSXParserFastRefresh = NewParser(.{ .jsx = true, .typescript = true, .react_fast_refresh = true });
+const JSXParserFastRefresh = NewParser(.{ .jsx = .react, .react_fast_refresh = true });
+const TSXParserFastRefresh = NewParser(.{ .jsx = .react, .typescript = true, .react_fast_refresh = true });
const TypeScriptParserFastRefresh = NewParser(.{ .typescript = true, .react_fast_refresh = true });
const JavaScriptImportScanner = NewParser(.{ .scan_only = true });
-const JSXImportScanner = NewParser(.{ .jsx = true, .scan_only = true });
-const TSXImportScanner = NewParser(.{ .jsx = true, .typescript = true, .scan_only = true });
+const JSXImportScanner = NewParser(.{ .jsx = .react, .scan_only = true });
+const TSXImportScanner = NewParser(.{ .jsx = .react, .typescript = true, .scan_only = true });
const TypeScriptImportScanner = NewParser(.{ .typescript = true, .scan_only = true });
// The "await" and "yield" expressions are never allowed in argument lists but
diff --git a/src/linker.zig b/src/linker.zig
index 4e42423b8..16d6d8342 100644
--- a/src/linker.zig
+++ b/src/linker.zig
@@ -166,7 +166,7 @@ pub const Linker = struct {
}
pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker) string {
- if (this.options.platform == .bun) return "/node_modules.server.bun";
+ if (this.options.platform.isBun()) return "/node_modules.server.bun";
return if (this.options.node_modules_bundle_url.len > 0)
this.options.node_modules_bundle_url
@@ -197,7 +197,7 @@ pub const Linker = struct {
comptime ignore_runtime: bool,
) !void {
var needs_runtime = result.ast.uses_exports_ref or result.ast.uses_module_ref or result.ast.runtime_imports.hasAny();
- const source_dir = if (file_path.is_symlink and file_path.pretty.len > 0 and import_path_format == .absolute_url and linker.options.platform != .bun)
+ const source_dir = if (file_path.is_symlink and file_path.pretty.len > 0 and import_path_format == .absolute_url and linker.options.platform.isNotBun())
Fs.PathName.init(file_path.pretty).dirWithTrailingSlash()
else
file_path.sourceDir();
@@ -644,7 +644,7 @@ pub const Linker = struct {
import_record.path = try linker.generateImportPath(
source_dir,
- if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform != .bun) path.pretty else path.text,
+ if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform.isNotBun()) path.pretty else path.text,
if (resolve_result.package_json) |package_json| package_json.version else "",
Bundler.isCacheEnabled and loader == .file,
path.namespace,
diff --git a/src/options.zig b/src/options.zig
index 78c302807..af574ca1d 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -338,18 +338,33 @@ pub const Platform = enum {
neutral,
browser,
bun,
+ bun_macro,
node,
+ pub inline fn isBun(this: Platform) bool {
+ return switch (this) {
+ .bun_macro, .bun => true,
+ else => false,
+ };
+ }
+
+ pub inline fn isNotBun(this: Platform) bool {
+ return switch (this) {
+ .bun_macro, .bun => false,
+ else => true,
+ };
+ }
+
pub inline fn isClient(this: Platform) bool {
return switch (this) {
- .bun => false,
+ .bun_macro, .bun => false,
else => true,
};
}
pub inline fn supportsBrowserField(this: Platform) bool {
return switch (this) {
- .neutral, .browser, .bun => true,
+ .bun_macro, .neutral, .browser, .bun => true,
else => false,
};
}
@@ -360,7 +375,7 @@ pub const Platform = enum {
pub inline fn processBrowserDefineValue(this: Platform) ?string {
return switch (this) {
.browser => browser_define_value_true,
- .bun, .node => browser_define_value_false,
+ .bun_macro, .bun, .node => browser_define_value_false,
else => null,
};
}
diff --git a/src/runtime.zig b/src/runtime.zig
index 77a0430d1..c8aed0dc7 100644
--- a/src/runtime.zig
+++ b/src/runtime.zig
@@ -205,6 +205,7 @@ pub const Runtime = struct {
hot_module_reloading: bool = false,
hot_module_reloading_entry: bool = false,
keep_names_for_arrow_functions: bool = true,
+ is_macro_runtime: bool = false,
};
pub const Names = struct {