aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-20 13:48:05 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-20 13:48:05 -0700
commit7e2539ed702f0667163b726a6bb12bbd5569979d (patch)
tree0daa2e883acb4e93a2df2bbb16eefb0dc7ce539f
parent60b5fb95b19b2f96dcfd851663b40e1155c9cc0e (diff)
downloadbun-jarred/ast.tar.gz
bun-jarred/ast.tar.zst
bun-jarred/ast.zip
Diffstat (limited to '')
-rw-r--r--examples/macros/dogeify.tsx3
-rw-r--r--examples/macros/hello.js4
-rw-r--r--examples/macros/package.json6
-rw-r--r--examples/macros/tsconfig.json5
-rw-r--r--src/bundler.zig79
-rw-r--r--src/defines.zig4
-rw-r--r--src/javascript/jsc/bindings/bindings.cpp13
-rw-r--r--src/javascript/jsc/bindings/bindings.zig6
-rw-r--r--src/javascript/jsc/javascript.zig130
-rw-r--r--src/js_ast.zig288
-rw-r--r--src/js_parser/ast.js111
-rw-r--r--src/js_parser/js_parser.zig626
-rw-r--r--src/linker.zig2
-rw-r--r--src/node_fallbacks.zig8
-rw-r--r--src/options.zig15
-rw-r--r--src/runtime.zig1
16 files changed, 1023 insertions, 278 deletions
diff --git a/examples/macros/dogeify.tsx b/examples/macros/dogeify.tsx
new file mode 100644
index 000000000..0b5a29e80
--- /dev/null
+++ b/examples/macros/dogeify.tsx
@@ -0,0 +1,3 @@
+export function dogeify(astNode: any) {
+ return <void />;
+}
diff --git a/examples/macros/hello.js b/examples/macros/hello.js
new file mode 100644
index 000000000..80ea9ba03
--- /dev/null
+++ b/examples/macros/hello.js
@@ -0,0 +1,4 @@
+import { dogeify } from "macro:./dogeify";
+
+const wow = dogeify`Call #1!`;
+const suchDoge = dogeify`Call #2!`;
diff --git a/examples/macros/package.json b/examples/macros/package.json
new file mode 100644
index 000000000..f93a6aa26
--- /dev/null
+++ b/examples/macros/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "macros",
+ "version": "1.0.0",
+ "main": "index.js",
+ "license": "MIT"
+}
diff --git a/examples/macros/tsconfig.json b/examples/macros/tsconfig.json
new file mode 100644
index 000000000..a224293f4
--- /dev/null
+++ b/examples/macros/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ }
+}
diff --git a/src/bundler.zig b/src/bundler.zig
index 85a1802d9..bd6c69465 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -751,7 +751,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);
@@ -771,7 +771,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,
@@ -971,7 +971,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(
@@ -2201,7 +2201,7 @@ 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.isBun() 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
@@ -2216,6 +2216,8 @@ pub const Bundler = struct {
opts.macro_context = &bundler.macro_context.?;
+ opts.features.is_macro = bundler.options.platform == .bunMacro;
+
const value = (bundler.resolver.caches.js.parse(
allocator,
opts,
@@ -3113,6 +3115,75 @@ pub const ServerEntryPoint = struct {
}
};
+// This is not very fast.
+// The idea is: we want to generate a unique entry point per macro function export that registers the macro
+// Registering the macro happens in VirtualMachine
+// We "register" it which just marks the JSValue as protected.
+// This is mostly a workaround for being unable to call ESM exported functions from C++.
+// When that is resolved, we should remove this.
+pub const MacroEntryPoint = struct {
+ code_buffer: [std.fs.MAX_PATH_BYTES * 2 + 500]u8 = undefined,
+ output_code_buffer: [std.fs.MAX_PATH_BYTES * 8 + 500]u8 = undefined,
+ source: logger.Source = undefined,
+
+ pub fn generateID(entry_path: string, function_name: string, buf: []u8, len: *u32) i32 {
+ var hasher = std.hash.Wyhash.init(0);
+ hasher.update(js_ast.Macro.namespaceWithColon);
+ hasher.update(entry_path);
+ hasher.update(function_name);
+ const truncated_u32 = @truncate(u32, hasher.final());
+
+ const specifier = std.fmt.bufPrint(buf, js_ast.Macro.namespaceWithColon ++ "//{x}.js", .{truncated_u32}) catch unreachable;
+ len.* = @truncate(u32, specifier.len);
+
+ return generateIDFromSpecifier(specifier);
+ }
+
+ pub fn generateIDFromSpecifier(specifier: string) i32 {
+ return @bitCast(i32, @truncate(u32, std.hash.Wyhash.hash(0, specifier)));
+ }
+
+ pub fn generate(
+ entry: *MacroEntryPoint,
+ bundler: *Bundler,
+ import_path: Fs.PathName,
+ function_name: string,
+ macro_id: i32,
+ macro_label_: string,
+ ) !void {
+ const dir_to_use: string = import_path.dirWithTrailingSlash();
+ std.mem.copy(u8, entry.code_buffer[0..macro_label_.len], macro_label_);
+ const macro_label = entry.code_buffer[0..macro_label_.len];
+
+ const code = try std.fmt.bufPrint(
+ entry.code_buffer[macro_label.len..],
+ \\//Auto-generated file
+ \\import * as Macros from '{s}{s}';
+ \\
+ \\if (!('{s}' in Macros)) {{
+ \\ throw new Error("Macro '{s}' not found in '{s}{s}'");
+ \\}}
+ \\
+ \\Bun.registerMacro({d}, Macros['{s}']);
+ ,
+ .{
+ dir_to_use,
+ import_path.filename,
+ function_name,
+ function_name,
+ dir_to_use,
+ import_path.filename,
+ macro_id,
+ function_name,
+ },
+ );
+
+ entry.source = logger.Source.initPathString(macro_label, code);
+ entry.source.path.text = macro_label;
+ entry.source.path.namespace = js_ast.Macro.namespace;
+ }
+};
+
pub const ResolveResults = std.AutoHashMap(
u64,
void,
diff --git a/src/defines.zig b/src/defines.zig
index dc08e7ac8..9c9e8a3f8 100644
--- a/src/defines.zig
+++ b/src/defines.zig
@@ -269,10 +269,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/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp
index 407b452d2..407a03b96 100644
--- a/src/javascript/jsc/bindings/bindings.cpp
+++ b/src/javascript/jsc/bindings/bindings.cpp
@@ -512,7 +512,7 @@ bWTF__String JSC__JSString__value(JSC__JSString *arg0, JSC__JSGlobalObject *arg1
JSC__JSValue JSC__JSModuleLoader__callExportedFunction(JSC__JSGlobalObject *globalObject,
ZigString specifier, ZigString functionName,
JSC__JSValue *arguments,
- unsigned char args_len,
+ unsigned char argumentsCount,
ZigException *zig_exception) {
JSC::VM &vm = globalObject->vm();
JSC::JSLockHolder lock(vm);
@@ -531,22 +531,21 @@ JSC__JSValue JSC__JSModuleLoader__callExportedFunction(JSC__JSGlobalObject *glob
if (JSC::JSModuleRecord *record =
JSC::jsDynamicCast<JSC::JSModuleRecord *>(vm, entry->getDirect(vm, recordIdentifier))) {
auto fn_impl = WTF::ExternalStringImpl::createStatic(functionName.ptr, functionName.len);
- auto fn_ident = reinterpret_cast<WTF::UniquedStringImpl *>(specifier_impl.ptr());
- auto env = record->getModuleNamespace(globalObject);
+ auto fn_ident = reinterpret_cast<WTF::UniquedStringImpl *>(fn_impl.ptr());
+ auto moduleNamespace = record->getModuleNamespace(globalObject);
if (JSC::JSValue macroFunctionExport =
- env->getIfPropertyExists(globalObject, JSC::PropertyName(fn_ident))) {
+ moduleNamespace->getIfPropertyExists(globalObject, JSC::PropertyName(fn_ident))) {
if (JSC::JSObject *macroFunction = JSC::asObject(macroFunctionExport.asCell())) {
- // auto functionNameImpl =
- // WTF::ExternalStringImpl::createStatic(functionName.ptr, functionName.len);
JSC::VMEntryScope entryScope(vm, globalObject);
auto callData = JSC::getCallData(vm, macroFunction);
if (callData.type == JSC::CallData::Type::None) return JSC::JSValue::encode({});
JSC::MarkedArgumentBuffer argList;
- for (size_t i = 0; i < args_len; i++) argList.append(JSC::JSValue::decode(arguments[i]));
+ for (size_t i = 0; i < argumentsCount; i++)
+ argList.append(JSC::JSValue::decode(arguments[i]));
NakedPtr<JSC::Exception> uncaughtException;
JSC::JSValue reval = JSC::call(globalObject, macroFunction, callData,
diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig
index 96c1767b4..09fa04d3b 100644
--- a/src/javascript/jsc/bindings/bindings.zig
+++ b/src/javascript/jsc/bindings/bindings.zig
@@ -1445,11 +1445,11 @@ pub const JSValue = enum(i64) {
}
pub inline fn asRef(this: JSValue) C_API.JSValueRef {
- return @intToPtr(C_API.JSValueRef, @intCast(usize, @enumToInt(this)));
+ return @intToPtr(C_API.JSValueRef, @bitCast(usize, @enumToInt(this)));
}
pub inline fn fromRef(this: C_API.JSValueRef) JSValue {
- return @intToEnum(JSValue, @intCast(i64, @ptrToInt(this)));
+ return @intToEnum(JSValue, @bitCast(i64, @ptrToInt(this)));
}
pub inline fn asObjectRef(this: JSValue) C_API.JSObjectRef {
@@ -1457,7 +1457,7 @@ pub const JSValue = enum(i64) {
}
pub inline fn asVoid(this: JSValue) *c_void {
- return @intToPtr(*c_void, @intCast(usize, @enumToInt(this)));
+ return @intToPtr(*c_void, @bitCast(usize, @enumToInt(this)));
}
pub const Extern = [_][]const u8{ "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "get", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" };
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 9e6f5defd..e461f48d3 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -9,8 +9,10 @@ const Api = @import("../../api/schema.zig").Api;
const options = @import("../../options.zig");
const Bundler = @import("../../bundler.zig").Bundler;
const ServerEntryPoint = @import("../../bundler.zig").ServerEntryPoint;
+const MacroEntryPoint = @import("../../bundler.zig").MacroEntryPoint;
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;
@@ -158,6 +160,41 @@ pub const Bun = struct {
return JSValue.createStringArray(VirtualMachine.vm.global, styles.ptr, styles.len).asRef();
}
+ 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 getRouteFiles(
this: void,
ctx: js.JSContextRef,
@@ -330,6 +367,13 @@ pub const Bun = struct {
.@"return" = "string",
},
},
+ .registerMacro = .{
+ .rfn = Bun.registerMacro,
+ .ts = d.ts{
+ .name = "registerMacro",
+ .@"return" = "undefined",
+ },
+ },
},
.{
.main = .{
@@ -367,6 +411,9 @@ pub const VirtualMachine = struct {
allocator: *std.mem.Allocator,
node_modules: ?*NodeModuleBundle = null,
bundler: Bundler,
+
+ macro_mode: bool = false,
+
watcher: ?*http.Watcher = null,
console: *ZigConsoleClient,
log: *logger.Log,
@@ -375,6 +422,7 @@ pub const VirtualMachine = struct {
process: js.JSObjectRef = null,
blobs: *Blob.Group = undefined,
flush_list: std.ArrayList(string),
+ macro_entry_points: std.AutoArrayHashMap(i32, *MacroEntryPoint),
entry_point: ServerEntryPoint = undefined,
arena: *std.heap.ArenaAllocator = undefined,
@@ -383,9 +431,22 @@ pub const VirtualMachine = struct {
transpiled_count: usize = 0,
resolved_count: usize = 0,
had_errors: bool = false,
+ macros: MacroMap,
pub threadlocal var vm_loaded = false;
pub threadlocal var vm: *VirtualMachine = undefined;
+ pub const MacroMap = std.AutoArrayHashMap(i32, js.JSObjectRef);
+
+ pub fn enableMacroMode(this: *VirtualMachine) void {
+ this.bundler.options.platform = .bunMacro;
+ 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,
_args: Api.TransformOptions,
@@ -420,6 +481,8 @@ pub const VirtualMachine = struct {
.console = console,
.node_modules = bundler.options.node_modules_bundle,
.log = log,
+ .macros = MacroMap.init(allocator),
+ .macro_entry_points = @TypeOf(VirtualMachine.vm.macro_entry_points).init(allocator),
.flush_list = std.ArrayList(string).init(allocator),
.blobs = try Blob.Group.init(allocator),
};
@@ -550,7 +613,7 @@ pub const VirtualMachine = struct {
var parse_result = ParseResult{ .source = vm.entry_point.source, .ast = main_ast, .loader = .js, .input_fd = null };
var file_path = Fs.Path.init(bundler.fs.top_level_dir);
file_path.name.dir = bundler.fs.top_level_dir;
- file_path.name.base = "bun:main";
+ file_path.name.base = main_file_name;
try bundler.linker.link(
file_path,
&parse_result,
@@ -579,6 +642,19 @@ pub const VirtualMachine = struct {
.hash = 0,
.bytecodecache_fd = 0,
};
+ } else if (_specifier.len > js_ast.Macro.namespaceWithColon.len and
+ strings.eqlComptimeIgnoreLen(_specifier[0..js_ast.Macro.namespaceWithColon.len], js_ast.Macro.namespaceWithColon))
+ {
+ if (vm.macro_entry_points.get(MacroEntryPoint.generateIDFromSpecifier(_specifier))) |entry| {
+ return ResolvedSource{
+ .allocator = null,
+ .source_code = ZigString.init(entry.source.contents),
+ .specifier = ZigString.init(_specifier),
+ .source_url = ZigString.init(_specifier),
+ .hash = 0,
+ .bytecodecache_fd = 0,
+ };
+ }
}
const specifier = normalizeSpecifier(_specifier);
@@ -691,10 +767,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,
);
@@ -970,6 +1052,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
@@ -1049,7 +1171,7 @@ pub const VirtualMachine = struct {
var build_error = private_data_ptr.as(BuildError);
if (!build_error.logged) {
var writer = Output.errorWriter();
- build_error.msg.formatWriter(@TypeOf(writer), writer, allow_ansi_color) catch {};
+ build_error.msg.writeFormat(writer, allow_ansi_color) catch {};
build_error.logged = true;
}
this.had_errors = this.had_errors or build_error.msg.kind == .err;
@@ -1065,7 +1187,7 @@ pub const VirtualMachine = struct {
var resolve_error = private_data_ptr.as(ResolveError);
if (!resolve_error.logged) {
var writer = Output.errorWriter();
- resolve_error.msg.formatWriter(@TypeOf(writer), writer, allow_ansi_color) catch {};
+ resolve_error.msg.writeFormat(writer, allow_ansi_color) catch {};
resolve_error.logged = true;
}
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 1761e20ca..9ddafdf80 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -321,7 +321,7 @@ pub const Binding = struct {
*B.Object => {
return Binding{ .loc = loc, .data = B{ .b_object = t } };
},
- *B.Missing => {
+ B.Missing => {
return Binding{ .loc = loc, .data = B{ .b_missing = t } };
},
else => {
@@ -1700,6 +1700,7 @@ pub const Stmt = struct {
pub const All = NewBaseStore(Union, 128);
threadlocal var has_inited = false;
+ pub threadlocal var disable_reset = false;
pub fn create(allocator: *std.mem.Allocator) void {
if (has_inited) {
return;
@@ -1710,6 +1711,7 @@ pub const Stmt = struct {
}
pub fn reset() void {
+ if (disable_reset) return;
All.reset();
}
@@ -2367,7 +2369,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_number = Data.Store.All.append(Type, st),
+ .e_number = st,
},
};
},
@@ -2542,8 +2544,160 @@ pub const Expr = struct {
e_class,
e_require,
+ pub inline fn toPublicValue(this: Tag) u16 {
+ return @intCast(u16, @enumToInt(this)) + 16;
+ }
+
+ pub inline fn fromPublicValue(comptime ValueType: type, value: ValueType) ?Tag {
+ if (value < 16 or value > @enumToInt(Tag.e_require)) return null;
+
+ switch (comptime ValueType) {
+ f64 => {
+ return @intToEnum(@floatToInt(u16, value - 16), Tag);
+ },
+ else => {
+ return @intToEnum(@intCast(u6, @intCast(u16, value) - 16), Tag);
+ },
+ }
+ }
+
+ pub const names_strings = [_]string{
+ "<array>",
+ "<unary>",
+ "<binary>",
+ "<boolean>",
+ "<super>",
+ "<null>",
+ "<void>",
+ "<new>",
+ "<function>",
+ "<ntarget>",
+ "<import>",
+ "<call>",
+ "<dot>",
+ "<index>",
+ "<arrow>",
+ "<id>",
+ "<importid>",
+ "<private>",
+ "<jsx>",
+ "<missing>",
+ "<number>",
+ "<bigint>",
+ "<object>",
+ "<spread>",
+ "<string>",
+ "<tpart>",
+ "<template>",
+ "<regexp>",
+ "<await>",
+ "<yield>",
+ "<if>",
+ "<resolve>",
+ "<import>",
+ "<this>",
+ "<class>",
+ "<require>",
+ };
+ pub const valid_names_list: string = brk: {
+ var names_list = names_strings[0];
+ for (names_strings[1..]) |name_str, i| {
+ names_list = names_list ++ "\n " ++ name_str;
+ }
+ break :brk " " ++ names_list;
+ };
+
+ pub const TagName = std.EnumArray(Tag, string);
+
+ pub const names: TagName = brk: {
+ var array = TagName.initUndefined();
+ array.set(.e_array, names_strings[0]);
+ array.set(.e_unary, names_strings[1]);
+ array.set(.e_binary, names_strings[2]);
+ array.set(.e_boolean, names_strings[3]);
+ array.set(.e_super, names_strings[4]);
+ array.set(.e_null, names_strings[5]);
+ array.set(.e_undefined, names_strings[6]);
+ array.set(.e_new, names_strings[7]);
+ array.set(.e_function, names_strings[8]);
+ array.set(.e_new_target, names_strings[9]);
+ array.set(.e_import_meta, names_strings[10]);
+ array.set(.e_call, names_strings[11]);
+ array.set(.e_dot, names_strings[12]);
+ array.set(.e_index, names_strings[13]);
+ array.set(.e_arrow, names_strings[14]);
+ array.set(.e_identifier, names_strings[15]);
+ array.set(.e_import_identifier, names_strings[16]);
+ array.set(.e_private_identifier, names_strings[17]);
+ array.set(.e_jsx_element, names_strings[18]);
+ array.set(.e_missing, names_strings[19]);
+ array.set(.e_number, names_strings[20]);
+ array.set(.e_big_int, names_strings[21]);
+ array.set(.e_object, names_strings[22]);
+ array.set(.e_spread, names_strings[23]);
+ array.set(.e_string, names_strings[24]);
+ array.set(.e_template_part, names_strings[25]);
+ array.set(.e_template, names_strings[26]);
+ array.set(.e_reg_exp, names_strings[27]);
+ array.set(.e_await, names_strings[28]);
+ array.set(.e_yield, names_strings[29]);
+ array.set(.e_if, names_strings[30]);
+ array.set(.e_require_or_require_resolve, names_strings[31]);
+ array.set(.e_import, names_strings[32]);
+ array.set(.e_this, names_strings[33]);
+ array.set(.e_class, names_strings[34]);
+ array.set(.e_require, names_strings[35]);
+ break :brk array;
+ };
+ pub const TagExactSizeMatcher = strings.ExactSizeMatcher(8);
+ pub fn find(name_: string) ?Tag {
+ return switch (TagExactSizeMatcher.match(name_)) {
+ TagExactSizeMatcher.case("array") => Tag.e_array,
+ TagExactSizeMatcher.case("unary") => Tag.e_unary,
+ TagExactSizeMatcher.case("binary") => Tag.e_binary,
+ TagExactSizeMatcher.case("boolean") => Tag.e_boolean,
+ TagExactSizeMatcher.case("true") => Tag.e_boolean,
+ TagExactSizeMatcher.case("false") => Tag.e_boolean,
+ TagExactSizeMatcher.case("super") => Tag.e_super,
+ TagExactSizeMatcher.case("null") => Tag.e_null,
+ TagExactSizeMatcher.case("void") => Tag.e_undefined,
+ TagExactSizeMatcher.case("new") => Tag.e_new,
+ TagExactSizeMatcher.case("function") => Tag.e_function,
+ TagExactSizeMatcher.case("ntarget") => Tag.e_new_target,
+ TagExactSizeMatcher.case("imeta") => Tag.e_import_meta,
+ TagExactSizeMatcher.case("call") => Tag.e_call,
+ TagExactSizeMatcher.case("dot") => Tag.e_dot,
+ TagExactSizeMatcher.case("index") => Tag.e_index,
+ TagExactSizeMatcher.case("arrow") => Tag.e_arrow,
+ TagExactSizeMatcher.case("id") => Tag.e_identifier,
+ TagExactSizeMatcher.case("importid") => Tag.e_import_identifier,
+ TagExactSizeMatcher.case("jsx") => Tag.e_jsx_element,
+ TagExactSizeMatcher.case("missing") => Tag.e_missing,
+ TagExactSizeMatcher.case("number") => Tag.e_number,
+ TagExactSizeMatcher.case("bigint") => Tag.e_big_int,
+ TagExactSizeMatcher.case("object") => Tag.e_object,
+ TagExactSizeMatcher.case("spread") => Tag.e_spread,
+ TagExactSizeMatcher.case("string") => Tag.e_string,
+ TagExactSizeMatcher.case("tpart") => Tag.e_template_part,
+ TagExactSizeMatcher.case("template") => Tag.e_template,
+ TagExactSizeMatcher.case("regexp") => Tag.e_reg_exp,
+ TagExactSizeMatcher.case("await") => Tag.e_await,
+ TagExactSizeMatcher.case("yield") => Tag.e_yield,
+ TagExactSizeMatcher.case("if") => Tag.e_if,
+ TagExactSizeMatcher.case("import") => Tag.e_import,
+ TagExactSizeMatcher.case("this") => Tag.e_this,
+ TagExactSizeMatcher.case("class") => Tag.e_class,
+ TagExactSizeMatcher.case("require") => Tag.e_require,
+ else => null,
+ };
+ }
+
+ pub inline fn name(this: Tag) string {
+ return names.get(this);
+ }
+
pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void {
- return try std.json.stringify(@tagName(self), opts, o);
+ return try std.json.stringify(self.name(), opts, o);
}
pub fn isArray(self: Tag) bool {
@@ -3074,7 +3228,7 @@ pub const Expr = struct {
e_import: *E.Import,
e_boolean: E.Boolean,
- e_number: *E.Number,
+ e_number: E.Number,
e_big_int: *E.BigInt,
e_string: *E.String,
@@ -3129,6 +3283,7 @@ pub const Expr = struct {
);
threadlocal var has_inited = false;
+ pub threadlocal var disable_reset = false;
pub fn create(allocator: *std.mem.Allocator) void {
if (has_inited) {
return;
@@ -3140,6 +3295,7 @@ pub const Expr = struct {
}
pub fn reset() void {
+ if (disable_reset) return;
All.reset();
Identifier.reset();
}
@@ -3932,15 +4088,17 @@ pub const Macro = struct {
const js = @import("./javascript/jsc/JavascriptCore.zig");
const Zig = @import("./javascript/jsc/bindings/exports.zig");
const Bundler = @import("./bundler.zig").Bundler;
+ const MacroEntryPoint = @import("./bundler.zig").MacroEntryPoint;
pub const namespace: string = "macro";
+ pub const namespaceWithColon: string = namespace ++ ":";
pub fn isMacroPath(str: string) bool {
- return (str.len > "macro:".len and strings.eqlComptimeIgnoreLen(str[0.."macro:".len], "macro:"));
+ return (str.len > namespaceWithColon.len and strings.eqlComptimeIgnoreLen(str[0..namespaceWithColon.len], namespaceWithColon));
}
pub const MacroContext = struct {
- pub const MacroMap = std.AutoArrayHashMap(u64, Macro);
+ pub const MacroMap = std.AutoArrayHashMap(i32, Macro);
resolver: *Resolver,
env: *DotEnv.Loader,
@@ -3956,7 +4114,7 @@ pub const Macro = struct {
pub fn call(
this: *MacroContext,
- specifier: string,
+ import_record_path: string,
source_dir: string,
log: *logger.Log,
source: *const logger.Source,
@@ -3965,10 +4123,14 @@ pub const Macro = struct {
args: []Expr,
function_name: string,
) anyerror!Expr {
+ Expr.Data.Store.disable_reset = true;
+ Stmt.Data.Store.disable_reset = true;
+ defer Expr.Data.Store.disable_reset = false;
+ defer Stmt.Data.Store.disable_reset = false;
// const is_package_path = isPackagePath(specifier);
- std.debug.assert(strings.eqlComptime(specifier[0..6], "macro:"));
+ std.debug.assert(isMacroPath(import_record_path));
- const resolve_result = this.resolver.resolve(source_dir, specifier["macro:".len..], .stmt) catch |err| {
+ const resolve_result = this.resolver.resolve(source_dir, import_record_path[namespaceWithColon.len..], .stmt) catch |err| {
switch (err) {
error.ModuleNotFound => {
log.addResolveError(
@@ -3976,7 +4138,7 @@ pub const Macro = struct {
import_range,
log.msgs.allocator,
"Macro \"{s}\" not found",
- .{specifier},
+ .{import_record_path},
.stmt,
) catch unreachable;
return error.MacroNotFound;
@@ -3987,28 +4149,57 @@ pub const Macro = struct {
import_range,
log.msgs.allocator,
"{s} resolving macro \"{s}\"",
- .{ @errorName(err), specifier },
+ .{ @errorName(err), import_record_path },
) catch unreachable;
return err;
},
}
};
- var macro_entry = this.macros.getOrPut(std.hash.Wyhash.hash(0, resolve_result.path_pair.primary.text)) catch unreachable;
+ var specifier_buf: [64]u8 = undefined;
+ var specifier_buf_len: u32 = 0;
+ const hash = MacroEntryPoint.generateID(
+ resolve_result.path_pair.primary.text,
+ function_name,
+ &specifier_buf,
+ &specifier_buf_len,
+ );
+
+ var macro_entry = this.macros.getOrPut(hash) catch unreachable;
if (!macro_entry.found_existing) {
- macro_entry.value_ptr.* = try Macro.init(
+ macro_entry.value_ptr.* = Macro.init(
default_allocator,
this.resolver,
resolve_result,
log,
this.env,
- specifier,
- source_dir,
- );
+ function_name,
+ specifier_buf[0..specifier_buf_len],
+ hash,
+ ) catch |err| {
+ macro_entry.value_ptr.* = Macro{ .resolver = undefined, .disabled = true };
+ return err;
+ };
+ Output.flush();
}
defer Output.flush();
- return Macro.Runner.run(macro_entry.value_ptr.*, log, default_allocator, function_name, caller, args, source);
+ const macro = macro_entry.value_ptr.*;
+ if (macro.disabled) {
+ return caller;
+ }
+ macro.vm.enableMacroMode();
+ defer macro.vm.disableMacroMode();
+ return Macro.Runner.run(
+ macro,
+ log,
+ default_allocator,
+ function_name,
+ caller,
+ args,
+ source,
+ hash,
+ );
// this.macros.getOrPut(key: K)
}
};
@@ -4045,6 +4236,15 @@ pub const Macro = struct {
},
);
+ // pub fn isInstanceOf(
+ // ctx: js.JSContextRef,
+ // obj: js.JSObjectRef,
+ // value: js.JSValueRef,
+ // exception: js.ExceptionRef,
+ // ) bool {
+ // js.JSValueToNumber(ctx, value, exception);
+ // }
+
pub fn toString(
this: *JSExpr,
ctx: js.JSContextRef,
@@ -4117,6 +4317,7 @@ pub const Macro = struct {
vm: *JavaScript.VirtualMachine = undefined,
resolved: ResolveResult = undefined,
+ disabled: bool = false,
pub fn init(
allocator: *std.mem.Allocator,
@@ -4124,8 +4325,9 @@ pub const Macro = struct {
resolved: ResolveResult,
log: *logger.Log,
env: *DotEnv.Loader,
+ function_name: string,
specifier: string,
- source_dir: string,
+ hash: i32,
) !Macro {
const path = resolved.path_pair.primary;
@@ -4133,20 +4335,29 @@ pub const Macro = struct {
JavaScript.VirtualMachine.vm
else brk: {
var _vm = try JavaScript.VirtualMachine.init(default_allocator, resolver.opts.transform_options, null, log, env);
+ _vm.enableMacroMode();
+
_vm.bundler.configureLinker();
_vm.bundler.configureDefines() catch unreachable;
break :brk _vm;
};
- JavaScript.VirtualMachine.vm_loaded = true;
+ vm.enableMacroMode();
- var loaded_result = try vm.loadEntryPoint(path.text);
+ var loaded_result = try vm.loadMacroEntryPoint(path.text, function_name, specifier, hash);
if (loaded_result.status(vm.global.vm()) == JSC.JSPromise.Status.Rejected) {
vm.defaultErrorHandler(loaded_result.result(vm.global.vm()), null);
+ vm.disableMacroMode();
return error.MacroLoadError;
}
+ JavaScript.VirtualMachine.vm_loaded = true;
+
+ // We don't need to do anything with the result.
+ // We just want to make sure the promise is finished.
+ _ = loaded_result.result(vm.global.vm());
+
return Macro{
.vm = vm,
.resolved = resolved,
@@ -4155,7 +4366,7 @@ pub const Macro = struct {
}
pub const Runner = struct {
- threadlocal var args_buf: [32]JSC.JSValue = undefined;
+ threadlocal var args_buf: [32]js.JSObjectRef = undefined;
threadlocal var expr_nodes_buf: [32]JSExpr = undefined;
threadlocal var exception_holder: Zig.ZigException.Holder = undefined;
pub fn run(
@@ -4166,32 +4377,37 @@ pub const Macro = struct {
caller: Expr,
args: []Expr,
source: *const logger.Source,
+ id: i32,
) Expr {
+ if (comptime isDebug) Output.prettyln("<r><d>[macro]<r> call <d><b>{s}<r>", .{function_name});
+
exception_holder = Zig.ZigException.Holder.init();
expr_nodes_buf[0] = JSExpr{ .expr = caller };
- args_buf[0] = JSC.JSValue.fromRef(JSExpr.Class.make(
+ args_buf[0] = JSExpr.Class.make(
macro.vm.global.ref(),
&expr_nodes_buf[0],
- ).?);
+ );
for (args) |arg, i| {
expr_nodes_buf[i + 1] = JSExpr{ .expr = arg };
- args_buf[i + 1] = JSC.JSValue.fromRef(
+ args_buf[i + 1] =
JSExpr.Class.make(
- macro.vm.global.ref(),
- &expr_nodes_buf[i + 1],
- ).?,
+ macro.vm.global.ref(),
+ &expr_nodes_buf[i + 1],
);
}
+ args_buf[args.len + 2] = null;
- // Step 1. Resolve the macro specifier
- const result = JSC.JSModuleLoader.callExportedFunction(
- macro.vm.global,
- JSC.ZigString.init(macro.resolved.path_pair.primary.text),
- JSC.ZigString.init(function_name),
- &args_buf,
- @truncate(u8, args.len + 1),
- &exception_holder.zig_exception,
- );
+ var macro_callback = macro.vm.macros.get(id) orelse return caller;
+ var result = js.JSObjectCallAsFunctionReturnValue(macro.vm.global.ref(), macro_callback, null, args.len + 1, &args_buf);
+ var promise = JSC.JSPromise.resolvedPromise(macro.vm.global, result);
+ macro.vm.global.vm().drainMicrotasks();
+
+ if (promise.status(macro.vm.global.vm()) == .Rejected) {
+ macro.vm.defaultErrorHandler(promise.result(macro.vm.global.vm()), null);
+ return caller;
+ }
+
+ const value = promise.result(macro.vm.global.vm());
return caller;
}
diff --git a/src/js_parser/ast.js b/src/js_parser/ast.js
new file mode 100644
index 000000000..a02748664
--- /dev/null
+++ b/src/js_parser/ast.js
@@ -0,0 +1,111 @@
+globalThis.BunASTNode ??= class BunASTNode {
+ position = -1;
+
+};
+
+if (!globalThis.BunAST) {
+ globalThis.BunAST = {
+ EArray: class EArray extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EUnary: class EUnary extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EBinary: class EBinary extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EClass: class EClass extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ ENew: class ENew extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EFunction: class EFunction extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ ECall: class ECall extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EDot: class EDot extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EIndex: class EIndex extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EArrow: class EArrow extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EIdentifier: class EIdentifier extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EImportIdentifier: class EImportIdentifier extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EPrivateIdentifier: class EPrivateIdentifier extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EJsxElement: class EJsxElement extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EObject: class EObject extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ ESpread: class ESpread extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ ETemplatePart: class ETemplatePart extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ ETemplate: class ETemplate extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ ERegExp: class ERegExp extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EAwait: class EAwait extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EYield: class EYield extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EIf: class EIf extends BunASTNode {
+ no = Number.MAX_SAFE_INTEGER;
+ yes = Number.MAX_SAFE_INTEGER;
+ },
+ ERequire: class ERequire extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EImport: class EImport extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EBoolean: class EBoolean extends BunASTNode {
+ val = false;
+ },
+ ENumber: class ENumber extends BunASTNode {
+ val = 0;
+ },
+ EBigInt: class EBigInt extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EString: class EString extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EMissing: class EMissing extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EThis: class EThis extends BunASTNode {
+ },
+ ESuper: class ESuper extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ ENull: class ENull extends BunASTNode {
+ },
+ EUndefined: class EUndefined extends BunASTNode {
+ },
+ ENewTarget: class ENewTarget extends BunASTNode {
+ #ptr = Number.MAX_SAFE_INTEGER;
+ },
+ EImportMeta: class EImportMeta extends BunASTNode {
+ },
+ };
+}
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 26e94e0d3..39055d7b3 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -692,6 +692,98 @@ const StaticSymbolName = struct {
};
};
+const BunJSX = struct {
+ const TagNumberExprArray = std.EnumArray(Expr.Tag, E.Number);
+
+ pub const tag_numbers: TagNumberExprArray = brk: {
+ var numbers = TagNumberExprArray.initFill(E.Number{ .value = 0 });
+ tag_numbers.set(.e_array, E.Number{ .value = @intToFloat(f64, Tag.e_array.toPublicValue()) });
+ tag_numbers.set(.e_unary, E.Number{ .value = @intToFloat(f64, Tag.e_unary.toPublicValue()) });
+ tag_numbers.set(.e_binary, E.Number{ .value = @intToFloat(f64, Tag.e_binary.toPublicValue()) });
+ tag_numbers.set(.e_class, E.Number{ .value = @intToFloat(f64, Tag.e_class.toPublicValue()) });
+ tag_numbers.set(.e_new, E.Number{ .value = @intToFloat(f64, Tag.e_new.toPublicValue()) });
+ tag_numbers.set(.e_function, E.Number{ .value = @intToFloat(f64, Tag.e_function.toPublicValue()) });
+ tag_numbers.set(.e_call, E.Number{ .value = @intToFloat(f64, Tag.e_call.toPublicValue()) });
+ tag_numbers.set(.e_dot, E.Number{ .value = @intToFloat(f64, Tag.e_dot.toPublicValue()) });
+ tag_numbers.set(.e_index, E.Number{ .value = @intToFloat(f64, Tag.e_index.toPublicValue()) });
+ tag_numbers.set(.e_arrow, E.Number{ .value = @intToFloat(f64, Tag.e_arrow.toPublicValue()) });
+ tag_numbers.set(.e_identifier, E.Number{ .value = @intToFloat(f64, Tag.e_identifier.toPublicValue()) });
+ tag_numbers.set(.e_import_identifier, E.Number{ .value = @intToFloat(f64, Tag.e_import_identifier.toPublicValue()) });
+ tag_numbers.set(.e_private_identifier, E.Number{ .value = @intToFloat(f64, Tag.e_private_identifier.toPublicValue()) });
+ tag_numbers.set(.e_jsx_element, E.Number{ .value = @intToFloat(f64, Tag.e_jsx_element.toPublicValue()) });
+ tag_numbers.set(.e_object, E.Number{ .value = @intToFloat(f64, Tag.e_object.toPublicValue()) });
+ tag_numbers.set(.e_spread, E.Number{ .value = @intToFloat(f64, Tag.e_spread.toPublicValue()) });
+ tag_numbers.set(.e_template_part, E.Number{ .value = @intToFloat(f64, Tag.e_template_part.toPublicValue()) });
+ tag_numbers.set(.e_template, E.Number{ .value = @intToFloat(f64, Tag.e_template.toPublicValue()) });
+ tag_numbers.set(.e_reg_exp, E.Number{ .value = @intToFloat(f64, Tag.e_reg_exp.toPublicValue()) });
+ tag_numbers.set(.e_await, E.Number{ .value = @intToFloat(f64, Tag.e_await.toPublicValue()) });
+ tag_numbers.set(.e_yield, E.Number{ .value = @intToFloat(f64, Tag.e_yield.toPublicValue()) });
+ tag_numbers.set(.e_if, E.Number{ .value = @intToFloat(f64, Tag.e_if.toPublicValue()) });
+ tag_numbers.set(.e_require, E.Number{ .value = @intToFloat(f64, Tag.e_require.toPublicValue()) });
+ tag_numbers.set(.e_require_or_require_resolve, E.Number{ .value = @intToFloat(f64, Tag.e_require_or_require_resolve.toPublicValue()) });
+ tag_numbers.set(.e_import, E.Number{ .value = @intToFloat(f64, Tag.e_import.toPublicValue()) });
+ tag_numbers.set(.e_boolean, E.Number{ .value = @intToFloat(f64, Tag.e_boolean.toPublicValue()) });
+ tag_numbers.set(.e_number, E.Number{ .value = @intToFloat(f64, Tag.e_number.toPublicValue()) });
+ tag_numbers.set(.e_big_int, E.Number{ .value = @intToFloat(f64, Tag.e_big_int.toPublicValue()) });
+ tag_numbers.set(.e_string, E.Number{ .value = @intToFloat(f64, Tag.e_string.toPublicValue()) });
+ tag_numbers.set(.e_missing, E.Number{ .value = @intToFloat(f64, Tag.e_missing.toPublicValue()) });
+ tag_numbers.set(.e_this, E.Number{ .value = @intToFloat(f64, Tag.e_this.toPublicValue()) });
+ tag_numbers.set(.e_super, E.Number{ .value = @intToFloat(f64, Tag.e_super.toPublicValue()) });
+ tag_numbers.set(.e_null, E.Number{ .value = @intToFloat(f64, Tag.e_null.toPublicValue()) });
+ tag_numbers.set(.e_undefined, E.Number{ .value = @intToFloat(f64, Tag.e_undefined.toPublicValue()) });
+ tag_numbers.set(.e_new_target, E.Number{ .value = @intToFloat(f64, Tag.e_new_target.toPublicValue()) });
+ tag_numbers.set(.e_import_meta, E.Number{ .value = @intToFloat(f64, Tag.e_import_meta.toPublicValue()) });
+ break :brk tag_numbers;
+ };
+ pub const StaticExpr = struct {
+ // pub const one: E.Expr = E.Expr{ .tag = .e_number, .data = };
+ };
+
+ pub const tag_name_key: string = "t";
+ pub const tag_name_key_string = E.String{ .utf8 = tag_name_key };
+
+ pub const children_name_key: string = "c";
+ pub const children_name_key_string = E.String{ .utf8 = children_name_key };
+
+ pub const value_name_key: string = "v";
+ pub const value_name_string = E.String{ .utf8 = value_name_key };
+
+ pub const number_name_key: string = "n";
+ pub const number_name_string = E.String{ .utf8 = number_name_key };
+
+ pub const @"undefined" = E.Object{
+ .properties = &[_]G.Property{
+ .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_undefined) },
+ },
+ };
+ pub const @"null" = E.Object{
+ .properties = &[_]G.Property{
+ .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_null) },
+ },
+ };
+ pub const @"false" = E.Object{
+ .properties = &[_]G.Property{
+ .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_boolean) },
+ .{ .key = &value_name_key, .value = Expr{ .data = Prefill.Data.Zero, .loc = logger.Loc.Empty } },
+ },
+ .is_single_line = true,
+ };
+ pub const @"true" = E.Object{
+ .properties = &[_]G.Property{
+ .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_boolean) },
+ .{ .key = &value_name_key, .value = Expr{ .data = Prefill.Data.One, .loc = logger.Loc.Empty } },
+ },
+ .is_single_line = true,
+ };
+ pub const @"empty_string" = E.Object{
+ .properties = &[_]G.Property{
+ .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_string) },
+ .{ .key = &value_name_key, .value = Expr{ .data = Prefill.Data.EmptyString, .loc = logger.Loc.Empty } },
+ },
+ .is_single_line = true,
+ };
+};
+
pub const SideEffects = enum(u2) {
could_have_side_effects,
no_side_effects,
@@ -1878,6 +1970,12 @@ pub const Parser = struct {
}
pub fn parse(self: *Parser) !js_ast.Result {
+ if (self.options.features.is_macro and self.options.ts) {
+ return try self._parse(TypeScriptMacroParser);
+ } else if (self.options.features.is_macro) {
+ return try self._parse(JavaScriptMacroParser);
+ }
+
if (self.options.ts and self.options.jsx.parse) {
if (self.options.features.react_fast_refresh) {
return try self._parse(TSXParserFastRefresh);
@@ -1987,7 +2085,7 @@ pub const Parser = struct {
}
// Auto-import JSX
- if (p.options.jsx.parse) {
+ if (self.options.jsx.parse and !self.options.features.is_macro) {
const jsx_symbol: *const Symbol = &p.symbols.items[p.jsx_runtime.ref.inner_index];
const jsx_static_symbol: *const Symbol = &p.symbols.items[p.jsxs_runtime.ref.inner_index];
const jsx_fragment_symbol: *const Symbol = &p.symbols.items[p.jsx_fragment.ref.inner_index];
@@ -2553,8 +2651,14 @@ pub const Prefill = struct {
pub var ColumnNumber = [_]u16{ '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 One = E.Number{ .value = 1.0 };
+ pub const Two = E.Number{ .value = 2.0 };
+ pub const Three = E.Number{ .value = 3.0 };
+ pub const Four = E.Number{ .value = 4.0 };
+ pub const Five = E.Number{ .value = 5.0 };
+ pub var EmptyString = E.String{};
};
pub const String = struct {
pub var Key = E.String{ .value = &Prefill.StringLiteral.Key };
@@ -2564,20 +2668,27 @@ pub const Prefill = struct {
pub var ColumnNumber = E.String{ .value = &Prefill.StringLiteral.ColumnNumber };
};
pub const Data = struct {
- pub var BMissing = B{ .b_missing = BMissing_ };
- pub var BMissing_ = B.Missing{};
-
- pub var EMissing = Expr.Data{ .e_missing = EMissing_ };
- pub var EMissing_ = E.Missing{};
-
- pub var SEmpty = Stmt.Data{ .s_empty = SEmpty_ };
- pub var SEmpty_ = S.Empty{};
-
- 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 BMissing = B{ .b_missing = BMissing_ };
+ pub const BMissing_ = B.Missing{};
+
+ pub const EMissing = Expr.Data{ .e_missing = EMissing_ };
+ pub const EMissing_ = E.Missing{};
+
+ pub const SEmpty = Stmt.Data{ .s_empty = SEmpty_ };
+ pub const SEmpty_ = S.Empty{};
+
+ pub const EmptyString = Expr.Data{ .e_string = &Prefill.Value.EmptyString };
+
+ pub const Filename = Expr.Data{ .e_string = &Prefill.String.Filename };
+ pub const LineNumber = Expr.Data{ .e_string = &Prefill.String.LineNumber };
+ pub const ColumnNumber = Expr.Data{ .e_string = &Prefill.String.ColumnNumber };
+ pub const This = Expr.Data{ .e_this = E.This{} };
+ pub const Zero = Expr.Data{ .e_number = .{ .value = 0 } };
+ pub const One = Expr.Data{ .e_number = .{ .value = 1 } };
+ pub const Two = Expr.Data{ .e_number = .{ .value = 2 } };
+ pub const Three = Expr.Data{ .e_number = .{ .value = 3 } };
+ pub const Four = Expr.Data{ .e_number = .{ .value = 4 } };
+ pub const Five = Expr.Data{ .e_number = .{ .value = 5 } };
};
pub const Runtime = struct {
pub var JSXFilename = "__jsxFilename";
@@ -2605,6 +2716,8 @@ const ParserFeatures = struct {
jsx: bool = false,
scan_only: bool = false,
+ is_macro: bool = false,
+
// *** How React Fast Refresh works ***
//
// Implmenetations:
@@ -2662,21 +2775,37 @@ const ImportItemForNamespaceMap = std.StringArrayHashMap(LocRef);
const MacroRefs = std.AutoArrayHashMap(Ref, u32);
+const JSXTransformType = enum {
+ none,
+ react,
+ bun_macro,
+};
+
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 only_scan_imports_and_do_not_visit = js_parser_features.scan_only;
- const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh;
-
- const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord);
- const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports;
- const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void;
- const ParsePassSymbolUsageType = if (only_scan_imports_and_do_not_visit and is_typescript_enabled) *ScanPassResult.ParsePassSymbolUsageMap else void;
+
// P is for Parser!
// public only because of Binding.ToExpr
return struct {
+ pub const is_typescript_enabled = js_parser_features.typescript;
+ pub const is_jsx_enabled = js_parser_features.jsx;
+ pub const jsx_transform_type: JSXTransformType = brk: {
+ if (!is_jsx_enabled) break :brk JSXTransformType.none;
+
+ if (js_parser_features.is_macro) {
+ break :brk JSXTransformType.bun_macro;
+ } else {
+ break :brk JSXTransformType.react;
+ }
+ };
+ pub const only_scan_imports_and_do_not_visit = js_parser_features.scan_only;
+ pub const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh;
+
+ const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord);
+ const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports;
+ const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void;
+ const ParsePassSymbolUsageType = if (only_scan_imports_and_do_not_visit and is_typescript_enabled) *ScanPassResult.ParsePassSymbolUsageMap else void;
const P = @This();
allocator: *std.mem.Allocator,
options: Parser.Options,
@@ -3534,7 +3663,7 @@ pub fn NewParser(
}
}
- if (is_jsx_enabled) {
+ if (jsx_transform_type == .react) {
generated_symbols_count += 7;
if (p.options.jsx.development) generated_symbols_count += 1;
@@ -3577,22 +3706,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;
+ }
+ },
+ .bun_macro => {
+ p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable;
+ },
+ else => {},
}
}
@@ -10037,7 +10172,7 @@ pub fn NewParser(
// <A[]>(x)
// <A>(x) => {}
// <A = B>(x) => {}
- if (is_typescript_enabled and is_jsx_enabled) {
+ if (comptime is_typescript_enabled and is_jsx_enabled) {
var oldLexer = std.mem.toBytes(p.lexer);
try p.lexer.next();
@@ -10064,7 +10199,7 @@ pub fn NewParser(
}
}
- if (is_jsx_enabled) {
+ if (comptime is_jsx_enabled) {
// Use NextInsideJSXElement() instead of Next() so we parse "<<" as "<"
try p.lexer.nextInsideJSXElement();
const element = try p.parseJSXElement(loc);
@@ -10077,7 +10212,7 @@ pub fn NewParser(
return element;
}
- if (is_typescript_enabled) {
+ if (comptime is_typescript_enabled) {
// This is either an old-style type cast or a generic lambda function
// "<T>(x)"
@@ -10883,204 +11018,257 @@ 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) {
+ .bun_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);
+ }
- 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 (prop.value) |val| {
+ e_.properties[prop_i].value = p.visitExpr(val);
+ }
+ }
+
+ 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),
+ };
- // 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);
+ 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);
+ },
+ else => unreachable,
+ }
},
else => unreachable,
}
@@ -11100,7 +11288,6 @@ pub fn NewParser(
const ref = 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;
- Output.prettyln("Calling Macro!! {s}\n", .{name});
const record = &p.import_records.items[import_record_id];
return p.options.macro_context.call(
record.path.text,
@@ -15080,6 +15267,9 @@ const JSXParser = NewParser(.{ .jsx = true });
const TSXParser = NewParser(.{ .jsx = true, .typescript = true });
const TypeScriptParser = NewParser(.{ .typescript = true });
+const JavaScriptMacroParser = NewParser(.{ .is_macro = true });
+const TypeScriptMacroParser = NewParser(.{ .typescript = true, .is_macro = true, .jsx = 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 });
diff --git a/src/linker.zig b/src/linker.zig
index ee77fecce..382fc721c 100644
--- a/src/linker.zig
+++ b/src/linker.zig
@@ -165,7 +165,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
diff --git a/src/node_fallbacks.zig b/src/node_fallbacks.zig
index 561b2b8cf..fa51ef7df 100644
--- a/src/node_fallbacks.zig
+++ b/src/node_fallbacks.zig
@@ -499,3 +499,11 @@ pub fn contentsFromPath(path: string) ?string {
}
pub const buffer_fallback_import_name: string = "node:buffer";
+
+pub fn isDisabledFallback(name: string) bool {
+ if (name.len >= 2) {
+ return (strings.eqlComptime(name, "fs") or strings.eqlComptime(name[0..3], "fs/")) or (name.len >= "module".len and strings.eqlComptime(name, "module"));
+ }
+
+ return false;
+}
diff --git a/src/options.zig b/src/options.zig
index 02d44fa08..229defe1b 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -337,18 +337,26 @@ pub const Platform = enum {
neutral,
browser,
bun,
+ bunMacro,
node,
+ pub inline fn isBun(this: Platform) bool {
+ return switch (this) {
+ .bunMacro, .bun => true,
+ else => false,
+ };
+ }
+
pub inline fn isClient(this: Platform) bool {
return switch (this) {
- .bun => false,
+ .bunMacro, .bun => false,
else => true,
};
}
pub inline fn supportsBrowserField(this: Platform) bool {
return switch (this) {
- .neutral, .browser, .bun => true,
+ .bunMacro, .neutral, .browser, .bun => true,
else => false,
};
}
@@ -359,7 +367,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,
+ .bunMacro, .bun, .node => browser_define_value_false,
else => null,
};
}
@@ -446,6 +454,7 @@ pub const Platform = enum {
var listc = [_]string{ MAIN_FIELD_NAMES[0], MAIN_FIELD_NAMES[1], MAIN_FIELD_NAMES[2] };
array.set(Platform.browser, &listc);
array.set(Platform.bun, &listc);
+ array.set(Platform.bunMacro, &listc);
// Original comment:
// The neutral platform is for people that don't want esbuild to try to
diff --git a/src/runtime.zig b/src/runtime.zig
index 046e1a863..3f9671708 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: bool = false,
};
pub const Names = struct {