diff options
Diffstat (limited to 'src/javascript/jsc/api/transpiler.zig')
-rw-r--r-- | src/javascript/jsc/api/transpiler.zig | 1304 |
1 files changed, 0 insertions, 1304 deletions
diff --git a/src/javascript/jsc/api/transpiler.zig b/src/javascript/jsc/api/transpiler.zig deleted file mode 100644 index 69732c643..000000000 --- a/src/javascript/jsc/api/transpiler.zig +++ /dev/null @@ -1,1304 +0,0 @@ -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(); -} |