mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
- Move napi files from bindings/ to compat/napi/ - Move module loader files from bindings/ to module/ - InternalModuleRegistry, JSModuleLoader, JSCommonJSModule, etc. - Update all import paths and cmake configurations Part of source code reorganization to make code location "guessable". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1393 lines
55 KiB
Zig
1393 lines
55 KiB
Zig
const ModuleLoader = @This();
|
|
|
|
pub const node_fallbacks = @import("../node_fallbacks.zig");
|
|
pub const AsyncModule = @import("./AsyncModule.zig").AsyncModule;
|
|
pub const RuntimeTranspilerStore = @import("./RuntimeTranspilerStore.zig").RuntimeTranspilerStore;
|
|
pub const HardcodedModule = @import("./HardcodedModule.zig").HardcodedModule;
|
|
|
|
transpile_source_code_arena: ?*bun.ArenaAllocator = null,
|
|
eval_source: ?*logger.Source = null,
|
|
|
|
comptime {
|
|
_ = Bun__transpileVirtualModule;
|
|
_ = Bun__runVirtualModule;
|
|
_ = Bun__transpileFile;
|
|
_ = Bun__fetchBuiltinModule;
|
|
_ = Bun__getDefaultLoader;
|
|
}
|
|
|
|
pub var is_allowed_to_use_internal_testing_apis = false;
|
|
|
|
/// This must be called after calling transpileSourceCode
|
|
pub fn resetArena(this: *ModuleLoader, jsc_vm: *VirtualMachine) void {
|
|
bun.assert(&jsc_vm.module_loader == this);
|
|
if (this.transpile_source_code_arena) |arena| {
|
|
if (jsc_vm.smol) {
|
|
_ = arena.reset(.free_all);
|
|
} else {
|
|
_ = arena.reset(.{ .retain_with_limit = 8 * 1024 * 1024 });
|
|
}
|
|
}
|
|
}
|
|
|
|
fn resolveEmbeddedNodeFileViaMemfd(file: *bun.StandaloneModuleGraph.File, path_buffer: *bun.PathBuffer, fd: *i32) ![]const u8 {
|
|
var label_buf: [128]u8 = undefined;
|
|
const count = struct {
|
|
pub var counter = std.atomic.Value(u32).init(0);
|
|
pub fn get() u32 {
|
|
return counter.fetchAdd(1, .seq_cst);
|
|
}
|
|
}.get();
|
|
const label = std.fmt.bufPrintZ(&label_buf, "node-addon-{d}", .{count}) catch "";
|
|
const memfd = try bun.sys.memfd_create(label, .executable).unwrap();
|
|
errdefer memfd.close();
|
|
|
|
fd.* = @intCast(memfd.cast());
|
|
errdefer fd.* = -1;
|
|
|
|
try bun.sys.ftruncate(memfd, @intCast(file.contents.len)).unwrap();
|
|
try bun.sys.File.writeAll(.{ .handle = memfd }, file.contents).unwrap();
|
|
|
|
return try std.fmt.bufPrint(path_buffer, "/proc/self/fd/{d}", .{memfd.cast()});
|
|
}
|
|
|
|
pub fn resolveEmbeddedFile(vm: *VirtualMachine, path_buf: *bun.PathBuffer, linux_memfd: *i32, input_path: []const u8, extname: []const u8) ?[]const u8 {
|
|
if (input_path.len == 0) return null;
|
|
var graph = vm.standalone_module_graph orelse return null;
|
|
const file = graph.find(input_path) orelse return null;
|
|
|
|
if (comptime Environment.isLinux) {
|
|
// Best-effort: use memfd to avoid hitting the disk
|
|
if (resolveEmbeddedNodeFileViaMemfd(file, path_buf, linux_memfd)) |path| {
|
|
return path;
|
|
} else |_| {
|
|
// fall back to temp file
|
|
}
|
|
}
|
|
|
|
// atomically write to a tmpfile and then move it to the final destination
|
|
const tmpname_buf = bun.path_buffer_pool.get();
|
|
defer bun.path_buffer_pool.put(tmpname_buf);
|
|
const tmpfilename = bun.fs.FileSystem.tmpname(extname, tmpname_buf, bun.hash(file.name)) catch return null;
|
|
const tmpdir: bun.FD = .fromStdDir(bun.fs.FileSystem.instance.tmpdir() catch return null);
|
|
|
|
// First we open the tmpfile, to avoid any other work in the event of failure.
|
|
const tmpfile = bun.Tmpfile.create(tmpdir, tmpfilename).unwrap() catch return null;
|
|
defer tmpfile.fd.close();
|
|
|
|
switch (bun.api.node.fs.NodeFS.writeFileWithPathBuffer(
|
|
tmpname_buf, // not used
|
|
|
|
.{
|
|
.data = .{
|
|
.encoded_slice = ZigString.Slice.fromUTF8NeverFree(file.contents),
|
|
},
|
|
.dirfd = tmpdir,
|
|
.file = .{ .fd = tmpfile.fd },
|
|
.encoding = .buffer,
|
|
},
|
|
)) {
|
|
.err => {
|
|
return null;
|
|
},
|
|
else => {},
|
|
}
|
|
return bun.path.joinAbsStringBuf(bun.fs.FileSystem.instance.fs.tmpdirPath(), path_buf, &[_]string{tmpfilename}, .auto);
|
|
}
|
|
|
|
pub export fn Bun__getDefaultLoader(global: *JSGlobalObject, str: *const bun.String) api.Loader {
|
|
var jsc_vm = global.bunVM();
|
|
const filename = str.toUTF8(jsc_vm.allocator);
|
|
defer filename.deinit();
|
|
const loader = jsc_vm.transpiler.options.loader(Fs.PathName.init(filename.slice()).ext).toAPI();
|
|
if (loader == .file) {
|
|
return api.Loader.js;
|
|
}
|
|
|
|
return loader;
|
|
}
|
|
|
|
pub fn transpileSourceCode(
|
|
jsc_vm: *VirtualMachine,
|
|
specifier: string,
|
|
referrer: string,
|
|
input_specifier: String,
|
|
path: Fs.Path,
|
|
loader: options.Loader,
|
|
module_type: options.ModuleType,
|
|
log: *logger.Log,
|
|
virtual_source: ?*const logger.Source,
|
|
promise_ptr: ?*?*jsc.JSInternalPromise,
|
|
source_code_printer: *js_printer.BufferPrinter,
|
|
globalObject: ?*JSGlobalObject,
|
|
comptime flags: FetchFlags,
|
|
) !ResolvedSource {
|
|
const disable_transpilying = comptime flags.disableTranspiling();
|
|
|
|
if (comptime disable_transpilying) {
|
|
if (!(loader.isJavaScriptLike() or loader == .toml or loader == .yaml or loader == .text or loader == .json or loader == .jsonc)) {
|
|
// Don't print "export default <file path>"
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.empty,
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
};
|
|
}
|
|
}
|
|
|
|
switch (loader) {
|
|
.js, .jsx, .ts, .tsx, .json, .jsonc, .toml, .yaml, .text => {
|
|
// Ensure that if there was an ASTMemoryAllocator in use, it's not used anymore.
|
|
var ast_scope = js_ast.ASTMemoryAllocator.Scope{};
|
|
ast_scope.enter();
|
|
defer ast_scope.exit();
|
|
|
|
jsc_vm.transpiled_count += 1;
|
|
jsc_vm.transpiler.resetStore();
|
|
const hash = bun.Watcher.getHash(path.text);
|
|
const is_main = jsc_vm.main.len == path.text.len and
|
|
jsc_vm.main_hash == hash and
|
|
strings.eqlLong(jsc_vm.main, path.text, false);
|
|
|
|
var arena_: ?*bun.ArenaAllocator = brk: {
|
|
// Attempt to reuse the Arena from the parser when we can
|
|
// This code is potentially re-entrant, so only one Arena can be reused at a time
|
|
// That's why we have to check if the Arena is null
|
|
//
|
|
// Using an Arena here is a significant memory optimization when loading many files
|
|
if (jsc_vm.module_loader.transpile_source_code_arena) |shared| {
|
|
jsc_vm.module_loader.transpile_source_code_arena = null;
|
|
break :brk shared;
|
|
}
|
|
|
|
// we must allocate the arena so that the pointer it points to is always valid.
|
|
const arena = try jsc_vm.allocator.create(bun.ArenaAllocator);
|
|
arena.* = bun.ArenaAllocator.init(bun.default_allocator);
|
|
break :brk arena;
|
|
};
|
|
|
|
var give_back_arena = true;
|
|
defer {
|
|
if (give_back_arena) {
|
|
if (jsc_vm.module_loader.transpile_source_code_arena == null) {
|
|
// when .print_source is used
|
|
// caller is responsible for freeing the arena
|
|
if (flags != .print_source) {
|
|
if (jsc_vm.smol) {
|
|
_ = arena_.?.reset(.free_all);
|
|
} else {
|
|
_ = arena_.?.reset(.{ .retain_with_limit = 8 * 1024 * 1024 });
|
|
}
|
|
}
|
|
|
|
jsc_vm.module_loader.transpile_source_code_arena = arena_;
|
|
} else {
|
|
arena_.?.deinit();
|
|
jsc_vm.allocator.destroy(arena_.?);
|
|
}
|
|
}
|
|
}
|
|
|
|
var arena = arena_.?;
|
|
const allocator = arena.allocator();
|
|
|
|
var fd: ?StoredFileDescriptorType = null;
|
|
var package_json: ?*PackageJSON = null;
|
|
|
|
if (jsc_vm.bun_watcher.indexOf(hash)) |index| {
|
|
fd = jsc_vm.bun_watcher.watchlist().items(.fd)[index].unwrapValid();
|
|
package_json = jsc_vm.bun_watcher.watchlist().items(.package_json)[index];
|
|
}
|
|
|
|
var cache = jsc.RuntimeTranspilerCache{
|
|
.output_code_allocator = allocator,
|
|
.sourcemap_allocator = bun.default_allocator,
|
|
};
|
|
|
|
const old = jsc_vm.transpiler.log;
|
|
jsc_vm.transpiler.log = log;
|
|
jsc_vm.transpiler.linker.log = log;
|
|
jsc_vm.transpiler.resolver.log = log;
|
|
if (jsc_vm.transpiler.resolver.package_manager) |pm| {
|
|
pm.log = log;
|
|
}
|
|
|
|
defer {
|
|
jsc_vm.transpiler.log = old;
|
|
jsc_vm.transpiler.linker.log = old;
|
|
jsc_vm.transpiler.resolver.log = old;
|
|
if (jsc_vm.transpiler.resolver.package_manager) |pm| {
|
|
pm.log = old;
|
|
}
|
|
}
|
|
|
|
// this should be a cheap lookup because 24 bytes == 8 * 3 so it's read 3 machine words
|
|
const is_node_override = strings.hasPrefixComptime(specifier, node_fallbacks.import_path);
|
|
|
|
const macro_remappings = if (jsc_vm.macro_mode or !jsc_vm.has_any_macro_remappings or is_node_override)
|
|
MacroRemap{}
|
|
else
|
|
jsc_vm.transpiler.options.macro_remap;
|
|
|
|
var fallback_source: logger.Source = undefined;
|
|
|
|
// Usually, we want to close the input file automatically.
|
|
//
|
|
// If we're re-using the file descriptor from the fs watcher
|
|
// Do not close it because that will break the kqueue-based watcher
|
|
//
|
|
var should_close_input_file_fd = fd == null;
|
|
|
|
// We don't want cjs wrappers around non-js files
|
|
const module_type_only_for_wrappables = switch (loader) {
|
|
.js, .jsx, .ts, .tsx => module_type,
|
|
else => .unknown,
|
|
};
|
|
|
|
var input_file_fd: StoredFileDescriptorType = bun.invalid_fd;
|
|
var parse_options = Transpiler.ParseOptions{
|
|
.allocator = allocator,
|
|
.path = path,
|
|
.loader = loader,
|
|
.dirname_fd = bun.invalid_fd,
|
|
.file_descriptor = fd,
|
|
.file_fd_ptr = &input_file_fd,
|
|
.file_hash = hash,
|
|
.macro_remappings = macro_remappings,
|
|
.jsx = jsc_vm.transpiler.options.jsx,
|
|
.emit_decorator_metadata = jsc_vm.transpiler.options.emit_decorator_metadata,
|
|
.virtual_source = virtual_source,
|
|
.dont_bundle_twice = true,
|
|
.allow_commonjs = true,
|
|
.module_type = module_type_only_for_wrappables,
|
|
.inject_jest_globals = jsc_vm.transpiler.options.rewrite_jest_for_tests,
|
|
.keep_json_and_toml_as_one_statement = true,
|
|
.allow_bytecode_cache = true,
|
|
.set_breakpoint_on_first_line = is_main and
|
|
jsc_vm.debugger != null and
|
|
jsc_vm.debugger.?.set_breakpoint_on_first_line and
|
|
setBreakPointOnFirstLine(),
|
|
.runtime_transpiler_cache = if (!disable_transpilying and !jsc.RuntimeTranspilerCache.is_disabled) &cache else null,
|
|
.remove_cjs_module_wrapper = is_main and jsc_vm.module_loader.eval_source != null,
|
|
};
|
|
defer {
|
|
if (should_close_input_file_fd and input_file_fd != bun.invalid_fd) {
|
|
input_file_fd.close();
|
|
input_file_fd = bun.invalid_fd;
|
|
}
|
|
}
|
|
|
|
if (is_node_override) {
|
|
if (node_fallbacks.contentsFromPath(specifier)) |code| {
|
|
const fallback_path = Fs.Path.initWithNamespace(specifier, "node");
|
|
fallback_source = logger.Source{ .path = fallback_path, .contents = code };
|
|
parse_options.virtual_source = &fallback_source;
|
|
}
|
|
}
|
|
|
|
var parse_result: ParseResult = switch (disable_transpilying or
|
|
(loader == .json)) {
|
|
inline else => |return_file_only| brk: {
|
|
break :brk jsc_vm.transpiler.parseMaybeReturnFileOnly(
|
|
parse_options,
|
|
null,
|
|
return_file_only,
|
|
) orelse {
|
|
if (comptime !disable_transpilying) {
|
|
if (jsc_vm.isWatcherEnabled()) {
|
|
if (input_file_fd.isValid()) {
|
|
if (!is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
|
|
should_close_input_file_fd = false;
|
|
_ = jsc_vm.bun_watcher.addFile(
|
|
input_file_fd,
|
|
path.text,
|
|
hash,
|
|
loader,
|
|
.invalid,
|
|
package_json,
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
give_back_arena = false;
|
|
return error.ParseError;
|
|
};
|
|
},
|
|
};
|
|
|
|
const source = &parse_result.source;
|
|
|
|
if (parse_result.loader == .wasm) {
|
|
return transpileSourceCode(
|
|
jsc_vm,
|
|
specifier,
|
|
referrer,
|
|
input_specifier,
|
|
path,
|
|
.wasm,
|
|
.unknown, // cjs/esm don't make sense for wasm
|
|
log,
|
|
&parse_result.source,
|
|
promise_ptr,
|
|
source_code_printer,
|
|
globalObject,
|
|
flags,
|
|
);
|
|
}
|
|
|
|
if (comptime !disable_transpilying) {
|
|
if (jsc_vm.isWatcherEnabled()) {
|
|
if (input_file_fd.isValid()) {
|
|
if (!is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
|
|
should_close_input_file_fd = false;
|
|
_ = jsc_vm.bun_watcher.addFile(
|
|
input_file_fd,
|
|
path.text,
|
|
hash,
|
|
loader,
|
|
.invalid,
|
|
package_json,
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jsc_vm.transpiler.log.errors > 0) {
|
|
give_back_arena = false;
|
|
return error.ParseError;
|
|
}
|
|
|
|
if (loader == .json) {
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.cloneUTF8(source.contents),
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.tag = ResolvedSource.Tag.json_for_object_loader,
|
|
};
|
|
}
|
|
|
|
if (comptime disable_transpilying) {
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = switch (comptime flags) {
|
|
.print_source_and_clone => bun.String.init(jsc_vm.allocator.dupe(u8, source.contents) catch unreachable),
|
|
.print_source => bun.String.init(source.contents),
|
|
else => @compileError("unreachable"),
|
|
},
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
};
|
|
}
|
|
|
|
if (loader == .json or loader == .jsonc or loader == .toml or loader == .yaml) {
|
|
if (parse_result.empty) {
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.jsvalue_for_export = JSValue.createEmptyObject(jsc_vm.global, 0),
|
|
.tag = .exports_object,
|
|
};
|
|
}
|
|
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.jsvalue_for_export = parse_result.ast.parts.at(0).stmts[0].data.s_expr.value.toJS(allocator, globalObject orelse jsc_vm.global) catch |e| panic("Unexpected JS error: {s}", .{@errorName(e)}),
|
|
.tag = .exports_object,
|
|
};
|
|
}
|
|
|
|
if (parse_result.already_bundled != .none) {
|
|
const bytecode_slice = parse_result.already_bundled.bytecodeSlice();
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.cloneLatin1(source.contents),
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.already_bundled = true,
|
|
.bytecode_cache = if (bytecode_slice.len > 0) bytecode_slice.ptr else null,
|
|
.bytecode_cache_size = bytecode_slice.len,
|
|
.is_commonjs_module = parse_result.already_bundled.isCommonJS(),
|
|
};
|
|
}
|
|
|
|
if (parse_result.empty) {
|
|
const was_cjs = (loader == .js or loader == .ts) and brk: {
|
|
const ext = std.fs.path.extension(source.path.text);
|
|
break :brk strings.eqlComptime(ext, ".cjs") or strings.eqlComptime(ext, ".cts");
|
|
};
|
|
if (was_cjs) {
|
|
return .{
|
|
.allocator = null,
|
|
.source_code = bun.String.static("(function(){})"),
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.is_commonjs_module = true,
|
|
.tag = .javascript,
|
|
};
|
|
}
|
|
}
|
|
|
|
if (cache.entry) |*entry| {
|
|
jsc_vm.source_mappings.putMappings(source, .{
|
|
.list = .{ .items = @constCast(entry.sourcemap), .capacity = entry.sourcemap.len },
|
|
.allocator = bun.default_allocator,
|
|
}) catch {};
|
|
|
|
if (comptime Environment.allow_assert) {
|
|
dumpSourceString(jsc_vm, specifier, entry.output_code.byteSlice());
|
|
}
|
|
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = switch (entry.output_code) {
|
|
.string => entry.output_code.string,
|
|
.utf8 => brk: {
|
|
const result = bun.String.cloneUTF8(entry.output_code.utf8);
|
|
cache.output_code_allocator.free(entry.output_code.utf8);
|
|
entry.output_code.utf8 = "";
|
|
break :brk result;
|
|
},
|
|
},
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.is_commonjs_module = entry.metadata.module_type == .cjs,
|
|
.tag = brk: {
|
|
if (entry.metadata.module_type == .cjs and source.path.isFile()) {
|
|
const actual_package_json: *PackageJSON = package_json orelse brk2: {
|
|
// this should already be cached virtually always so it's fine to do this
|
|
const dir_info = (jsc_vm.transpiler.resolver.readDirInfo(source.path.name.dir) catch null) orelse
|
|
break :brk .javascript;
|
|
|
|
break :brk2 dir_info.package_json orelse dir_info.enclosing_package_json;
|
|
} orelse break :brk .javascript;
|
|
|
|
if (actual_package_json.module_type == .esm) {
|
|
break :brk ResolvedSource.Tag.package_json_type_module;
|
|
}
|
|
}
|
|
|
|
break :brk ResolvedSource.Tag.javascript;
|
|
},
|
|
};
|
|
}
|
|
|
|
const start_count = jsc_vm.transpiler.linker.import_counter;
|
|
|
|
// We _must_ link because:
|
|
// - node_modules bundle won't be properly
|
|
try jsc_vm.transpiler.linker.link(
|
|
path,
|
|
&parse_result,
|
|
jsc_vm.origin,
|
|
.absolute_path,
|
|
false,
|
|
true,
|
|
);
|
|
|
|
if (parse_result.pending_imports.len > 0) {
|
|
if (promise_ptr == null) {
|
|
return error.UnexpectedPendingResolution;
|
|
}
|
|
|
|
if (source.contents_is_recycled) {
|
|
// this shared buffer is about to become owned by the AsyncModule struct
|
|
jsc_vm.transpiler.resolver.caches.fs.resetSharedBuffer(
|
|
jsc_vm.transpiler.resolver.caches.fs.sharedBuffer(),
|
|
);
|
|
}
|
|
|
|
jsc_vm.modules.enqueue(
|
|
globalObject.?,
|
|
.{
|
|
.parse_result = parse_result,
|
|
.path = path,
|
|
.loader = loader,
|
|
.fd = fd,
|
|
.package_json = package_json,
|
|
.hash = hash,
|
|
.promise_ptr = promise_ptr,
|
|
.specifier = specifier,
|
|
.referrer = referrer,
|
|
.arena = arena,
|
|
},
|
|
);
|
|
give_back_arena = false;
|
|
return error.AsyncModule;
|
|
}
|
|
|
|
if (!jsc_vm.macro_mode)
|
|
jsc_vm.resolved_count += jsc_vm.transpiler.linker.import_counter - start_count;
|
|
jsc_vm.transpiler.linker.import_counter = 0;
|
|
|
|
var printer = source_code_printer.*;
|
|
printer.ctx.reset();
|
|
defer source_code_printer.* = printer;
|
|
_ = brk: {
|
|
var mapper = jsc_vm.sourceMapHandler(&printer);
|
|
|
|
break :brk try jsc_vm.transpiler.printWithSourceMap(
|
|
parse_result,
|
|
@TypeOf(&printer),
|
|
&printer,
|
|
.esm_ascii,
|
|
mapper.get(),
|
|
);
|
|
};
|
|
|
|
if (comptime Environment.dump_source) {
|
|
dumpSource(jsc_vm, specifier, &printer);
|
|
}
|
|
|
|
defer {
|
|
if (is_main) {
|
|
jsc_vm.has_loaded = true;
|
|
}
|
|
}
|
|
|
|
if (jsc_vm.isWatcherEnabled()) {
|
|
var resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, input_specifier, path.text, null, false);
|
|
resolved_source.is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs;
|
|
return resolved_source;
|
|
}
|
|
|
|
// Pass along package.json type "module" if set.
|
|
const tag: ResolvedSource.Tag = switch (loader) {
|
|
.json, .jsonc => .json_for_object_loader,
|
|
.js, .jsx, .ts, .tsx => brk: {
|
|
const module_type_ = if (package_json) |pkg| pkg.module_type else module_type;
|
|
|
|
break :brk switch (module_type_) {
|
|
.esm => .package_json_type_module,
|
|
.cjs => .package_json_type_commonjs,
|
|
else => .javascript,
|
|
};
|
|
},
|
|
else => .javascript,
|
|
};
|
|
|
|
return .{
|
|
.allocator = null,
|
|
.source_code = brk: {
|
|
const written = printer.ctx.getWritten();
|
|
const result = cache.output_code orelse bun.String.cloneLatin1(written);
|
|
|
|
if (written.len > 1024 * 1024 * 2 or jsc_vm.smol) {
|
|
printer.ctx.buffer.deinit();
|
|
}
|
|
|
|
break :brk result;
|
|
},
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs,
|
|
.tag = tag,
|
|
};
|
|
},
|
|
// 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 = Transpiler.ParseOptions{
|
|
// .allocator = allocator,
|
|
// .path = path,
|
|
// .loader = loader,
|
|
// .dirname_fd = 0,
|
|
// .file_descriptor = fd,
|
|
// .file_hash = hash,
|
|
// .macro_remappings = MacroRemap{},
|
|
// .jsx = jsc_vm.transpiler.options.jsx,
|
|
// };
|
|
|
|
// var parse_result = jsc_vm.transpiler.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, source.contents) catch unreachable),
|
|
// .specifier = ZigString.init(specifier),
|
|
// .source_url = input_specifier.createIfDifferent(path.text),
|
|
// .tag = ResolvedSource.Tag.wasm,
|
|
// };
|
|
// },
|
|
.wasm => {
|
|
if (strings.eqlComptime(referrer, "undefined") and strings.eqlLong(jsc_vm.main, path.text, true)) {
|
|
if (virtual_source) |source| {
|
|
if (globalObject) |globalThis| {
|
|
// attempt to avoid reading the WASM file twice.
|
|
const decoded: jsc.DecodedJSValue = .{
|
|
.u = .{ .ptr = @ptrCast(globalThis) },
|
|
};
|
|
const globalValue = decoded.encode();
|
|
globalValue.put(
|
|
globalThis,
|
|
ZigString.static("wasmSourceBytes"),
|
|
try jsc.ArrayBuffer.create(globalThis, source.contents, .Uint8Array),
|
|
);
|
|
}
|
|
}
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.static(@embedFile("../js/wasi-runner.js")),
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.tag = .esm,
|
|
};
|
|
}
|
|
|
|
return transpileSourceCode(
|
|
jsc_vm,
|
|
specifier,
|
|
referrer,
|
|
input_specifier,
|
|
path,
|
|
.file,
|
|
.unknown, // cjs/esm don't make sense for wasm
|
|
log,
|
|
virtual_source,
|
|
promise_ptr,
|
|
source_code_printer,
|
|
globalObject,
|
|
flags,
|
|
);
|
|
},
|
|
|
|
.sqlite_embedded, .sqlite => {
|
|
const sqlite_module_source_code_string = brk: {
|
|
if (jsc_vm.hot_reload == .hot) {
|
|
break :brk
|
|
\\// Generated code
|
|
\\import {Database} from 'bun:sqlite';
|
|
\\const {path} = import.meta;
|
|
\\
|
|
\\// Don't reload the database if it's already loaded
|
|
\\const registry = (globalThis[Symbol.for("bun:sqlite:hot")] ??= new Map());
|
|
\\
|
|
\\export let db = registry.get(path);
|
|
\\export const __esModule = true;
|
|
\\if (!db) {
|
|
\\ // Load the database
|
|
\\ db = new Database(path);
|
|
\\ registry.set(path, db);
|
|
\\}
|
|
\\
|
|
\\export default db;
|
|
;
|
|
}
|
|
|
|
break :brk
|
|
\\// Generated code
|
|
\\import {Database} from 'bun:sqlite';
|
|
\\export const db = new Database(import.meta.path);
|
|
\\
|
|
\\export const __esModule = true;
|
|
\\export default db;
|
|
;
|
|
};
|
|
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.cloneUTF8(sqlite_module_source_code_string),
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.tag = .esm,
|
|
};
|
|
},
|
|
|
|
.html => {
|
|
if (flags.disableTranspiling()) {
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.empty,
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.tag = .esm,
|
|
};
|
|
}
|
|
|
|
if (globalObject == null) {
|
|
return error.NotSupported;
|
|
}
|
|
|
|
const html_bundle = try jsc.API.HTMLBundle.init(globalObject.?, path.text);
|
|
return ResolvedSource{
|
|
.allocator = &jsc_vm.allocator,
|
|
.jsvalue_for_export = html_bundle.toJS(globalObject.?),
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.tag = .export_default_object,
|
|
};
|
|
},
|
|
|
|
else => {
|
|
if (flags.disableTranspiling()) {
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.empty,
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.tag = .esm,
|
|
};
|
|
}
|
|
|
|
if (virtual_source == null) {
|
|
if (jsc_vm.isWatcherEnabled()) auto_watch: {
|
|
if (std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
|
|
const input_fd: bun.StoredFileDescriptorType = brk: {
|
|
// on macOS, we need a file descriptor to receive event notifications on it.
|
|
// so we use O_EVTONLY to open the file descriptor without asking any additional permissions.
|
|
if (bun.Watcher.requires_file_descriptors) {
|
|
switch (bun.sys.open(
|
|
&(std.posix.toPosixPath(path.text) catch break :auto_watch),
|
|
bun.c.O_EVTONLY,
|
|
0,
|
|
)) {
|
|
.err => break :auto_watch,
|
|
.result => |fd| break :brk fd,
|
|
}
|
|
} else {
|
|
// Otherwise, don't even bother opening it.
|
|
break :brk .invalid;
|
|
}
|
|
};
|
|
const hash = bun.Watcher.getHash(path.text);
|
|
switch (jsc_vm.bun_watcher.addFile(
|
|
input_fd,
|
|
path.text,
|
|
hash,
|
|
loader,
|
|
.invalid,
|
|
null,
|
|
true,
|
|
)) {
|
|
.err => {
|
|
if (comptime Environment.isMac) {
|
|
// If any error occurs and we just
|
|
// opened the file descriptor to
|
|
// receive event notifications on
|
|
// it, we should close it.
|
|
if (input_fd.isValid()) {
|
|
input_fd.close();
|
|
}
|
|
}
|
|
|
|
// we don't consider it a failure if we cannot watch the file
|
|
// they didn't open the file
|
|
},
|
|
.result => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const value = brk: {
|
|
if (!jsc_vm.origin.isEmpty()) {
|
|
var buf = bun.handleOom(MutableString.init2048(jsc_vm.allocator));
|
|
defer buf.deinit();
|
|
var writer = buf.writer();
|
|
jsc.API.Bun.getPublicPath(specifier, jsc_vm.origin, @TypeOf(&writer), &writer);
|
|
break :brk try bun.String.createUTF8ForJS(globalObject.?, buf.slice());
|
|
}
|
|
|
|
break :brk try bun.String.createUTF8ForJS(globalObject.?, path.text);
|
|
};
|
|
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.jsvalue_for_export = value,
|
|
.specifier = input_specifier,
|
|
.source_url = input_specifier.createIfDifferent(path.text),
|
|
.tag = .export_default_object,
|
|
};
|
|
},
|
|
}
|
|
}
|
|
|
|
pub export fn Bun__resolveAndFetchBuiltinModule(
|
|
jsc_vm: *VirtualMachine,
|
|
specifier: *bun.String,
|
|
ret: *jsc.ErrorableResolvedSource,
|
|
) bool {
|
|
jsc.markBinding(@src());
|
|
var log = logger.Log.init(jsc_vm.transpiler.allocator);
|
|
defer log.deinit();
|
|
|
|
const alias = HardcodedModule.Alias.bun_aliases.getWithEql(specifier.*, bun.String.eqlComptime) orelse
|
|
return false;
|
|
const hardcoded = HardcodedModule.map.get(alias.path) orelse {
|
|
bun.debugAssert(false);
|
|
return false;
|
|
};
|
|
ret.* = .ok(
|
|
getHardcodedModule(jsc_vm, specifier.*, hardcoded) orelse
|
|
return false,
|
|
);
|
|
return true;
|
|
}
|
|
|
|
pub export fn Bun__fetchBuiltinModule(
|
|
jsc_vm: *VirtualMachine,
|
|
globalObject: *JSGlobalObject,
|
|
specifier: *bun.String,
|
|
referrer: *bun.String,
|
|
ret: *jsc.ErrorableResolvedSource,
|
|
) bool {
|
|
jsc.markBinding(@src());
|
|
var log = logger.Log.init(jsc_vm.transpiler.allocator);
|
|
defer log.deinit();
|
|
|
|
if (ModuleLoader.fetchBuiltinModule(
|
|
jsc_vm,
|
|
specifier.*,
|
|
) catch |err| {
|
|
if (err == error.AsyncModule) {
|
|
unreachable;
|
|
}
|
|
|
|
VirtualMachine.processFetchLog(globalObject, specifier.*, referrer.*, &log, ret, err);
|
|
return true;
|
|
}) |builtin| {
|
|
ret.* = jsc.ErrorableResolvedSource.ok(builtin);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const always_sync_modules = .{"reflect-metadata"};
|
|
|
|
pub export fn Bun__transpileFile(
|
|
jsc_vm: *VirtualMachine,
|
|
globalObject: *JSGlobalObject,
|
|
specifier_ptr: *bun.String,
|
|
referrer: *bun.String,
|
|
type_attribute: ?*const bun.String,
|
|
ret: *jsc.ErrorableResolvedSource,
|
|
allow_promise: bool,
|
|
is_commonjs_require: bool,
|
|
_force_loader_type: bun.schema.api.Loader,
|
|
) ?*anyopaque {
|
|
jsc.markBinding(@src());
|
|
const force_loader_type: bun.options.Loader.Optional = .fromAPI(_force_loader_type);
|
|
var log = logger.Log.init(jsc_vm.transpiler.allocator);
|
|
defer log.deinit();
|
|
|
|
var _specifier = specifier_ptr.toUTF8(jsc_vm.allocator);
|
|
var referrer_slice = referrer.toUTF8(jsc_vm.allocator);
|
|
defer _specifier.deinit();
|
|
defer referrer_slice.deinit();
|
|
|
|
var type_attribute_str: ?string = null;
|
|
if (type_attribute) |attribute| if (attribute.asUTF8()) |attr_utf8| {
|
|
type_attribute_str = attr_utf8;
|
|
};
|
|
|
|
var virtual_source_to_use: ?logger.Source = null;
|
|
var blob_to_deinit: ?jsc.WebCore.Blob = null;
|
|
var lr = options.getLoaderAndVirtualSource(_specifier.slice(), jsc_vm, &virtual_source_to_use, &blob_to_deinit, type_attribute_str) catch {
|
|
ret.* = jsc.ErrorableResolvedSource.err(error.JSErrorObject, globalObject.ERR(.MODULE_NOT_FOUND, "Blob not found", .{}).toJS());
|
|
return null;
|
|
};
|
|
defer if (blob_to_deinit) |*blob| blob.deinit();
|
|
|
|
if (force_loader_type.unwrap()) |loader_type| {
|
|
@branchHint(.unlikely);
|
|
bun.assert(!is_commonjs_require);
|
|
lr.loader = loader_type;
|
|
} else if (is_commonjs_require and jsc_vm.has_mutated_built_in_extensions > 0) {
|
|
@branchHint(.unlikely);
|
|
if (node_module_module.findLongestRegisteredExtension(jsc_vm, _specifier.slice())) |entry| {
|
|
switch (entry) {
|
|
.loader => |loader| {
|
|
lr.loader = loader;
|
|
},
|
|
.custom => |strong| {
|
|
ret.* = jsc.ErrorableResolvedSource.ok(ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.empty,
|
|
.specifier = .empty,
|
|
.source_url = .empty,
|
|
.cjs_custom_extension_index = strong.get(),
|
|
.tag = .common_js_custom_extension,
|
|
});
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
const module_type: options.ModuleType = brk: {
|
|
const ext = lr.path.name.ext;
|
|
// regular expression /.[cm][jt]s$/
|
|
if (ext.len == ".cjs".len) {
|
|
if (strings.eqlComptimeIgnoreLen(ext, ".cjs"))
|
|
break :brk .cjs;
|
|
if (strings.eqlComptimeIgnoreLen(ext, ".mjs"))
|
|
break :brk .esm;
|
|
if (strings.eqlComptimeIgnoreLen(ext, ".cts"))
|
|
break :brk .cjs;
|
|
if (strings.eqlComptimeIgnoreLen(ext, ".mts"))
|
|
break :brk .esm;
|
|
}
|
|
// regular expression /.[jt]s$/
|
|
if (ext.len == ".ts".len) {
|
|
if (strings.eqlComptimeIgnoreLen(ext, ".js") or
|
|
strings.eqlComptimeIgnoreLen(ext, ".ts"))
|
|
{
|
|
// Use the package.json module type if it exists
|
|
break :brk if (lr.package_json) |pkg|
|
|
pkg.module_type
|
|
else
|
|
.unknown;
|
|
}
|
|
}
|
|
// For JSX TSX and other extensions, let the file contents.
|
|
break :brk .unknown;
|
|
};
|
|
const pkg_name: ?[]const u8 = if (lr.package_json) |pkg|
|
|
if (pkg.name.len > 0) pkg.name else null
|
|
else
|
|
null;
|
|
|
|
// We only run the transpiler concurrently when we can.
|
|
// Today, that's:
|
|
//
|
|
// Import Statements (import 'foo')
|
|
// Import Expressions (import('foo'))
|
|
//
|
|
transpile_async: {
|
|
if (comptime bun.FeatureFlags.concurrent_transpiler) {
|
|
const concurrent_loader = lr.loader orelse .file;
|
|
if (blob_to_deinit == null and
|
|
allow_promise and
|
|
(jsc_vm.has_loaded or jsc_vm.is_in_preload) and
|
|
concurrent_loader.isJavaScriptLike() and
|
|
!lr.is_main and
|
|
// Plugins make this complicated,
|
|
// TODO: allow running concurrently when no onLoad handlers match a plugin.
|
|
jsc_vm.plugin_runner == null and jsc_vm.transpiler_store.enabled)
|
|
{
|
|
// This absolutely disgusting hack is a workaround in cases
|
|
// where an async import is made to a CJS file with side
|
|
// effects that other modules depend on, without incurring
|
|
// the cost of transpiling/loading CJS modules synchronously.
|
|
//
|
|
// The cause of this comes from the fact that we immediately
|
|
// and synchronously evaluate CJS modules after they've been
|
|
// transpiled, but transpiling (which, for async imports,
|
|
// happens in a thread pool), can resolve in whatever order.
|
|
// This messes up module execution order.
|
|
//
|
|
// This is only _really_ important for
|
|
// import("some-polyfill") cases, the most impactful of
|
|
// which is `reflect-metadata`. People could also use
|
|
// require or just preload their polyfills, but they aren't
|
|
// doing this. This hack makes important polyfills work without
|
|
// incurring the cost of transpiling/loading CJS modules
|
|
// synchronously. The proper fix is to evaluate CJS modules
|
|
// at the same time as ES modules. This is blocked by the
|
|
// fact that we need exports from CJS modules and our parser
|
|
// doesn't record them.
|
|
if (pkg_name) |pkg_name_| {
|
|
inline for (always_sync_modules) |always_sync_specifier| {
|
|
if (bun.strings.eqlComptime(pkg_name_, always_sync_specifier)) {
|
|
break :transpile_async;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: check if the resolved source must be transpiled synchronously
|
|
return jsc_vm.transpiler_store.transpile(
|
|
jsc_vm,
|
|
globalObject,
|
|
specifier_ptr.dupeRef(),
|
|
lr.path,
|
|
referrer.dupeRef(),
|
|
concurrent_loader,
|
|
lr.package_json,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
const synchronous_loader: options.Loader = lr.loader orelse loader: {
|
|
if (jsc_vm.has_loaded or jsc_vm.is_in_preload) {
|
|
// Extensionless files in this context are treated as the JS loader
|
|
if (lr.path.name.ext.len == 0) {
|
|
break :loader .tsx;
|
|
}
|
|
|
|
// Unknown extensions are to be treated as file loader
|
|
if (is_commonjs_require) {
|
|
if (jsc_vm.commonjs_custom_extensions.entries.len > 0 and
|
|
jsc_vm.has_mutated_built_in_extensions == 0)
|
|
{
|
|
@branchHint(.unlikely);
|
|
if (node_module_module.findLongestRegisteredExtension(jsc_vm, lr.path.text)) |entry| {
|
|
switch (entry) {
|
|
.loader => |loader| break :loader loader,
|
|
.custom => |strong| {
|
|
ret.* = jsc.ErrorableResolvedSource.ok(ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.empty,
|
|
.specifier = .empty,
|
|
.source_url = .empty,
|
|
.cjs_custom_extension_index = strong.get(),
|
|
.tag = .common_js_custom_extension,
|
|
});
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// For Node.js compatibility, requiring a file with an
|
|
// unknown extension will be treated as a JS file
|
|
break :loader .ts;
|
|
}
|
|
|
|
// For ESM, Bun treats unknown extensions as file loader
|
|
break :loader .file;
|
|
} else {
|
|
// Unless it's potentially the main module
|
|
// This is important so that "bun run ./foo-i-have-no-extension" works
|
|
break :loader .tsx;
|
|
}
|
|
};
|
|
|
|
if (comptime Environment.allow_assert)
|
|
debug("transpile({s}, {s}, sync)", .{ lr.specifier, @tagName(synchronous_loader) });
|
|
|
|
defer jsc_vm.module_loader.resetArena(jsc_vm);
|
|
|
|
var promise: ?*jsc.JSInternalPromise = null;
|
|
ret.* = jsc.ErrorableResolvedSource.ok(
|
|
ModuleLoader.transpileSourceCode(
|
|
jsc_vm,
|
|
lr.specifier,
|
|
referrer_slice.slice(),
|
|
specifier_ptr.*,
|
|
lr.path,
|
|
synchronous_loader,
|
|
module_type,
|
|
&log,
|
|
lr.virtual_source,
|
|
if (allow_promise) &promise else null,
|
|
VirtualMachine.source_code_printer.?,
|
|
globalObject,
|
|
FetchFlags.transpile,
|
|
) catch |err| {
|
|
switch (err) {
|
|
error.AsyncModule => {
|
|
bun.assert(promise != null);
|
|
return promise;
|
|
},
|
|
error.PluginError => return null,
|
|
error.JSError => {
|
|
ret.* = jsc.ErrorableResolvedSource.err(error.JSError, globalObject.takeError(error.JSError));
|
|
return null;
|
|
},
|
|
else => {
|
|
VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer.*, &log, ret, err);
|
|
return null;
|
|
},
|
|
}
|
|
},
|
|
);
|
|
return promise;
|
|
}
|
|
|
|
export fn Bun__runVirtualModule(globalObject: *JSGlobalObject, specifier_ptr: *const bun.String) JSValue {
|
|
jsc.markBinding(@src());
|
|
if (globalObject.bunVM().plugin_runner == null) return JSValue.zero;
|
|
|
|
const specifier_slice = specifier_ptr.toUTF8(bun.default_allocator);
|
|
defer specifier_slice.deinit();
|
|
const specifier = specifier_slice.slice();
|
|
|
|
if (!PluginRunner.couldBePlugin(specifier)) {
|
|
return JSValue.zero;
|
|
}
|
|
|
|
const namespace = PluginRunner.extractNamespace(specifier);
|
|
const after_namespace = if (namespace.len == 0)
|
|
specifier
|
|
else
|
|
specifier[@min(namespace.len + 1, specifier.len)..];
|
|
|
|
return globalObject.runOnLoadPlugins(bun.String.init(namespace), bun.String.init(after_namespace), .bun) catch {
|
|
return JSValue.zero;
|
|
} orelse return .zero;
|
|
}
|
|
|
|
fn getHardcodedModule(jsc_vm: *VirtualMachine, specifier: bun.String, hardcoded: HardcodedModule) ?ResolvedSource {
|
|
analytics.Features.builtin_modules.insert(hardcoded);
|
|
return switch (hardcoded) {
|
|
.@"bun:main" => .{
|
|
.allocator = null,
|
|
.source_code = bun.String.cloneUTF8(jsc_vm.entry_point.source.contents),
|
|
.specifier = specifier,
|
|
.source_url = specifier,
|
|
.tag = .esm,
|
|
.source_code_needs_deref = true,
|
|
},
|
|
.@"bun:internal-for-testing" => {
|
|
if (!Environment.isDebug) {
|
|
if (!is_allowed_to_use_internal_testing_apis)
|
|
return null;
|
|
}
|
|
return jsSyntheticModule(.@"bun:internal-for-testing", specifier);
|
|
},
|
|
.@"bun:wrap" => .{
|
|
.allocator = null,
|
|
.source_code = String.init(Runtime.Runtime.sourceCode()),
|
|
.specifier = specifier,
|
|
.source_url = specifier,
|
|
},
|
|
inline else => |tag| jsSyntheticModule(@field(ResolvedSource.Tag, @tagName(tag)), specifier),
|
|
};
|
|
}
|
|
|
|
pub fn fetchBuiltinModule(jsc_vm: *VirtualMachine, specifier: bun.String) !?ResolvedSource {
|
|
if (HardcodedModule.map.getWithEql(specifier, bun.String.eqlComptime)) |hardcoded| {
|
|
return getHardcodedModule(jsc_vm, specifier, hardcoded);
|
|
}
|
|
|
|
if (specifier.hasPrefixComptime(js_ast.Macro.namespaceWithColon)) {
|
|
const spec = specifier.toUTF8(bun.default_allocator);
|
|
defer spec.deinit();
|
|
if (jsc_vm.macro_entry_points.get(MacroEntryPoint.generateIDFromSpecifier(spec.slice()))) |entry| {
|
|
return .{
|
|
.allocator = null,
|
|
.source_code = bun.String.cloneUTF8(entry.source.contents),
|
|
.specifier = specifier,
|
|
.source_url = specifier.dupeRef(),
|
|
};
|
|
}
|
|
} else if (jsc_vm.standalone_module_graph) |graph| {
|
|
const specifier_utf8 = specifier.toUTF8(bun.default_allocator);
|
|
defer specifier_utf8.deinit();
|
|
if (graph.files.getPtr(specifier_utf8.slice())) |file| {
|
|
if (file.loader == .sqlite or file.loader == .sqlite_embedded) {
|
|
const code =
|
|
\\/* Generated code */
|
|
\\import {Database} from 'bun:sqlite';
|
|
\\import {readFileSync} from 'node:fs';
|
|
\\export const db = new Database(readFileSync(import.meta.path));
|
|
\\
|
|
\\export const __esModule = true;
|
|
\\export default db;
|
|
;
|
|
return .{
|
|
.allocator = null,
|
|
.source_code = bun.String.static(code),
|
|
.specifier = specifier,
|
|
.source_url = specifier.dupeRef(),
|
|
.source_code_needs_deref = false,
|
|
};
|
|
}
|
|
|
|
return .{
|
|
.allocator = null,
|
|
.source_code = file.toWTFString(),
|
|
.specifier = specifier,
|
|
.source_url = specifier.dupeRef(),
|
|
.source_code_needs_deref = false,
|
|
.bytecode_cache = if (file.bytecode.len > 0) file.bytecode.ptr else null,
|
|
.bytecode_cache_size = file.bytecode.len,
|
|
.is_commonjs_module = file.module_format == .cjs,
|
|
};
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export fn Bun__transpileVirtualModule(
|
|
globalObject: *JSGlobalObject,
|
|
specifier_ptr: *const bun.String,
|
|
referrer_ptr: *const bun.String,
|
|
source_code: *ZigString,
|
|
loader_: api.Loader,
|
|
ret: *jsc.ErrorableResolvedSource,
|
|
) bool {
|
|
jsc.markBinding(@src());
|
|
const jsc_vm = globalObject.bunVM();
|
|
// Plugin runner is not required for virtual modules created via build.module()
|
|
// bun.assert(jsc_vm.plugin_runner != null);
|
|
|
|
var specifier_slice = specifier_ptr.toUTF8(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 referrer_slice = referrer_ptr.toUTF8(jsc_vm.allocator);
|
|
defer referrer_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.fromAPI(loader_)
|
|
else
|
|
jsc_vm.transpiler.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();
|
|
defer jsc_vm.module_loader.resetArena(jsc_vm);
|
|
|
|
ret.* = jsc.ErrorableResolvedSource.ok(
|
|
ModuleLoader.transpileSourceCode(
|
|
jsc_vm,
|
|
specifier_slice.slice(),
|
|
referrer_slice.slice(),
|
|
specifier_ptr.*,
|
|
path,
|
|
loader,
|
|
.unknown,
|
|
&log,
|
|
&virtual_source,
|
|
null,
|
|
VirtualMachine.source_code_printer.?,
|
|
globalObject,
|
|
FetchFlags.transpile,
|
|
) catch |err| {
|
|
switch (err) {
|
|
error.PluginError => return true,
|
|
error.JSError => {
|
|
ret.* = jsc.ErrorableResolvedSource.err(error.JSError, globalObject.takeError(error.JSError));
|
|
return true;
|
|
},
|
|
else => {
|
|
VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer_ptr.*, &log, ret, err);
|
|
return true;
|
|
},
|
|
}
|
|
},
|
|
);
|
|
analytics.Features.virtual_modules += 1;
|
|
return true;
|
|
}
|
|
|
|
inline fn jsSyntheticModule(name: ResolvedSource.Tag, specifier: String) ResolvedSource {
|
|
return ResolvedSource{
|
|
.allocator = null,
|
|
.source_code = bun.String.empty,
|
|
.specifier = specifier,
|
|
.source_url = bun.String.static(@tagName(name)),
|
|
.tag = name,
|
|
.source_code_needs_deref = false,
|
|
};
|
|
}
|
|
|
|
/// Dumps the module source to a file in /tmp/bun-debug-src/{filepath}
|
|
///
|
|
/// This can technically fail if concurrent access across processes happens, or permission issues.
|
|
/// Errors here should always be ignored.
|
|
pub const FetchFlags = enum {
|
|
transpile,
|
|
print_source,
|
|
print_source_and_clone,
|
|
|
|
pub fn disableTranspiling(this: FetchFlags) bool {
|
|
return this != .transpile;
|
|
}
|
|
};
|
|
|
|
/// Support embedded .node files
|
|
export fn Bun__resolveEmbeddedNodeFile(vm: *VirtualMachine, in_out_str: *bun.String, linux_memfd_fd_to_close: *i32) bool {
|
|
if (vm.standalone_module_graph == null) return false;
|
|
|
|
const input_path = in_out_str.toUTF8(bun.default_allocator);
|
|
defer input_path.deinit();
|
|
const path_buffer = bun.path_buffer_pool.get();
|
|
defer bun.path_buffer_pool.put(path_buffer);
|
|
const result = ModuleLoader.resolveEmbeddedFile(vm, path_buffer, linux_memfd_fd_to_close, input_path.slice(), "node") orelse return false;
|
|
in_out_str.* = bun.String.cloneUTF8(result);
|
|
return true;
|
|
}
|
|
|
|
export fn ModuleLoader__isBuiltin(data: [*]const u8, len: usize) bool {
|
|
const str = data[0..len];
|
|
return HardcodedModule.Alias.bun_aliases.get(str) != null;
|
|
}
|
|
|
|
const debug = Output.scoped(.ModuleLoader, .hidden);
|
|
|
|
const string = []const u8;
|
|
|
|
const Fs = @import("../fs.zig");
|
|
const Runtime = @import("../runtime.zig");
|
|
const ast = @import("../import_record.zig");
|
|
const node_module_module = @import("./module/NodeModuleModule.zig");
|
|
const std = @import("std");
|
|
const panic = std.debug.panic;
|
|
|
|
const options = @import("../options.zig");
|
|
const ModuleType = options.ModuleType;
|
|
|
|
const MacroRemap = @import("../resolver/package_json.zig").MacroMap;
|
|
const PackageJSON = @import("../resolver/package_json.zig").PackageJSON;
|
|
|
|
const dumpSource = @import("./RuntimeTranspilerStore.zig").dumpSource;
|
|
const dumpSourceString = @import("./RuntimeTranspilerStore.zig").dumpSourceString;
|
|
const setBreakPointOnFirstLine = @import("./RuntimeTranspilerStore.zig").setBreakPointOnFirstLine;
|
|
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const MutableString = bun.MutableString;
|
|
const Output = bun.Output;
|
|
const StoredFileDescriptorType = bun.StoredFileDescriptorType;
|
|
const String = bun.String;
|
|
const Transpiler = bun.Transpiler;
|
|
const analytics = bun.analytics;
|
|
const js_ast = bun.ast;
|
|
const js_printer = bun.js_printer;
|
|
const logger = bun.logger;
|
|
const strings = bun.strings;
|
|
const Arena = bun.allocators.MimallocArena;
|
|
const api = bun.schema.api;
|
|
|
|
const jsc = bun.jsc;
|
|
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
|
const JSValue = bun.jsc.JSValue;
|
|
const ResolvedSource = bun.jsc.ResolvedSource;
|
|
const VirtualMachine = bun.jsc.VirtualMachine;
|
|
const ZigString = bun.jsc.ZigString;
|
|
const Bun = jsc.API.Bun;
|
|
|
|
const ParseResult = bun.transpiler.ParseResult;
|
|
const PluginRunner = bun.transpiler.PluginRunner;
|
|
const MacroEntryPoint = bun.transpiler.EntryPoints.MacroEntryPoint;
|