diff options
author | 2021-09-20 13:48:05 -0700 | |
---|---|---|
committer | 2021-09-20 13:48:05 -0700 | |
commit | 7e2539ed702f0667163b726a6bb12bbd5569979d (patch) | |
tree | 0daa2e883acb4e93a2df2bbb16eefb0dc7ce539f | |
parent | 60b5fb95b19b2f96dcfd851663b40e1155c9cc0e (diff) | |
download | bun-jarred/ast.tar.gz bun-jarred/ast.tar.zst bun-jarred/ast.zip |
WIPjarred/ast
Diffstat (limited to '')
-rw-r--r-- | examples/macros/dogeify.tsx | 3 | ||||
-rw-r--r-- | examples/macros/hello.js | 4 | ||||
-rw-r--r-- | examples/macros/package.json | 6 | ||||
-rw-r--r-- | examples/macros/tsconfig.json | 5 | ||||
-rw-r--r-- | src/bundler.zig | 79 | ||||
-rw-r--r-- | src/defines.zig | 4 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.cpp | 13 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.zig | 6 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 130 | ||||
-rw-r--r-- | src/js_ast.zig | 288 | ||||
-rw-r--r-- | src/js_parser/ast.js | 111 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 626 | ||||
-rw-r--r-- | src/linker.zig | 2 | ||||
-rw-r--r-- | src/node_fallbacks.zig | 8 | ||||
-rw-r--r-- | src/options.zig | 15 | ||||
-rw-r--r-- | src/runtime.zig | 1 |
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 { |