aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/api/transpiler.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/api/transpiler.zig')
-rw-r--r--src/bun.js/api/transpiler.zig1304
1 files changed, 1304 insertions, 0 deletions
diff --git a/src/bun.js/api/transpiler.zig b/src/bun.js/api/transpiler.zig
new file mode 100644
index 000000000..6d3f3f6fd
--- /dev/null
+++ b/src/bun.js/api/transpiler.zig
@@ -0,0 +1,1304 @@
+const std = @import("std");
+const Api = @import("../../api/schema.zig").Api;
+const FilesystemRouter = @import("../../router.zig");
+const http = @import("../../http.zig");
+const JavaScript = @import("../javascript.zig");
+const QueryStringMap = @import("../../url.zig").QueryStringMap;
+const CombinedScanner = @import("../../url.zig").CombinedScanner;
+const bun = @import("../../global.zig");
+const string = bun.string;
+const JSC = @import("../../jsc.zig");
+const js = JSC.C;
+const WebCore = @import("../webcore/response.zig");
+const Bundler = @import("../../bundler.zig");
+const options = @import("../../options.zig");
+const VirtualMachine = JavaScript.VirtualMachine;
+const ScriptSrcStream = std.io.FixedBufferStream([]u8);
+const ZigString = JSC.ZigString;
+const Fs = @import("../../fs.zig");
+const Base = @import("../base.zig");
+const getAllocator = Base.getAllocator;
+const JSObject = JSC.JSObject;
+const JSError = Base.JSError;
+const JSValue = JSC.JSValue;
+const JSGlobalObject = JSC.JSGlobalObject;
+const strings = @import("strings");
+const NewClass = Base.NewClass;
+const To = Base.To;
+const Request = WebCore.Request;
+const d = Base.d;
+const FetchEvent = WebCore.FetchEvent;
+const MacroMap = @import("../../resolver/package_json.zig").MacroMap;
+const TSConfigJSON = @import("../../resolver/tsconfig_json.zig").TSConfigJSON;
+const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON;
+const logger = @import("../../logger.zig");
+const Loader = options.Loader;
+const Platform = options.Platform;
+const JSAst = @import("../../js_ast.zig");
+const Transpiler = @This();
+const JSParser = @import("../../js_parser.zig");
+const JSPrinter = @import("../../js_printer.zig");
+const ScanPassResult = JSParser.ScanPassResult;
+const Mimalloc = @import("../../mimalloc_arena.zig");
+const Runtime = @import("../../runtime.zig").Runtime;
+const JSLexer = @import("../../js_lexer.zig");
+const Expr = JSAst.Expr;
+
+bundler: Bundler.Bundler,
+arena: std.heap.ArenaAllocator,
+transpiler_options: TranspilerOptions,
+scan_pass_result: ScanPassResult,
+buffer_writer: ?JSPrinter.BufferWriter = null,
+
+pub const Class = NewClass(
+ Transpiler,
+ .{ .name = "Transpiler" },
+ .{
+ .scanImports = .{
+ .rfn = scanImports,
+ },
+ .scan = .{
+ .rfn = scan,
+ },
+ .transform = .{
+ .rfn = transform,
+ },
+ .transformSync = .{
+ .rfn = transformSync,
+ },
+ // .resolve = .{
+ // .rfn = resolve,
+ // },
+ // .buildSync = .{
+ // .rfn = buildSync,
+ // },
+ .finalize = finalize,
+ },
+ .{},
+);
+
+pub const Constructor = JSC.NewConstructor(
+ @This(),
+ .{
+ .constructor = .{ .rfn = constructor },
+ },
+ .{},
+);
+
+const default_transform_options: Api.TransformOptions = brk: {
+ var opts = std.mem.zeroes(Api.TransformOptions);
+ opts.disable_hmr = true;
+ opts.platform = Api.Platform.browser;
+ opts.serve = false;
+ break :brk opts;
+};
+
+const TranspilerOptions = struct {
+ transform: Api.TransformOptions = default_transform_options,
+ default_loader: options.Loader = options.Loader.jsx,
+ macro_map: MacroMap = MacroMap{},
+ tsconfig: ?*TSConfigJSON = null,
+ tsconfig_buf: []const u8 = "",
+ macros_buf: []const u8 = "",
+ log: logger.Log,
+ runtime: Runtime.Features = Runtime.Features{ .top_level_await = true },
+ tree_shaking: bool = false,
+ trim_unused_imports: ?bool = null,
+};
+
+// Mimalloc gets unstable if we try to move this to a different thread
+// threadlocal var transform_buffer: bun.MutableString = undefined;
+// threadlocal var transform_buffer_loaded: bool = false;
+
+// This is going to be hard to not leak
+pub const TransformTask = struct {
+ input_code: ZigString = ZigString.init(""),
+ protected_input_value: JSC.JSValue = @intToEnum(JSC.JSValue, 0),
+ output_code: ZigString = ZigString.init(""),
+ bundler: Bundler.Bundler = undefined,
+ log: logger.Log,
+ err: ?anyerror = null,
+ macro_map: MacroMap = MacroMap{},
+ tsconfig: ?*TSConfigJSON = null,
+ loader: Loader,
+ global: *JSGlobalObject,
+ replace_exports: Runtime.Features.ReplaceableExport.Map = .{},
+
+ pub const AsyncTransformTask = JSC.ConcurrentPromiseTask(TransformTask);
+ pub const AsyncTransformEventLoopTask = AsyncTransformTask.EventLoopTask;
+
+ pub fn create(transpiler: *Transpiler, protected_input_value: JSC.C.JSValueRef, globalThis: *JSGlobalObject, input_code: ZigString, loader: Loader) !*AsyncTransformTask {
+ var transform_task = try bun.default_allocator.create(TransformTask);
+ transform_task.* = .{
+ .input_code = input_code,
+ .protected_input_value = if (protected_input_value != null) JSC.JSValue.fromRef(protected_input_value) else @intToEnum(JSC.JSValue, 0),
+ .bundler = undefined,
+ .global = globalThis,
+ .macro_map = transpiler.transpiler_options.macro_map,
+ .tsconfig = transpiler.transpiler_options.tsconfig,
+ .log = logger.Log.init(bun.default_allocator),
+ .loader = loader,
+ .replace_exports = transpiler.transpiler_options.runtime.replace_exports,
+ };
+ transform_task.bundler = transpiler.bundler;
+ transform_task.bundler.linker.resolver = &transform_task.bundler.resolver;
+
+ transform_task.bundler.setLog(&transform_task.log);
+ transform_task.bundler.setAllocator(bun.default_allocator);
+ return try AsyncTransformTask.createOnJSThread(bun.default_allocator, globalThis, transform_task);
+ }
+
+ pub fn run(this: *TransformTask) void {
+ const name = this.loader.stdinName();
+ const source = logger.Source.initPathString(name, this.input_code.slice());
+
+ JSAst.Stmt.Data.Store.create(bun.default_allocator);
+ JSAst.Expr.Data.Store.create(bun.default_allocator);
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+
+ const allocator = arena.allocator();
+
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ arena.deinit();
+ }
+
+ this.bundler.setAllocator(allocator);
+ const jsx = if (this.tsconfig != null)
+ this.tsconfig.?.mergeJSX(this.bundler.options.jsx)
+ else
+ this.bundler.options.jsx;
+
+ const parse_options = Bundler.Bundler.ParseOptions{
+ .allocator = allocator,
+ .macro_remappings = this.macro_map,
+ .dirname_fd = 0,
+ .file_descriptor = null,
+ .loader = this.loader,
+ .jsx = jsx,
+ .path = source.path,
+ .virtual_source = &source,
+ .replace_exports = this.replace_exports,
+ // .allocator = this.
+ };
+
+ const parse_result = this.bundler.parse(parse_options, null) orelse {
+ this.err = error.ParseError;
+ return;
+ };
+
+ if (parse_result.empty) {
+ this.output_code = ZigString.init("");
+ return;
+ }
+
+ var global_allocator = arena.backingAllocator();
+ var buffer_writer = JSPrinter.BufferWriter.init(global_allocator) catch |err| {
+ this.err = err;
+ return;
+ };
+ buffer_writer.buffer.list.ensureTotalCapacity(global_allocator, 512) catch unreachable;
+ buffer_writer.reset();
+
+ // defer {
+ // transform_buffer = buffer_writer.buffer;
+ // }
+
+ var printer = JSPrinter.BufferPrinter.init(buffer_writer);
+ const printed = this.bundler.print(parse_result, @TypeOf(&printer), &printer, .esm_ascii) catch |err| {
+ this.err = err;
+ return;
+ };
+
+ if (printed > 0) {
+ buffer_writer = printer.ctx;
+ buffer_writer.buffer.list.items = buffer_writer.written;
+
+ var output = JSC.ZigString.init(buffer_writer.written);
+ output.mark();
+ this.output_code = output;
+ } else {
+ this.output_code = ZigString.init("");
+ }
+ }
+
+ pub fn then(this: *TransformTask, promise: *JSC.JSInternalPromise) void {
+ if (this.log.hasAny() or this.err != null) {
+ const error_value: JSValue = brk: {
+ if (this.err) |err| {
+ if (!this.log.hasAny()) {
+ break :brk JSC.JSValue.fromRef(JSC.BuildError.create(
+ this.global,
+ bun.default_allocator,
+ logger.Msg{
+ .data = logger.Data{ .text = std.mem.span(@errorName(err)) },
+ },
+ ));
+ }
+ }
+
+ break :brk this.log.toJS(this.global, bun.default_allocator, "Transform failed");
+ };
+
+ promise.reject(this.global, error_value);
+ return;
+ }
+
+ finish(this.output_code, this.global, promise);
+
+ if (@enumToInt(this.protected_input_value) != 0) {
+ this.protected_input_value = @intToEnum(JSC.JSValue, 0);
+ }
+ this.deinit();
+ }
+
+ noinline fn finish(code: ZigString, global: *JSGlobalObject, promise: *JSC.JSInternalPromise) void {
+ promise.resolve(global, code.toValueGC(global));
+ }
+
+ pub fn deinit(this: *TransformTask) void {
+ var should_cleanup = false;
+ defer if (should_cleanup) bun.Global.mimalloc_cleanup(false);
+
+ this.log.deinit();
+ if (this.input_code.isGloballyAllocated()) {
+ this.input_code.deinitGlobal();
+ }
+
+ if (this.output_code.isGloballyAllocated()) {
+ should_cleanup = this.output_code.len > 512_000;
+ this.output_code.deinitGlobal();
+ }
+
+ bun.default_allocator.destroy(this);
+ }
+};
+
+fn exportReplacementValue(value: JSValue, globalThis: *JSGlobalObject) ?JSAst.Expr {
+ if (value.isBoolean()) {
+ return Expr{
+ .data = .{
+ .e_boolean = .{
+ .value = value.toBoolean(),
+ },
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isNumber()) {
+ return Expr{
+ .data = .{
+ .e_number = .{ .value = value.asNumber() },
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isNull()) {
+ return Expr{
+ .data = .{
+ .e_null = .{},
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isUndefined()) {
+ return Expr{
+ .data = .{
+ .e_undefined = .{},
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isString()) {
+ var str = JSAst.E.String{
+ .data = std.fmt.allocPrint(bun.default_allocator, "{}", .{value.getZigString(globalThis)}) catch unreachable,
+ };
+ var out = bun.default_allocator.create(JSAst.E.String) catch unreachable;
+ out.* = str;
+ return Expr{
+ .data = .{
+ .e_string = out,
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ return null;
+}
+
+fn transformOptionsFromJSC(ctx: JSC.C.JSContextRef, temp_allocator: std.mem.Allocator, args: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) !TranspilerOptions {
+ var globalThis = ctx.ptr();
+ const object = args.next() orelse return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
+ if (object.isUndefinedOrNull()) return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
+
+ args.eat();
+ var allocator = args.arena.allocator();
+
+ var transpiler = TranspilerOptions{
+ .default_loader = .jsx,
+ .transform = default_transform_options,
+ .log = logger.Log.init(allocator),
+ };
+ transpiler.log.level = .warn;
+
+ if (!object.isObject()) {
+ JSC.throwInvalidArguments("Expected an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ if (object.getIfPropertyExists(ctx.ptr(), "define")) |define| {
+ define: {
+ if (define.isUndefinedOrNull()) {
+ break :define;
+ }
+
+ if (!define.isObject()) {
+ JSC.throwInvalidArguments("define must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var array = JSC.C.JSObjectCopyPropertyNames(globalThis.ref(), define.asObjectRef());
+ defer JSC.C.JSPropertyNameArrayRelease(array);
+ const count = JSC.C.JSPropertyNameArrayGetCount(array);
+ // cannot be a temporary because it may be loaded on different threads.
+ var map_entries = allocator.alloc([]u8, count * 2) catch unreachable;
+ var names = map_entries[0..count];
+
+ var values = map_entries[count..];
+
+ var i: usize = 0;
+ while (i < count) : (i += 1) {
+ var property_name_ref = JSC.C.JSPropertyNameArrayGetNameAtIndex(
+ array,
+ i,
+ );
+ defer JSC.C.JSStringRelease(property_name_ref);
+ const prop: []const u8 = JSC.C.JSStringGetCharacters8Ptr(property_name_ref)[0..JSC.C.JSStringGetLength(property_name_ref)];
+ const property_value: JSC.JSValue = JSC.JSValue.fromRef(
+ JSC.C.JSObjectGetProperty(
+ globalThis.ref(),
+ define.asObjectRef(),
+ property_name_ref,
+ null,
+ ),
+ );
+ const value_type = property_value.jsType();
+
+ if (!value_type.isStringLike()) {
+ JSC.throwInvalidArguments("define \"{s}\" must be a JSON string", .{prop}, ctx, exception);
+ return transpiler;
+ }
+ names[i] = allocator.dupe(u8, prop) catch unreachable;
+ var val = JSC.ZigString.init("");
+ property_value.toZigString(&val, globalThis);
+ if (val.len == 0) {
+ val = JSC.ZigString.init("\"\"");
+ }
+ values[i] = std.fmt.allocPrint(allocator, "{}", .{val}) catch unreachable;
+ }
+ transpiler.transform.define = Api.StringMap{
+ .keys = names,
+ .values = values,
+ };
+ }
+ }
+
+ if (object.get(globalThis, "external")) |external| {
+ external: {
+ if (external.isUndefinedOrNull()) break :external;
+
+ const toplevel_type = external.jsType();
+ if (toplevel_type.isStringLike()) {
+ var zig_str = JSC.ZigString.init("");
+ external.toZigString(&zig_str, globalThis);
+ if (zig_str.len == 0) break :external;
+ var single_external = allocator.alloc(string, 1) catch unreachable;
+ single_external[0] = std.fmt.allocPrint(allocator, "{}", .{external}) catch unreachable;
+ transpiler.transform.external = single_external;
+ } else if (toplevel_type.isArray()) {
+ const count = external.getLengthOfArray(globalThis);
+ if (count == 0) break :external;
+
+ var externals = allocator.alloc(string, count) catch unreachable;
+ var iter = external.arrayIterator(globalThis);
+ var i: usize = 0;
+ while (iter.next()) |entry| {
+ if (!entry.jsType().isStringLike()) {
+ JSC.throwInvalidArguments("external must be a string or string[]", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var zig_str = JSC.ZigString.init("");
+ entry.toZigString(&zig_str, globalThis);
+ if (zig_str.len == 0) continue;
+ externals[i] = std.fmt.allocPrint(allocator, "{}", .{external}) catch unreachable;
+ i += 1;
+ }
+
+ transpiler.transform.external = externals[0..i];
+ } else {
+ JSC.throwInvalidArguments("external must be a string or string[]", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+ }
+
+ if (object.get(globalThis, "loader")) |loader| {
+ if (Loader.fromJS(globalThis, loader, exception)) |resolved| {
+ if (!resolved.isJavaScriptLike()) {
+ JSC.throwInvalidArguments("only JavaScript-like loaders supported for now", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ transpiler.default_loader = resolved;
+ }
+
+ if (exception.* != null) {
+ return transpiler;
+ }
+ }
+
+ if (object.get(globalThis, "platform")) |platform| {
+ if (Platform.fromJS(globalThis, platform, exception)) |resolved| {
+ transpiler.transform.platform = resolved.toAPI();
+ }
+
+ if (exception.* != null) {
+ return transpiler;
+ }
+ }
+
+ if (object.get(globalThis, "tsconfig")) |tsconfig| {
+ tsconfig: {
+ if (tsconfig.isUndefinedOrNull()) break :tsconfig;
+ const kind = tsconfig.jsType();
+ var out = JSC.ZigString.init("");
+
+ if (kind.isArray()) {
+ JSC.throwInvalidArguments("tsconfig must be a string or object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ if (!kind.isStringLike()) {
+ tsconfig.jsonStringify(globalThis, 0, &out);
+ } else {
+ tsconfig.toZigString(&out, globalThis);
+ }
+
+ if (out.len == 0) break :tsconfig;
+ transpiler.tsconfig_buf = std.fmt.allocPrint(allocator, "{}", .{out}) catch unreachable;
+
+ // TODO: JSC -> Ast conversion
+ if (TSConfigJSON.parse(
+ allocator,
+ &transpiler.log,
+ logger.Source.initPathString("tsconfig.json", transpiler.tsconfig_buf),
+ &VirtualMachine.vm.bundler.resolver.caches.json,
+ true,
+ ) catch null) |parsed_tsconfig| {
+ transpiler.tsconfig = parsed_tsconfig;
+ }
+ }
+ }
+
+ transpiler.runtime.allow_runtime = false;
+
+ if (object.getIfPropertyExists(globalThis, "macro")) |macros| {
+ macros: {
+ if (macros.isUndefinedOrNull()) break :macros;
+ const kind = macros.jsType();
+ const is_object = kind.isObject();
+ if (!(kind.isStringLike() or is_object)) {
+ JSC.throwInvalidArguments("macro must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var out: ZigString = ZigString.init("");
+ // TODO: write a converter between JSC types and Bun AST types
+ if (is_object) {
+ macros.jsonStringify(globalThis, 0, &out);
+ } else {
+ macros.toZigString(&out, globalThis);
+ }
+
+ if (out.len == 0) break :macros;
+ transpiler.macros_buf = std.fmt.allocPrint(allocator, "{}", .{out}) catch unreachable;
+ const source = logger.Source.initPathString("macros.json", transpiler.macros_buf);
+ const json = (VirtualMachine.vm.bundler.resolver.caches.json.parseJSON(
+ &transpiler.log,
+ source,
+ allocator,
+ ) catch null) orelse break :macros;
+ transpiler.macro_map = PackageJSON.parseMacrosJSON(allocator, json, &transpiler.log, &source);
+ }
+ }
+
+ if (object.get(globalThis, "autoImportJSX")) |flag| {
+ transpiler.runtime.auto_import_jsx = flag.toBoolean();
+ }
+
+ if (object.get(globalThis, "allowBunRuntime")) |flag| {
+ transpiler.runtime.allow_runtime = flag.toBoolean();
+ }
+
+ if (object.get(globalThis, "jsxOptimizationInline")) |flag| {
+ transpiler.runtime.jsx_optimization_inline = flag.toBoolean();
+ }
+
+ if (object.get(globalThis, "jsxOptimizationHoist")) |flag| {
+ transpiler.runtime.jsx_optimization_hoist = flag.toBoolean();
+
+ if (!transpiler.runtime.jsx_optimization_inline and transpiler.runtime.jsx_optimization_hoist) {
+ JSC.throwInvalidArguments("jsxOptimizationHoist requires jsxOptimizationInline", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+
+ if (object.get(globalThis, "sourcemap")) |flag| {
+ if (flag.isBoolean() or flag.isUndefinedOrNull()) {
+ if (flag.toBoolean()) {
+ transpiler.transform.source_map = Api.SourceMapMode.external;
+ } else {
+ transpiler.transform.source_map = Api.SourceMapMode.inline_into_file;
+ }
+ } else {
+ var sourcemap = flag.toSlice(globalThis, allocator);
+ if (options.SourceMapOption.map.get(sourcemap.slice())) |source| {
+ transpiler.transform.source_map = source.toAPI();
+ } else {
+ JSC.throwInvalidArguments("sourcemap must be one of \"inline\", \"external\", or \"none\"", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+ }
+
+ var tree_shaking: ?bool = null;
+ if (object.get(globalThis, "treeShaking")) |treeShaking| {
+ tree_shaking = treeShaking.toBoolean();
+ }
+
+ var trim_unused_imports: ?bool = null;
+ if (object.get(globalThis, "trimUnusedImports")) |trimUnusedImports| {
+ trim_unused_imports = trimUnusedImports.toBoolean();
+ }
+
+ if (object.getTruthy(globalThis, "exports")) |exports| {
+ if (!exports.isObject()) {
+ JSC.throwInvalidArguments("exports must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var replacements = Runtime.Features.ReplaceableExport.Map{};
+ errdefer replacements.clearAndFree(bun.default_allocator);
+
+ if (exports.getTruthy(globalThis, "eliminate")) |eliminate| {
+ if (!eliminate.jsType().isArray()) {
+ JSC.throwInvalidArguments("exports.eliminate must be an array", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var total_name_buf_len: u32 = 0;
+ var string_count: u32 = 0;
+ var iter = JSC.JSArrayIterator.init(eliminate, globalThis);
+ {
+ var length_iter = iter;
+ while (length_iter.next()) |value| {
+ if (value.isString()) {
+ const length = value.getLengthOfArray(globalThis);
+ string_count += @as(u32, @boolToInt(length > 0));
+ total_name_buf_len += length;
+ }
+ }
+ }
+
+ if (total_name_buf_len > 0) {
+ var buf = try std.ArrayListUnmanaged(u8).initCapacity(bun.default_allocator, total_name_buf_len);
+ try replacements.ensureUnusedCapacity(bun.default_allocator, string_count);
+ {
+ var length_iter = iter;
+ while (length_iter.next()) |value| {
+ if (!value.isString()) continue;
+ var str = value.getZigString(globalThis);
+ if (str.len == 0) continue;
+ const name = std.fmt.bufPrint(buf.items.ptr[buf.items.len..buf.capacity], "{}", .{str}) catch {
+ JSC.throwInvalidArguments("Error reading exports.eliminate. TODO: utf-16", .{}, ctx, exception);
+ return transpiler;
+ };
+ buf.items.len += name.len;
+ if (name.len > 0) {
+ replacements.putAssumeCapacity(name, .{ .delete = .{} });
+ }
+ }
+ }
+ }
+ }
+
+ if (exports.getTruthy(globalThis, "replace")) |replace| {
+ if (!replace.isObject()) {
+ JSC.throwInvalidArguments("replace must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var total_name_buf_len: usize = 0;
+
+ var array = js.JSObjectCopyPropertyNames(ctx, replace.asObjectRef());
+ defer js.JSPropertyNameArrayRelease(array);
+ const property_names_count = @intCast(u32, js.JSPropertyNameArrayGetCount(array));
+ var iter = JSC.JSPropertyNameIterator{
+ .array = array,
+ .count = @intCast(u32, property_names_count),
+ };
+
+ {
+ var key_iter = iter;
+ while (key_iter.next()) |item| {
+ total_name_buf_len += JSC.C.JSStringGetLength(item);
+ }
+ }
+
+ if (total_name_buf_len > 0) {
+ var total_name_buf = try std.ArrayList(u8).initCapacity(bun.default_allocator, total_name_buf_len);
+ errdefer total_name_buf.clearAndFree();
+
+ try replacements.ensureUnusedCapacity(bun.default_allocator, property_names_count);
+ defer {
+ if (exception.* != null) {
+ total_name_buf.clearAndFree();
+ replacements.clearAndFree(bun.default_allocator);
+ }
+ }
+
+ while (iter.next()) |item| {
+ const start = total_name_buf.items.len;
+ total_name_buf.items.len += @maximum(
+ // this returns a null terminated string
+ JSC.C.JSStringGetUTF8CString(item, total_name_buf.items.ptr + start, total_name_buf.capacity - start),
+ 1,
+ ) - 1;
+ JSC.C.JSStringRelease(item);
+ const key = total_name_buf.items[start..total_name_buf.items.len];
+ // if somehow the string is empty, skip it
+ if (key.len == 0)
+ continue;
+
+ const value = replace.get(globalThis, key).?;
+ if (value.isEmpty()) continue;
+
+ if (!JSLexer.isIdentifier(key)) {
+ JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{key}, ctx, exception);
+ total_name_buf.deinit();
+ return transpiler;
+ }
+
+ var entry = replacements.getOrPutAssumeCapacity(key);
+
+ if (exportReplacementValue(value, globalThis)) |expr| {
+ entry.value_ptr.* = .{ .replace = expr };
+ continue;
+ }
+
+ if (value.isObject() and value.getLengthOfArray(ctx.ptr()) == 2) {
+ const replacementValue = JSC.JSObject.getIndex(value, globalThis, 1);
+ if (exportReplacementValue(replacementValue, globalThis)) |to_replace| {
+ const replacementKey = JSC.JSObject.getIndex(value, globalThis, 0);
+ var slice = (try replacementKey.toSlice(globalThis, bun.default_allocator).cloneIfNeeded());
+ var replacement_name = slice.slice();
+
+ if (!JSLexer.isIdentifier(replacement_name)) {
+ JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{replacement_name}, ctx, exception);
+ total_name_buf.deinit();
+ slice.deinit();
+ return transpiler;
+ }
+
+ entry.value_ptr.* = .{
+ .inject = .{
+ .name = replacement_name,
+ .value = to_replace,
+ },
+ };
+ continue;
+ }
+ }
+
+ JSC.throwInvalidArguments("exports.replace values can only be string, null, undefined, number or boolean", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+ }
+
+ tree_shaking = tree_shaking orelse (replacements.count() > 0);
+ transpiler.runtime.replace_exports = replacements;
+ }
+
+ transpiler.tree_shaking = tree_shaking orelse false;
+ transpiler.trim_unused_imports = trim_unused_imports orelse transpiler.tree_shaking;
+
+ return transpiler;
+}
+
+pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) js.JSObjectRef {
+ var temp = std.heap.ArenaAllocator.init(getAllocator(ctx));
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer temp.deinit();
+ const transpiler_options: TranspilerOptions = if (arguments.len > 0)
+ transformOptionsFromJSC(ctx, temp.allocator(), &args, exception) catch {
+ JSC.throwInvalidArguments("Failed to create transpiler", .{}, ctx, exception);
+ return null;
+ }
+ else
+ TranspilerOptions{ .log = logger.Log.init(getAllocator(ctx)) };
+
+ if (exception.* != null) {
+ return null;
+ }
+
+ if ((transpiler_options.log.warnings + transpiler_options.log.errors) > 0) {
+ var out_exception = transpiler_options.log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to create transpiler");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ var log = getAllocator(ctx).create(logger.Log) catch unreachable;
+ log.* = transpiler_options.log;
+ var bundler = Bundler.Bundler.init(
+ getAllocator(ctx),
+ log,
+ transpiler_options.transform,
+ null,
+ JavaScript.VirtualMachine.vm.bundler.env,
+ ) catch |err| {
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to create transpiler");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Error creating transpiler: {s}", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+
+ bundler.configureLinkerWithAutoJSX(false);
+ bundler.options.env.behavior = .disable;
+ bundler.configureDefines() catch |err| {
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to load define");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to load define: {s}", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+
+ if (transpiler_options.macro_map.count() > 0) {
+ bundler.options.macro_remap = transpiler_options.macro_map;
+ }
+
+ bundler.options.tree_shaking = transpiler_options.tree_shaking;
+ bundler.options.trim_unused_imports = transpiler_options.trim_unused_imports;
+ bundler.options.allow_runtime = transpiler_options.runtime.allow_runtime;
+ bundler.options.auto_import_jsx = transpiler_options.runtime.auto_import_jsx;
+ bundler.options.hot_module_reloading = transpiler_options.runtime.hot_module_reloading;
+ bundler.options.jsx.supports_fast_refresh = bundler.options.hot_module_reloading and
+ bundler.options.allow_runtime and transpiler_options.runtime.react_fast_refresh;
+
+ var transpiler = getAllocator(ctx).create(Transpiler) catch unreachable;
+ transpiler.* = Transpiler{
+ .transpiler_options = transpiler_options,
+ .bundler = bundler,
+ .arena = args.arena,
+ .scan_pass_result = ScanPassResult.init(getAllocator(ctx)),
+ };
+
+ return Class.make(ctx, transpiler);
+}
+
+pub fn finalize(
+ this: *Transpiler,
+) void {
+ this.bundler.log.deinit();
+ this.scan_pass_result.named_imports.deinit();
+ this.scan_pass_result.import_records.deinit();
+ this.scan_pass_result.used_symbols.deinit();
+ if (this.buffer_writer != null) {
+ this.buffer_writer.?.buffer.deinit();
+ }
+
+ // bun.default_allocator.free(this.transpiler_options.tsconfig_buf);
+ // bun.default_allocator.free(this.transpiler_options.macros_buf);
+ this.arena.deinit();
+}
+
+fn getParseResult(this: *Transpiler, allocator: std.mem.Allocator, code: []const u8, loader: ?Loader, macro_js_ctx: JSValue) ?Bundler.ParseResult {
+ const name = this.transpiler_options.default_loader.stdinName();
+ const source = logger.Source.initPathString(name, code);
+
+ const jsx = if (this.transpiler_options.tsconfig != null)
+ this.transpiler_options.tsconfig.?.mergeJSX(this.bundler.options.jsx)
+ else
+ this.bundler.options.jsx;
+
+ const parse_options = Bundler.Bundler.ParseOptions{
+ .allocator = allocator,
+ .macro_remappings = this.transpiler_options.macro_map,
+ .dirname_fd = 0,
+ .file_descriptor = null,
+ .loader = loader orelse this.transpiler_options.default_loader,
+ .jsx = jsx,
+ .path = source.path,
+ .virtual_source = &source,
+ .replace_exports = this.transpiler_options.runtime.replace_exports,
+ .macro_js_ctx = macro_js_ctx,
+ // .allocator = this.
+ };
+
+ var parse_result = this.bundler.parse(parse_options, null);
+
+ // necessary because we don't run the linker
+ if (parse_result) |*res| {
+ for (res.ast.import_records) |*import| {
+ if (import.kind.isCommonJS()) {
+ import.wrap_with_to_module = true;
+ import.module_id = @truncate(u32, std.hash.Wyhash.hash(0, import.path.pretty));
+ }
+ }
+ }
+
+ return parse_result;
+}
+
+pub fn scan(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer args.arena.deinit();
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), args.arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code = code_holder.slice();
+ args.eat();
+ const loader: ?Loader = brk: {
+ if (args.next()) |arg| {
+ args.eat();
+ break :brk Loader.fromJS(ctx.ptr(), arg, exception);
+ }
+
+ break :brk null;
+ };
+
+ if (exception.* != null) return null;
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+ var prev_allocator = this.bundler.allocator;
+ this.bundler.setAllocator(arena.allocator());
+ var log = logger.Log.init(arena.backingAllocator());
+ defer log.deinit();
+ this.bundler.setLog(&log);
+ defer {
+ this.bundler.setLog(&this.transpiler_options.log);
+ this.bundler.setAllocator(prev_allocator);
+ arena.deinit();
+ }
+
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ }
+
+ const parse_result = getParseResult(this, arena.allocator(), code, loader, JSC.JSValue.zero) orelse {
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to parse", .{}, ctx, exception);
+ return null;
+ };
+
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ const exports_label = JSC.ZigString.init("exports");
+ const imports_label = JSC.ZigString.init("imports");
+ const named_imports_value = namedImportsToJS(
+ ctx.ptr(),
+ parse_result.ast.import_records,
+ exception,
+ );
+ if (exception.* != null) return null;
+ var named_exports_value = namedExportsToJS(
+ ctx.ptr(),
+ parse_result.ast.named_exports,
+ );
+ return JSC.JSValue.createObject2(ctx.ptr(), &imports_label, &exports_label, named_imports_value, named_exports_value).asObjectRef();
+}
+
+// pub fn build(
+// this: *Transpiler,
+// ctx: js.JSContextRef,
+// _: js.JSObjectRef,
+// _: js.JSObjectRef,
+// arguments: []const js.JSValueRef,
+// exception: js.ExceptionRef,
+// ) JSC.C.JSObjectRef {}
+
+pub fn transform(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer args.arena.deinit();
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), this.arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code = code_holder.slice();
+ args.eat();
+ const loader: ?Loader = brk: {
+ if (args.next()) |arg| {
+ args.eat();
+ break :brk Loader.fromJS(ctx.ptr(), arg, exception);
+ }
+
+ break :brk null;
+ };
+
+ if (exception.* != null) return null;
+ if (code_holder == .string) {
+ JSC.C.JSValueProtect(ctx, arguments[0]);
+ }
+
+ var task = TransformTask.create(this, if (code_holder == .string) arguments[0] else null, ctx.ptr(), ZigString.init(code), loader orelse this.transpiler_options.default_loader) catch return null;
+ task.schedule();
+ return task.promise.asObjectRef();
+}
+
+pub fn transformSync(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ defer args.arena.deinit();
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+ defer arena.deinit();
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code = code_holder.slice();
+ JSC.JSValue.c(arguments[0]).ensureStillAlive();
+ defer JSC.JSValue.c(arguments[0]).ensureStillAlive();
+
+ args.eat();
+ var js_ctx_value: JSC.JSValue = JSC.JSValue.zero;
+ const loader: ?Loader = brk: {
+ if (args.next()) |arg| {
+ args.eat();
+ if (arg.isNumber() or arg.isString()) {
+ break :brk Loader.fromJS(ctx.ptr(), arg, exception);
+ }
+
+ if (arg.isObject()) {
+ js_ctx_value = arg;
+ break :brk null;
+ }
+ }
+
+ break :brk null;
+ };
+
+ if (args.nextEat()) |arg| {
+ if (arg.isObject()) {
+ js_ctx_value = arg;
+ } else {
+ JSC.throwInvalidArguments("Expected a Loader or object", .{}, ctx, exception);
+ return null;
+ }
+ }
+ if (!js_ctx_value.isEmpty()) {
+ js_ctx_value.ensureStillAlive();
+ }
+
+ defer {
+ if (!js_ctx_value.isEmpty()) {
+ js_ctx_value.ensureStillAlive();
+ }
+ }
+
+ if (exception.* != null) return null;
+
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ }
+
+ var prev_bundler = this.bundler;
+ this.bundler.setAllocator(arena.allocator());
+ this.bundler.macro_context = null;
+ var log = logger.Log.init(arena.backingAllocator());
+ this.bundler.setLog(&log);
+
+ defer {
+ this.bundler = prev_bundler;
+ }
+
+ var parse_result = getParseResult(
+ this,
+ arena.allocator(),
+ code,
+ loader,
+ js_ctx_value,
+ ) orelse {
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to parse", .{}, ctx, exception);
+ return null;
+ };
+
+ if ((this.bundler.log.warnings + this.bundler.log.errors) > 0) {
+ var out_exception = this.bundler.log.toJS(ctx.ptr(), getAllocator(ctx), "Parse error");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ var buffer_writer = this.buffer_writer orelse brk: {
+ var writer = JSPrinter.BufferWriter.init(arena.backingAllocator()) catch {
+ JSC.throwInvalidArguments("Failed to create BufferWriter", .{}, ctx, exception);
+ return null;
+ };
+
+ writer.buffer.growIfNeeded(code.len) catch unreachable;
+ writer.buffer.list.expandToCapacity();
+ break :brk writer;
+ };
+
+ defer {
+ this.buffer_writer = buffer_writer;
+ }
+
+ buffer_writer.reset();
+ var printer = JSPrinter.BufferPrinter.init(buffer_writer);
+ _ = this.bundler.print(parse_result, @TypeOf(&printer), &printer, .esm_ascii) catch |err| {
+ JSC.JSError(bun.default_allocator, "Failed to print code: {s}", .{@errorName(err)}, ctx, exception);
+
+ return null;
+ };
+
+ // TODO: benchmark if pooling this way is faster or moving is faster
+ buffer_writer = printer.ctx;
+ var out = JSC.ZigString.init(buffer_writer.written);
+ out.mark();
+
+ return out.toValueGC(ctx.ptr()).asObjectRef();
+}
+
+fn namedExportsToJS(global: *JSGlobalObject, named_exports: JSAst.Ast.NamedExports) JSC.JSValue {
+ if (named_exports.count() == 0)
+ return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArray(global.ref(), 0, null, null));
+
+ var named_exports_iter = named_exports.iterator();
+ var stack_fallback = std.heap.stackFallback(@sizeOf(JSC.ZigString) * 32, getAllocator(global.ref()));
+ var allocator = stack_fallback.get();
+ var names = allocator.alloc(
+ JSC.ZigString,
+ named_exports.count(),
+ ) catch unreachable;
+ defer allocator.free(names);
+ var i: usize = 0;
+ while (named_exports_iter.next()) |entry| {
+ names[i] = JSC.ZigString.init(entry.key_ptr.*);
+ i += 1;
+ }
+ JSC.ZigString.sortAsc(names[0..i]);
+ return JSC.JSValue.createStringArray(global, names.ptr, names.len, true);
+}
+
+const ImportRecord = @import("../../import_record.zig").ImportRecord;
+
+fn namedImportsToJS(
+ global: *JSGlobalObject,
+ import_records: []const ImportRecord,
+ exception: JSC.C.ExceptionRef,
+) JSC.JSValue {
+ var stack_fallback = std.heap.stackFallback(@sizeOf(JSC.C.JSObjectRef) * 32, getAllocator(global.ref()));
+ var allocator = stack_fallback.get();
+
+ var i: usize = 0;
+ const path_label = JSC.ZigString.init("path");
+ const kind_label = JSC.ZigString.init("kind");
+ var array_items = allocator.alloc(
+ JSC.C.JSValueRef,
+ import_records.len,
+ ) catch unreachable;
+ defer allocator.free(array_items);
+
+ for (import_records) |record| {
+ if (record.is_internal) continue;
+
+ const path = JSC.ZigString.init(record.path.text).toValueGC(global);
+ const kind = JSC.ZigString.init(record.kind.label()).toValue(global);
+ array_items[i] = JSC.JSValue.createObject2(global, &path_label, &kind_label, path, kind).asObjectRef();
+ i += 1;
+ }
+
+ return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArray(global.ref(), i, array_items.ptr, exception));
+}
+
+pub fn scanImports(
+ this: *Transpiler,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+) JSC.C.JSObjectRef {
+ var args = JSC.Node.ArgumentsSlice.init(ctx.bunVM(), @ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
+ const code_arg = args.next() orelse {
+ JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+
+ const code_holder = JSC.Node.StringOrBuffer.fromJS(ctx.ptr(), args.arena.allocator(), code_arg, exception) orelse {
+ if (exception.* == null) JSC.throwInvalidArguments("Expected a string or Uint8Array", .{}, ctx, exception);
+ return null;
+ };
+ args.eat();
+ const code = code_holder.slice();
+
+ var loader: Loader = this.transpiler_options.default_loader;
+ if (args.next()) |arg| {
+ if (Loader.fromJS(ctx.ptr(), arg, exception)) |_loader| {
+ loader = _loader;
+ }
+ args.eat();
+ }
+
+ if (!loader.isJavaScriptLike()) {
+ JSC.throwInvalidArguments("Only JavaScript-like files support this fast path", .{}, ctx, exception);
+ return null;
+ }
+
+ if (exception.* != null) return null;
+
+ var arena = Mimalloc.Arena.init() catch unreachable;
+ var prev_allocator = this.bundler.allocator;
+ this.bundler.setAllocator(arena.allocator());
+ var log = logger.Log.init(arena.backingAllocator());
+ defer log.deinit();
+ this.bundler.setLog(&log);
+ defer {
+ this.bundler.setLog(&this.transpiler_options.log);
+ this.bundler.setAllocator(prev_allocator);
+ arena.deinit();
+ }
+
+ const source = logger.Source.initPathString(loader.stdinName(), code);
+ var bundler = &this.bundler;
+ const jsx = if (this.transpiler_options.tsconfig != null)
+ this.transpiler_options.tsconfig.?.mergeJSX(this.bundler.options.jsx)
+ else
+ this.bundler.options.jsx;
+
+ var opts = JSParser.Parser.Options.init(jsx, loader);
+ if (this.bundler.macro_context == null) {
+ this.bundler.macro_context = JSAst.Macro.MacroContext.init(&this.bundler);
+ }
+ opts.macro_context = &this.bundler.macro_context.?;
+
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+
+ defer {
+ JSAst.Stmt.Data.Store.reset();
+ JSAst.Expr.Data.Store.reset();
+ }
+
+ bundler.resolver.caches.js.scan(
+ bundler.allocator,
+ &this.scan_pass_result,
+ opts,
+ bundler.options.define,
+ &log,
+ &source,
+ ) catch |err| {
+ defer this.scan_pass_result.reset();
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to scan imports");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ JSC.throwInvalidArguments("Failed to scan imports: {s}", .{@errorName(err)}, ctx, exception);
+ return null;
+ };
+
+ defer this.scan_pass_result.reset();
+
+ if ((log.warnings + log.errors) > 0) {
+ var out_exception = log.toJS(ctx.ptr(), getAllocator(ctx), "Failed to scan imports");
+ exception.* = out_exception.asObjectRef();
+ return null;
+ }
+
+ const named_imports_value = namedImportsToJS(
+ ctx.ptr(),
+ this.scan_pass_result.import_records.items,
+ exception,
+ );
+ if (exception.* != null) return null;
+ return named_imports_value.asObjectRef();
+}