diff options
author | 2022-09-05 23:05:22 -0700 | |
---|---|---|
committer | 2022-09-05 23:05:22 -0700 | |
commit | 11aa17a57cc679d34e8e6f6f7aa665f565cb7305 (patch) | |
tree | ccb9a605cb4bcc42c6b2665ddbf8d04af72d94c7 /src/bun.js/javascript.zig | |
parent | d2397b60e79e4386c6a7b7a9783a6f8e379a5ae0 (diff) | |
download | bun-11aa17a57cc679d34e8e6f6f7aa665f565cb7305.tar.gz bun-11aa17a57cc679d34e8e6f6f7aa665f565cb7305.tar.zst bun-11aa17a57cc679d34e8e6f6f7aa665f565cb7305.zip |
Support async `onLoad` callbacks in `Bun.plugin`
Diffstat (limited to 'src/bun.js/javascript.zig')
-rw-r--r-- | src/bun.js/javascript.zig | 950 |
1 files changed, 564 insertions, 386 deletions
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 2dd7a9d3f..4053b89d5 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -325,6 +325,8 @@ pub const VirtualMachine = struct { timer: Bun.Timer = Bun.Timer{}, uws_event_loop: ?*uws.Loop = null, + is_printing_plugin: bool = false, + plugin_runner: ?PluginRunner = null, /// Do not access this field directly @@ -596,7 +598,7 @@ pub const VirtualMachine = struct { // } - threadlocal var source_code_printer: ?*js_printer.BufferPrinter = null; + pub threadlocal var source_code_printer: ?*js_printer.BufferPrinter = null; pub fn clearRefString(_: *anyopaque, ref_string: *JSC.RefString) void { _ = VirtualMachine.vm.ref_strings.remove(ref_string.hash); @@ -676,27 +678,8 @@ pub const VirtualMachine = struct { const shared_library_suffix = if (Environment.isMac) "dylib" else if (Environment.isLinux) "so" else ""; - const FetchFlags = enum { - transpile, - print_source, - print_source_and_clone, - - pub fn disableTranspiling(this: FetchFlags) bool { - return this != .transpile; - } - }; - - fn _fetch( - jsc_vm: *VirtualMachine, - globalObject: *JSGlobalObject, - _specifier: string, - _: string, - log: *logger.Log, - comptime flags: FetchFlags, - ) !ResolvedSource { - std.debug.assert(VirtualMachine.vm_loaded); - const disable_transpilying = comptime flags.disableTranspiling(); - if (jsc_vm.node_modules != null and strings.eqlComptime(_specifier, bun_file_import_path)) { + pub fn fetchBuiltinModule(jsc_vm: *VirtualMachine, specifier: string, log: *logger.Log, comptime disable_transpilying: bool) !?ResolvedSource { + if (jsc_vm.node_modules != null and strings.eqlComptime(specifier, bun_file_import_path)) { // We kind of need an abstraction around this. // Basically we should subclass JSC::SourceCode with: // - hash @@ -712,7 +695,7 @@ pub const VirtualMachine = struct { .source_url = ZigString.init(bun_file_import_path[1..]), .hash = 0, // TODO }; - } else if (jsc_vm.node_modules == null and strings.eqlComptime(_specifier, Runtime.Runtime.Imports.Name)) { + } else if (jsc_vm.node_modules == null and strings.eqlComptime(specifier, Runtime.Runtime.Imports.Name)) { return ResolvedSource{ .allocator = null, .source_code = ZigString.init(Runtime.Runtime.sourceContentBun()), @@ -720,7 +703,7 @@ pub const VirtualMachine = struct { .source_url = ZigString.init(Runtime.Runtime.Imports.Name), .hash = Runtime.Runtime.versionHash(), }; - } else if (HardcodedModule.Map.get(_specifier)) |hardcoded| { + } else if (HardcodedModule.Map.get(specifier)) |hardcoded| { switch (hardcoded) { // This is all complicated because the imports have to be linked and we want to run the printer on it // so it consistently handles bundled imports @@ -1011,356 +994,61 @@ pub const VirtualMachine = struct { }; }, } - } else if (_specifier.len > js_ast.Macro.namespaceWithColon.len and - strings.eqlComptimeIgnoreLen(_specifier[0..js_ast.Macro.namespaceWithColon.len], js_ast.Macro.namespaceWithColon)) + } else if (specifier.len > js_ast.Macro.namespaceWithColon.len and + strings.eqlComptimeIgnoreLen(specifier[0..js_ast.Macro.namespaceWithColon.len], js_ast.Macro.namespaceWithColon)) { if (comptime !disable_transpilying) { - if (jsc_vm.macro_entry_points.get(MacroEntryPoint.generateIDFromSpecifier(_specifier))) |entry| { + if (jsc_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), + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(specifier), .hash = 0, }; } } } - var specifier = normalizeSpecifier(_specifier); + return null; + } + + pub fn fetchWithoutOnLoadPlugins( + jsc_vm: *VirtualMachine, + _specifier: string, + log: *logger.Log, + ret: *ErrorableResolvedSource, + comptime flags: FetchFlags, + ) !ResolvedSource { + std.debug.assert(VirtualMachine.vm_loaded); + + if (try fetchBuiltinModule(jsc_vm, _specifier, log, comptime flags.disableTranspiling())) |builtin| { + return builtin; + } + + var specifier = ModuleLoader.normalizeSpecifier(jsc_vm, _specifier); var path = Fs.Path.init(specifier); - const default_loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: { + const loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: { if (strings.eqlLong(specifier, jsc_vm.main, true)) { break :brk options.Loader.js; } break :brk options.Loader.file; }; - var loader = default_loader; - var virtual_source: logger.Source = undefined; - var has_virtual_source = false; - var source_code_slice: ZigString.Slice = ZigString.Slice.empty; - defer source_code_slice.deinit(); - - if (jsc_vm.plugin_runner != null) { - const namespace = PluginRunner.extractNamespace(_specifier); - const after_namespace = if (namespace.len == 0) - specifier - else - _specifier[@minimum(namespace.len + 1, _specifier.len)..]; - - if (PluginRunner.couldBePlugin(_specifier)) { - if (globalObject.runOnLoadPlugins(ZigString.init(namespace), ZigString.init(after_namespace), .bun)) |plugin_result| { - if (plugin_result.isException(globalObject.vm()) or plugin_result.isAnyError(globalObject)) { - jsc_vm.runErrorHandler(plugin_result, null); - log.addError(null, logger.Loc.Empty, "Failed to run plugin") catch unreachable; - return error.PluginError; - } - - if (comptime Environment.allow_assert) - std.debug.assert(plugin_result.isObject()); - - if (plugin_result.get(globalObject, "loader")) |loader_value| { - if (!loader_value.isUndefinedOrNull()) { - const loader_string = loader_value.getZigString(globalObject); - if (comptime Environment.allow_assert) - std.debug.assert(loader_string.len > 0); - - if (loader_string.eqlComptime("js")) { - loader = options.Loader.js; - } else if (loader_string.eqlComptime("jsx")) { - loader = options.Loader.jsx; - } else if (loader_string.eqlComptime("tsx")) { - loader = options.Loader.tsx; - } else if (loader_string.eqlComptime("ts")) { - loader = options.Loader.ts; - } else if (loader_string.eqlComptime("json")) { - loader = options.Loader.json; - } else if (loader_string.eqlComptime("toml")) { - loader = options.Loader.toml; - } else if (loader_string.eqlComptime("object")) { - const exports_object: JSValue = @as(?JSValue, brk: { - const exports_value = plugin_result.get(globalObject, "exports") orelse break :brk null; - if (!exports_value.isObject()) { - break :brk null; - } - break :brk exports_value; - }) orelse { - log.addError(null, logger.Loc.Empty, "Expected object loader to return an \"exports\" object") catch unreachable; - return error.PluginError; - }; - return ResolvedSource{ - .allocator = null, - .source_code = ZigString{ - .ptr = @ptrCast([*]const u8, exports_object.asVoid()), - .len = 0, - }, - .specifier = ZigString.init(_specifier), - .source_url = ZigString.init(_specifier), - .hash = 0, - .tag = .object, - }; - } else { - log.addErrorFmt( - null, - logger.Loc.Empty, - jsc_vm.allocator, - "Expected onLoad() plugin \"loader\" to be one of \"js\", \"jsx\", \"tsx\", \"ts\", \"json\", or \"toml\" but received \"{any}\"", - .{loader_string}, - ) catch unreachable; - return error.PluginError; - } - } - } - - if (plugin_result.get(globalObject, "contents")) |code| { - if (code.asArrayBuffer(globalObject)) |array_buffer| { - virtual_source = .{ - .path = path, - .key_path = path, - .contents = array_buffer.byteSlice(), - }; - has_virtual_source = true; - } else if (code.isString()) { - source_code_slice = code.toSlice(globalObject, jsc_vm.allocator); - if (!source_code_slice.allocated) { - if (!strings.isAllASCII(source_code_slice.slice())) { - var allocated = try strings.allocateLatin1IntoUTF8(jsc_vm.allocator, []const u8, source_code_slice.slice()); - source_code_slice.ptr = allocated.ptr; - source_code_slice.len = @truncate(u32, allocated.len); - source_code_slice.allocated = true; - source_code_slice.allocator = jsc_vm.allocator; - } - } - virtual_source = .{ - .path = path, - .key_path = path, - .contents = source_code_slice.slice(), - }; - has_virtual_source = true; - } - } - - if (!has_virtual_source) { - log.addError(null, logger.Loc.Empty, "Expected onLoad() plugin to return \"contents\" as a string or ArrayBufferView") catch unreachable; - return error.PluginError; - } - } else { - std.debug.assert(std.fs.path.isAbsolute(specifier)); // if this crashes, it means the resolver was skipped. - } - } - } - - const transpiled_result = transpileSourceCode( + return try ModuleLoader.transpileSourceCode( jsc_vm, specifier, path, loader, log, - if (has_virtual_source) &virtual_source else null, + null, + ret, + VirtualMachine.source_code_printer.?, flags, ); - return transpiled_result; } - fn transpileSourceCode( - jsc_vm: *VirtualMachine, - specifier: string, - path: Fs.Path, - loader: options.Loader, - log: *logger.Log, - virtual_source: ?*const logger.Source, - comptime flags: FetchFlags, - ) !ResolvedSource { - const disable_transpilying = comptime flags.disableTranspiling(); - - switch (loader) { - .js, .jsx, .ts, .tsx, .json, .toml => { - jsc_vm.transpiled_count += 1; - jsc_vm.bundler.resetStore(); - const hash = http.Watcher.getHash(path.text); - - var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; - - var fd: ?StoredFileDescriptorType = null; - var package_json: ?*PackageJSON = null; - - if (jsc_vm.watcher) |watcher| { - if (watcher.indexOf(hash)) |index| { - const _fd = watcher.watchlist.items(.fd)[index]; - fd = if (_fd > 0) _fd else null; - package_json = watcher.watchlist.items(.package_json)[index]; - } - } - - var old = jsc_vm.bundler.log; - jsc_vm.bundler.log = log; - jsc_vm.bundler.linker.log = log; - jsc_vm.bundler.resolver.log = log; - - defer { - jsc_vm.bundler.log = old; - jsc_vm.bundler.linker.log = old; - jsc_vm.bundler.resolver.log = old; - } - - // this should be a cheap lookup because 24 bytes == 8 * 3 so it's read 3 machine words - const is_node_override = specifier.len > "/bun-vfs/node_modules/".len and strings.eqlComptimeIgnoreLen(specifier[0.."/bun-vfs/node_modules/".len], "/bun-vfs/node_modules/"); - - const macro_remappings = if (jsc_vm.macro_mode or !jsc_vm.has_any_macro_remappings or is_node_override) - MacroRemap{} - else - jsc_vm.bundler.options.macro_remap; - - var fallback_source: logger.Source = undefined; - - var parse_options = Bundler.ParseOptions{ - .allocator = allocator, - .path = path, - .loader = loader, - .dirname_fd = 0, - .file_descriptor = fd, - .file_hash = hash, - .macro_remappings = macro_remappings, - .jsx = jsc_vm.bundler.options.jsx, - .virtual_source = virtual_source, - }; - - if (is_node_override) { - if (NodeFallbackModules.contentsFromPath(specifier)) |code| { - const fallback_path = Fs.Path.initWithNamespace(specifier, "node"); - fallback_source = logger.Source{ .path = fallback_path, .contents = code, .key_path = fallback_path }; - parse_options.virtual_source = &fallback_source; - } - } - - var parse_result = jsc_vm.bundler.parseMaybeReturnFileOnly( - parse_options, - null, - disable_transpilying, - ) orelse { - return error.ParseError; - }; - - if (comptime disable_transpilying) { - return ResolvedSource{ - .allocator = null, - .source_code = switch (comptime flags) { - .print_source_and_clone => ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), - .print_source => ZigString.init(parse_result.source.contents), - else => unreachable, - }, - .specifier = ZigString.init(specifier), - .source_url = ZigString.init(path.text), - .hash = 0, - }; - } - - const start_count = jsc_vm.bundler.linker.import_counter; - // We _must_ link because: - // - node_modules bundle won't be properly - try jsc_vm.bundler.linker.link( - path, - &parse_result, - jsc_vm.origin, - .absolute_path, - false, - true, - ); - - if (!jsc_vm.macro_mode) - jsc_vm.resolved_count += jsc_vm.bundler.linker.import_counter - start_count; - jsc_vm.bundler.linker.import_counter = 0; - - var printer = source_code_printer.?.*; - printer.ctx.reset(); - - const written = brk: { - defer source_code_printer.?.* = printer; - break :brk try jsc_vm.bundler.printWithSourceMap( - parse_result, - @TypeOf(&printer), - &printer, - .esm_ascii, - SavedSourceMap.SourceMapHandler.init(&jsc_vm.source_mappings), - ); - }; - - if (written == 0) { - return error.PrintingErrorWriteFailed; - } - - if (jsc_vm.has_loaded) { - return jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null); - } - - return ResolvedSource{ - .allocator = null, - .source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())), - .specifier = ZigString.init(specifier), - .source_url = ZigString.init(path.text), - // // TODO: change hash to a bitfield - // .hash = 1, - - // having JSC own the memory causes crashes - .hash = 0, - }; - }, - // provideFetch() should be called - .napi => unreachable, - // .wasm => { - // jsc_vm.transpiled_count += 1; - // var fd: ?StoredFileDescriptorType = null; - - // var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; - - // const hash = http.Watcher.getHash(path.text); - // if (jsc_vm.watcher) |watcher| { - // if (watcher.indexOf(hash)) |index| { - // const _fd = watcher.watchlist.items(.fd)[index]; - // fd = if (_fd > 0) _fd else null; - // } - // } - - // var parse_options = Bundler.ParseOptions{ - // .allocator = allocator, - // .path = path, - // .loader = loader, - // .dirname_fd = 0, - // .file_descriptor = fd, - // .file_hash = hash, - // .macro_remappings = MacroRemap{}, - // .jsx = jsc_vm.bundler.options.jsx, - // }; - - // var parse_result = jsc_vm.bundler.parse( - // parse_options, - // null, - // ) orelse { - // return error.ParseError; - // }; - - // return ResolvedSource{ - // .allocator = if (jsc_vm.has_loaded) &jsc_vm.allocator else null, - // .source_code = ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), - // .specifier = ZigString.init(specifier), - // .source_url = ZigString.init(path.text), - // .hash = 0, - // .tag = ResolvedSource.Tag.wasm, - // }; - // }, - else => { - return ResolvedSource{ - .allocator = &vm.allocator, - .source_code = ZigString.init(try strings.quotedAlloc(jsc_vm.allocator, path.pretty)), - .specifier = ZigString.init(path.text), - .source_url = ZigString.init(path.text), - .hash = 0, - }; - }, - } - } pub const ResolveFunctionResult = struct { result: ?Resolver.Result, path: string, @@ -1546,42 +1234,6 @@ pub const VirtualMachine = struct { res.* = ErrorableZigString.ok(ZigString.init(result.path)); } - pub fn normalizeSpecifier(slice_: string) string { - var vm_ = VirtualMachine.vm; - - var slice = slice_; - if (slice.len == 0) return slice; - var was_http = false; - if (strings.hasPrefixComptime(slice, "https://")) { - slice = slice["https://".len..]; - was_http = true; - } else if (strings.hasPrefixComptime(slice, "http://")) { - slice = slice["http://".len..]; - was_http = true; - } - - if (strings.hasPrefix(slice, vm_.origin.host)) { - slice = slice[vm_.origin.host.len..]; - } else if (was_http) { - if (strings.indexOfChar(slice, '/')) |i| { - slice = slice[i..]; - } - } - - if (vm_.origin.path.len > 1) { - if (strings.hasPrefix(slice, vm_.origin.path)) { - slice = slice[vm_.origin.path.len..]; - } - } - - if (vm_.bundler.options.routes.asset_prefix_path.len > 0) { - if (strings.hasPrefix(slice, vm_.bundler.options.routes.asset_prefix_path)) { - slice = slice[vm_.bundler.options.routes.asset_prefix_path.len..]; - } - } - - return slice; - } // // This double prints // pub fn promiseRejectionTracker(global: *JSGlobalObject, promise: *JSPromise, _: JSPromiseRejectionOperation) callconv(.C) JSValue { @@ -1605,12 +1257,12 @@ pub const VirtualMachine = struct { global.bunVM(); const result = if (!jsc_vm.bundler.options.disable_transpilation) - @call(.{ .modifier = .always_inline }, _fetch, .{ jsc_vm, global, spec, source.slice(), &log, .transpile }) catch |err| { + @call(.{ .modifier = .always_inline }, fetchWithoutOnLoadPlugins, .{ jsc_vm, spec, &log, ret, .transpile }) catch |err| { processFetchLog(global, specifier, source, &log, ret, err); return; } else - _fetch(jsc_vm, global, spec, source.slice(), &log, .print_source_and_clone) catch |err| { + fetchWithoutOnLoadPlugins(jsc_vm, spec, &log, ret, .print_source_and_clone) catch |err| { processFetchLog(global, specifier, source, &log, ret, err); return; }; @@ -1657,7 +1309,7 @@ pub const VirtualMachine = struct { ret.success = true; } - fn processFetchLog(globalThis: *JSGlobalObject, specifier: ZigString, referrer: ZigString, log: *logger.Log, ret: *ErrorableResolvedSource, err: anyerror) void { + pub fn processFetchLog(globalThis: *JSGlobalObject, specifier: ZigString, referrer: ZigString, log: *logger.Log, ret: *ErrorableResolvedSource, err: anyerror) void { switch (log.msgs.items.len) { 0 => { const msg = logger.Msg{ @@ -2074,7 +1726,8 @@ pub const VirtualMachine = struct { @maximum(top.position.column_start, 0), )) |mapping| { var log = logger.Log.init(default_allocator); - var original_source = _fetch(this, this.global, top.source_url.slice(), "", &log, .print_source) catch return; + var errorable: ErrorableResolvedSource = undefined; + var original_source = fetchWithoutOnLoadPlugins(this, top.source_url.slice(), &log, &errorable, .print_source) catch return; const code = original_source.source_code.slice(); top.position.line = mapping.original.lines; top.position.line_start = mapping.original.lines; @@ -3091,3 +2744,528 @@ inline fn jsSyntheticModule(comptime name: ResolvedSource.Tag) ResolvedSource { .tag = name, }; } + +fn dumpSource(specifier: string, printer: anytype) !void { + const BunDebugHolder = struct { + pub var dir: ?std.fs.Dir = null; + }; + if (BunDebugHolder.dir == null) { + BunDebugHolder.dir = try std.fs.cwd().makeOpenPath("/tmp/bun-debug-src/", .{ .iterate = true }); + } + + if (std.fs.path.dirname(specifier)) |dir_path| { + var parent = try BunDebugHolder.dir.?.makeOpenPath(dir_path[1..], .{ .iterate = true }); + defer parent.close(); + try parent.writeFile(std.fs.path.basename(specifier), printer.ctx.getWritten()); + } else { + try BunDebugHolder.dir.?.writeFile(std.fs.path.basename(specifier), printer.ctx.getWritten()); + } +} + +pub const ModuleLoader = struct { + pub fn transpileSourceCode( + jsc_vm: *VirtualMachine, + specifier: string, + path: Fs.Path, + loader: options.Loader, + log: *logger.Log, + virtual_source: ?*const logger.Source, + ret: *ErrorableResolvedSource, + source_code_printer: *js_printer.BufferPrinter, + comptime flags: FetchFlags, + ) !ResolvedSource { + const disable_transpilying = comptime flags.disableTranspiling(); + + switch (loader) { + .js, .jsx, .ts, .tsx, .json, .toml => { + jsc_vm.transpiled_count += 1; + jsc_vm.bundler.resetStore(); + const hash = http.Watcher.getHash(path.text); + + var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; + + var fd: ?StoredFileDescriptorType = null; + var package_json: ?*PackageJSON = null; + + if (jsc_vm.watcher) |watcher| { + if (watcher.indexOf(hash)) |index| { + const _fd = watcher.watchlist.items(.fd)[index]; + fd = if (_fd > 0) _fd else null; + package_json = watcher.watchlist.items(.package_json)[index]; + } + } + + var old = jsc_vm.bundler.log; + jsc_vm.bundler.log = log; + jsc_vm.bundler.linker.log = log; + jsc_vm.bundler.resolver.log = log; + + defer { + jsc_vm.bundler.log = old; + jsc_vm.bundler.linker.log = old; + jsc_vm.bundler.resolver.log = old; + } + + // this should be a cheap lookup because 24 bytes == 8 * 3 so it's read 3 machine words + const is_node_override = specifier.len > "/bun-vfs/node_modules/".len and strings.eqlComptimeIgnoreLen(specifier[0.."/bun-vfs/node_modules/".len], "/bun-vfs/node_modules/"); + + const macro_remappings = if (jsc_vm.macro_mode or !jsc_vm.has_any_macro_remappings or is_node_override) + MacroRemap{} + else + jsc_vm.bundler.options.macro_remap; + + var fallback_source: logger.Source = undefined; + + var parse_options = Bundler.ParseOptions{ + .allocator = allocator, + .path = path, + .loader = loader, + .dirname_fd = 0, + .file_descriptor = fd, + .file_hash = hash, + .macro_remappings = macro_remappings, + .jsx = jsc_vm.bundler.options.jsx, + .virtual_source = virtual_source, + .hoist_bun_plugin = true, + }; + + if (is_node_override) { + if (NodeFallbackModules.contentsFromPath(specifier)) |code| { + const fallback_path = Fs.Path.initWithNamespace(specifier, "node"); + fallback_source = logger.Source{ .path = fallback_path, .contents = code, .key_path = fallback_path }; + parse_options.virtual_source = &fallback_source; + } + } + + var parse_result = jsc_vm.bundler.parseMaybeReturnFileOnly( + parse_options, + null, + disable_transpilying, + ) orelse { + return error.ParseError; + }; + + if (comptime disable_transpilying) { + return ResolvedSource{ + .allocator = null, + .source_code = switch (comptime flags) { + .print_source_and_clone => ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), + .print_source => ZigString.init(parse_result.source.contents), + else => unreachable, + }, + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(path.text), + .hash = 0, + }; + } + + const has_bun_plugin = parse_result.ast.bun_plugin.hoisted_stmts.items.len > 0; + + if (has_bun_plugin) { + try ModuleLoader.runBunPlugin(jsc_vm, source_code_printer, &parse_result, ret); + } + + var printer = source_code_printer.*; + printer.ctx.reset(); + + const start_count = jsc_vm.bundler.linker.import_counter; + // We _must_ link because: + // - node_modules bundle won't be properly + try jsc_vm.bundler.linker.link( + path, + &parse_result, + jsc_vm.origin, + .absolute_path, + false, + true, + ); + + if (!jsc_vm.macro_mode) + jsc_vm.resolved_count += jsc_vm.bundler.linker.import_counter - start_count; + jsc_vm.bundler.linker.import_counter = 0; + + const written = brk: { + defer source_code_printer.* = printer; + break :brk try jsc_vm.bundler.printWithSourceMap( + parse_result, + @TypeOf(&printer), + &printer, + .esm_ascii, + SavedSourceMap.SourceMapHandler.init(&jsc_vm.source_mappings), + ); + }; + + if (written == 0) { + // if it's an empty file but there were plugins + // we don't want it to break if you try to import from it + if (has_bun_plugin) { + return ResolvedSource{ + .allocator = null, + .source_code = ZigString.init("// auto-generated plugin stub\nexport default undefined\n"), + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(path.text), + // // TODO: change hash to a bitfield + // .hash = 1, + + // having JSC own the memory causes crashes + .hash = 0, + }; + } + return error.PrintingErrorWriteFailed; + } + + if (jsc_vm.has_loaded) { + return jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null); + } + + if (comptime Environment.dump_source) { + try dumpSource(specifier, &printer); + } + + return ResolvedSource{ + .allocator = null, + .source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())), + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(path.text), + // // TODO: change hash to a bitfield + // .hash = 1, + + // having JSC own the memory causes crashes + .hash = 0, + }; + }, + // provideFetch() should be called + .napi => unreachable, + // .wasm => { + // jsc_vm.transpiled_count += 1; + // var fd: ?StoredFileDescriptorType = null; + + // var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; + + // const hash = http.Watcher.getHash(path.text); + // if (jsc_vm.watcher) |watcher| { + // if (watcher.indexOf(hash)) |index| { + // const _fd = watcher.watchlist.items(.fd)[index]; + // fd = if (_fd > 0) _fd else null; + // } + // } + + // var parse_options = Bundler.ParseOptions{ + // .allocator = allocator, + // .path = path, + // .loader = loader, + // .dirname_fd = 0, + // .file_descriptor = fd, + // .file_hash = hash, + // .macro_remappings = MacroRemap{}, + // .jsx = jsc_vm.bundler.options.jsx, + // }; + + // var parse_result = jsc_vm.bundler.parse( + // parse_options, + // null, + // ) orelse { + // return error.ParseError; + // }; + + // return ResolvedSource{ + // .allocator = if (jsc_vm.has_loaded) &jsc_vm.allocator else null, + // .source_code = ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), + // .specifier = ZigString.init(specifier), + // .source_url = ZigString.init(path.text), + // .hash = 0, + // .tag = ResolvedSource.Tag.wasm, + // }; + // }, + else => { + return ResolvedSource{ + .allocator = &jsc_vm.allocator, + .source_code = ZigString.init(try strings.quotedAlloc(jsc_vm.allocator, path.pretty)), + .specifier = ZigString.init(path.text), + .source_url = ZigString.init(path.text), + .hash = 0, + }; + }, + } + } + + pub fn runBunPlugin( + jsc_vm: *VirtualMachine, + source_code_printer: *js_printer.BufferPrinter, + parse_result: *ParseResult, + ret: *ErrorableResolvedSource, + ) !void { + var printer = source_code_printer.*; + printer.ctx.reset(); + + defer printer.ctx.reset(); + // If we start transpiling in the middle of an existing transpilation session + // we will hit undefined memory bugs + // unless we disable resetting the store until we are done transpiling + const prev_disable_reset = js_ast.Stmt.Data.Store.disable_reset; + js_ast.Stmt.Data.Store.disable_reset = true; + js_ast.Expr.Data.Store.disable_reset = true; + + // flip the source code we use + // unless we're already transpiling a plugin + // that case could happen when + const was_printing_plugin = jsc_vm.is_printing_plugin; + const prev = jsc_vm.bundler.resolver.caches.fs.use_alternate_source_cache; + jsc_vm.is_printing_plugin = true; + defer { + js_ast.Stmt.Data.Store.disable_reset = prev_disable_reset; + js_ast.Expr.Data.Store.disable_reset = prev_disable_reset; + if (!was_printing_plugin) jsc_vm.bundler.resolver.caches.fs.use_alternate_source_cache = prev; + jsc_vm.is_printing_plugin = was_printing_plugin; + } + // we flip use_alternate_source_cache + if (!was_printing_plugin) jsc_vm.bundler.resolver.caches.fs.use_alternate_source_cache = !prev; + + // this is a bad idea, but it should work for now. + const original_name = parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name; + parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name = "globalThis.Bun.plugin"; + defer { + parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name = original_name; + } + const hoisted_stmts = parse_result.ast.bun_plugin.hoisted_stmts.items; + + var parts = [1]js_ast.Part{ + js_ast.Part{ + .stmts = hoisted_stmts, + }, + }; + var ast_copy = parse_result.ast; + ast_copy.parts = &parts; + ast_copy.prepend_part = null; + var temporary_source = parse_result.source; + var source_name = try std.fmt.allocPrint(jsc_vm.allocator, "{s}.plugin.{s}", .{ temporary_source.path.text, temporary_source.path.name.ext[1..] }); + temporary_source.path = Fs.Path.init(source_name); + + _ = brk: { + defer source_code_printer.* = printer; + break :brk try jsc_vm.bundler.printWithSourceMapMaybe( + ast_copy, + &temporary_source, + @TypeOf(&printer), + &printer, + .esm_ascii, + true, + SavedSourceMap.SourceMapHandler.init(&jsc_vm.source_mappings), + ); + }; + const wrote = printer.ctx.getWritten(); + + if (wrote.len > 0) { + if (comptime Environment.dump_source) + try dumpSource(temporary_source.path.text, &printer); + + var exception = [1]JSC.JSValue{JSC.JSValue.zero}; + _ = JSC.JSModuleLoader.evaluate( + jsc_vm.global, + wrote.ptr, + wrote.len, + temporary_source.path.text.ptr, + temporary_source.path.text.len, + JSC.JSValue.jsUndefined(), + &exception, + ); + if (!exception[0].isEmpty()) { + ret.* = JSC.ErrorableResolvedSource.err( + error.JSErrorObject, + exception[0].asVoid(), + ); + return error.PluginError; + } + } + } + pub fn normalizeSpecifier(jsc_vm: *VirtualMachine, slice_: string) string { + var slice = slice_; + if (slice.len == 0) return slice; + var was_http = false; + if (strings.hasPrefixComptime(slice, "https://")) { + slice = slice["https://".len..]; + was_http = true; + } else if (strings.hasPrefixComptime(slice, "http://")) { + slice = slice["http://".len..]; + was_http = true; + } + + if (strings.hasPrefix(slice, jsc_vm.origin.host)) { + slice = slice[jsc_vm.origin.host.len..]; + } else if (was_http) { + if (strings.indexOfChar(slice, '/')) |i| { + slice = slice[i..]; + } + } + + if (jsc_vm.origin.path.len > 1) { + if (strings.hasPrefix(slice, jsc_vm.origin.path)) { + slice = slice[jsc_vm.origin.path.len..]; + } + } + + if (jsc_vm.bundler.options.routes.asset_prefix_path.len > 0) { + if (strings.hasPrefix(slice, jsc_vm.bundler.options.routes.asset_prefix_path)) { + slice = slice[jsc_vm.bundler.options.routes.asset_prefix_path.len..]; + } + } + + return slice; + } + + pub export fn Bun__fetchBuiltinModule( + jsc_vm: *VirtualMachine, + globalObject: *JSC.JSGlobalObject, + specifier: *ZigString, + referrer: *ZigString, + ret: *ErrorableResolvedSource, + ) bool { + JSC.markBinding(); + var log = logger.Log.init(jsc_vm.bundler.allocator); + defer log.deinit(); + if (jsc_vm.fetchBuiltinModule(specifier.slice(), &log, true) catch |err| { + VirtualMachine.processFetchLog(globalObject, specifier.*, referrer.*, &log, ret, err); + return true; + }) |builtin| { + ret.* = ErrorableResolvedSource.ok(builtin); + return true; + } else { + return false; + } + } + + pub export fn Bun__transpileFile( + jsc_vm: *VirtualMachine, + globalObject: *JSC.JSGlobalObject, + specifier_ptr: *ZigString, + referrer: *ZigString, + ret: *ErrorableResolvedSource, + ) bool { + JSC.markBinding(); + var log = logger.Log.init(jsc_vm.bundler.allocator); + defer log.deinit(); + var _specifier = specifier_ptr.toSlice(jsc_vm.allocator); + defer _specifier.deinit(); + var specifier = normalizeSpecifier(jsc_vm, _specifier.slice()); + const path = Fs.Path.init(specifier); + const loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: { + if (strings.eqlLong(specifier, jsc_vm.main, true)) { + break :brk options.Loader.js; + } + + break :brk options.Loader.file; + }; + ret.* = ErrorableResolvedSource.ok( + ModuleLoader.transpileSourceCode( + jsc_vm, + specifier, + path, + loader, + &log, + null, + ret, + VirtualMachine.source_code_printer.?, + FetchFlags.transpile, + ) catch |err| { + if (err == error.PluginERror) { + return true; + } + VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer.*, &log, ret, err); + return true; + }, + ); + return true; + } + + export fn Bun__runVirtualModule(globalObject: *JSC.JSGlobalObject, specifier_ptr: *ZigString) JSValue { + JSC.markBinding(); + if (globalObject.bunVM().plugin_runner == null) return JSValue.zero; + + const specifier = specifier_ptr.slice(); + + if (!PluginRunner.couldBePlugin(specifier)) { + return JSValue.zero; + } + + const namespace = PluginRunner.extractNamespace(specifier); + const after_namespace = if (namespace.len == 0) + specifier + else + specifier[@minimum(namespace.len + 1, specifier.len)..]; + + return globalObject.runOnLoadPlugins(ZigString.init(namespace), ZigString.init(after_namespace), .bun) orelse return JSValue.zero; + } + + export fn Bun__transpileVirtualModule( + globalObject: *JSC.JSGlobalObject, + specifier_ptr: *ZigString, + referrer_ptr: *ZigString, + source_code: *ZigString, + loader_: Api.Loader, + ret: *ErrorableResolvedSource, + ) bool { + JSC.markBinding(); + const jsc_vm = globalObject.bunVM(); + std.debug.assert(jsc_vm.plugin_runner != null); + + var specifier_slice = specifier_ptr.toSlice(jsc_vm.allocator); + const specifier = specifier_slice.slice(); + defer specifier_slice.deinit(); + var source_code_slice = source_code.toSlice(jsc_vm.allocator); + defer source_code_slice.deinit(); + + var virtual_source = logger.Source.initPathString(specifier, source_code_slice.slice()); + var log = logger.Log.init(jsc_vm.allocator); + const path = Fs.Path.init(specifier); + + const loader = if (loader_ != ._none) + options.Loader.fromString(@tagName(loader_)).? + else + jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: { + if (strings.eqlLong(specifier, jsc_vm.main, true)) { + break :brk options.Loader.js; + } + + break :brk options.Loader.file; + }; + + defer log.deinit(); + ret.* = ErrorableResolvedSource.ok( + ModuleLoader.transpileSourceCode( + jsc_vm, + specifier, + path, + options.Loader.fromString(@tagName(loader)).?, + &log, + &virtual_source, + ret, + VirtualMachine.source_code_printer.?, + FetchFlags.transpile, + ) catch |err| { + if (err == error.PluginError) { + return true; + } + VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer_ptr.*, &log, ret, err); + return true; + }, + ); + return true; + } + + comptime { + _ = Bun__transpileVirtualModule; + _ = Bun__runVirtualModule; + _ = Bun__transpileFile; + _ = Bun__fetchBuiltinModule; + } +}; + +const FetchFlags = enum { + transpile, + print_source, + print_source_and_clone, + + pub fn disableTranspiling(this: FetchFlags) bool { + return this != .transpile; + } +}; |