From dfc36a82554f5a9220639b01fc0b14341a175e02 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Fri, 23 Jan 2026 10:43:22 -0800 Subject: [PATCH] start laying groundwork --- src/OutputFile.zig | 3 + src/StandaloneModuleGraph.zig | 18 ++++ src/bake/production.zig | 1 + src/bun.js/AsyncModule.zig | 1 + src/bun.js/ModuleLoader.zig | 7 ++ src/bun.js/RuntimeTranspilerCache.zig | 84 ++++++++++++++----- src/bun.js/RuntimeTranspilerStore.zig | 28 ++++++- src/bun.js/api/JSBundler.zig | 13 ++- src/bun.js/bindings/ResolvedSource.zig | 4 + src/bun.js/bindings/headers-handwritten.h | 1 + src/bundler/Chunk.zig | 3 + src/bundler/bundle_v2.zig | 1 + .../linker_context/OutputFileListBuilder.zig | 5 +- src/cli/Arguments.zig | 5 +- src/cli/build_command.zig | 2 + src/transpiler.zig | 7 ++ 16 files changed, 152 insertions(+), 31 deletions(-) diff --git a/src/OutputFile.zig b/src/OutputFile.zig index 1eb0fb90d2..b1aa8ac726 100644 --- a/src/OutputFile.zig +++ b/src/OutputFile.zig @@ -15,6 +15,7 @@ hash: u64 = 0, is_executable: bool = false, source_map_index: u32 = std.math.maxInt(u32), bytecode_index: u32 = std.math.maxInt(u32), +module_info_index: u32 = std.math.maxInt(u32), output_kind: jsc.API.BuildArtifact.OutputKind, /// Relative dest_path: []const u8 = "", @@ -210,6 +211,7 @@ pub const Options = struct { hash: ?u64 = null, source_map_index: ?u32 = null, bytecode_index: ?u32 = null, + module_info_index: ?u32 = null, output_path: string, source_index: Index.Optional = .none, size: ?usize = null, @@ -252,6 +254,7 @@ pub fn init(options: Options) OutputFile { .output_kind = options.output_kind, .bytecode_index = options.bytecode_index orelse std.math.maxInt(u32), .source_map_index = options.source_map_index orelse std.math.maxInt(u32), + .module_info_index = options.module_info_index orelse std.math.maxInt(u32), .is_executable = options.is_executable, .value = switch (options.data) { .buffer => |buffer| Value{ .buffer = .{ .allocator = buffer.allocator, .bytes = buffer.data } }, diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index b4123564cd..897fdd2a68 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -92,6 +92,7 @@ pub const StandaloneModuleGraph = struct { contents: Schema.StringPointer = .{}, sourcemap: Schema.StringPointer = .{}, bytecode: Schema.StringPointer = .{}, + module_info: Schema.StringPointer = .{}, encoding: Encoding = .latin1, loader: bun.options.Loader = .file, module_format: ModuleFormat = .none, @@ -159,6 +160,7 @@ pub const StandaloneModuleGraph = struct { encoding: Encoding = .binary, wtf_string: bun.String = bun.String.empty, bytecode: []u8 = "", + module_info: []u8 = "", module_format: ModuleFormat = .none, side: FileSide = .server, @@ -333,6 +335,7 @@ pub const StandaloneModuleGraph = struct { else .none, .bytecode = if (module.bytecode.length > 0) @constCast(sliceTo(raw_bytes, module.bytecode)) else &.{}, + .module_info = if (module.module_info.length > 0) @constCast(sliceTo(raw_bytes, module.module_info)) else &.{}, .module_format = module.module_format, .side = module.side, }, @@ -477,6 +480,20 @@ pub const StandaloneModuleGraph = struct { } }; + // Embed module_info for ESM bytecode (alignment is handled in serialization format) + const module_info: StringPointer = brk: { + if (output_file.module_info_index != std.math.maxInt(u32)) { + const mi_bytes = output_files[output_file.module_info_index].value.buffer.bytes; + const offset = string_builder.len; + const writable = string_builder.writable(); + @memcpy(writable[0..mi_bytes.len], mi_bytes[0..mi_bytes.len]); + string_builder.len += mi_bytes.len; + break :brk StringPointer{ .offset = @truncate(offset), .length = @truncate(mi_bytes.len) }; + } else { + break :brk .{}; + } + }; + if (comptime bun.Environment.is_canary or bun.Environment.isDebug) { if (bun.env_var.BUN_FEATURE_FLAG_DUMP_CODE.get()) |dump_code_dir| { const buf = bun.path_buffer_pool.get(); @@ -515,6 +532,7 @@ pub const StandaloneModuleGraph = struct { else => .none, } else .none, .bytecode = bytecode, + .module_info = module_info, .side = switch (output_file.side orelse .server) { .server => .server, .client => .client, diff --git a/src/bake/production.zig b/src/bake/production.zig index 3c18ac7d95..3d1d0f4cff 100644 --- a/src/bake/production.zig +++ b/src/bake/production.zig @@ -433,6 +433,7 @@ pub fn buildWithVm(ctx: bun.cli.Command.Context, cwd: []const u8, vm: *VirtualMa .asset => {}, .bytecode => {}, .sourcemap => {}, + .module_info => {}, } }, } diff --git a/src/bun.js/AsyncModule.zig b/src/bun.js/AsyncModule.zig index 054a9d0c4e..cd0068b507 100644 --- a/src/bun.js/AsyncModule.zig +++ b/src/bun.js/AsyncModule.zig @@ -694,6 +694,7 @@ pub const AsyncModule = struct { &printer, .esm_ascii, mapper.get(), + null, ); } diff --git a/src/bun.js/ModuleLoader.zig b/src/bun.js/ModuleLoader.zig index 515906df97..199d676584 100644 --- a/src/bun.js/ModuleLoader.zig +++ b/src/bun.js/ModuleLoader.zig @@ -178,6 +178,7 @@ pub fn transpileSourceCode( var cache = jsc.RuntimeTranspilerCache{ .output_code_allocator = allocator, .sourcemap_allocator = bun.default_allocator, + .esm_record_allocator = bun.default_allocator, }; const old = jsc_vm.transpiler.log; @@ -516,6 +517,7 @@ pub fn transpileSourceCode( &printer, .esm_ascii, mapper.get(), + null, ); }; @@ -1195,6 +1197,10 @@ pub fn fetchBuiltinModule(jsc_vm: *VirtualMachine, specifier: bun.String) !?Reso .source_code_needs_deref = false, .bytecode_cache = if (file.bytecode.len > 0) file.bytecode.ptr else null, .bytecode_cache_size = file.bytecode.len, + .module_info = if (file.module_info.len > 0) + analyze_transpiled_module.ModuleInfoDeserialized.create(file.module_info, bun.default_allocator) catch null + else + null, .is_commonjs_module = file.module_format == .cjs, }; } @@ -1338,6 +1344,7 @@ 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 analyze_transpiled_module = @import("../analyze_transpiled_module.zig"); const bun = @import("bun"); const Environment = bun.Environment; diff --git a/src/bun.js/RuntimeTranspilerCache.zig b/src/bun.js/RuntimeTranspilerCache.zig index 3f8125de1a..2b0224d287 100644 --- a/src/bun.js/RuntimeTranspilerCache.zig +++ b/src/bun.js/RuntimeTranspilerCache.zig @@ -14,7 +14,8 @@ /// Version 15: Updated global defines table list. /// Version 16: Added typeof undefined minification optimization. /// Version 17: Removed transpiler import rewrite for bun:test. Not bumping it causes test/js/bun/http/req-url-leak.test.ts to fail with SyntaxError: Export named 'expect' not found in module 'bun:test'. -const expected_version = 17; +/// Version 18: Support for ESM bytecode +const expected_version = 18; const debug = Output.scoped(.cache, .visible); const MINIMUM_CACHE_SIZE = 50 * 1024; @@ -32,6 +33,7 @@ pub const RuntimeTranspilerCache = struct { sourcemap_allocator: std.mem.Allocator, output_code_allocator: std.mem.Allocator, + esm_record_allocator: std.mem.Allocator, const seed = 42; pub const Metadata = struct { @@ -52,6 +54,10 @@ pub const RuntimeTranspilerCache = struct { sourcemap_byte_length: u64 = 0, sourcemap_hash: u64 = 0, + esm_record_byte_offset: u64 = 0, + esm_record_byte_length: u64 = 0, + esm_record_hash: u64 = 0, + pub const size = brk: { var count: usize = 0; const meta: Metadata = .{}; @@ -78,6 +84,10 @@ pub const RuntimeTranspilerCache = struct { try writer.writeInt(u64, this.sourcemap_byte_offset, .little); try writer.writeInt(u64, this.sourcemap_byte_length, .little); try writer.writeInt(u64, this.sourcemap_hash, .little); + + try writer.writeInt(u64, this.esm_record_byte_offset, .little); + try writer.writeInt(u64, this.esm_record_byte_length, .little); + try writer.writeInt(u64, this.esm_record_hash, .little); } pub fn decode(this: *Metadata, reader: anytype) !void { @@ -102,6 +112,10 @@ pub const RuntimeTranspilerCache = struct { this.sourcemap_byte_length = try reader.readInt(u64, .little); this.sourcemap_hash = try reader.readInt(u64, .little); + this.esm_record_byte_offset = try reader.readInt(u64, .little); + this.esm_record_byte_length = try reader.readInt(u64, .little); + this.esm_record_hash = try reader.readInt(u64, .little); + switch (this.module_type) { .esm, .cjs => {}, // Invalid module type @@ -120,6 +134,7 @@ pub const RuntimeTranspilerCache = struct { metadata: Metadata, output_code: OutputCode = .{ .utf8 = "" }, sourcemap: []const u8 = "", + esm_record: []const u8 = "", pub const OutputCode = union(enum) { utf8: []const u8, @@ -142,11 +157,14 @@ pub const RuntimeTranspilerCache = struct { } }; - pub fn deinit(this: *Entry, sourcemap_allocator: std.mem.Allocator, output_code_allocator: std.mem.Allocator) void { + pub fn deinit(this: *Entry, sourcemap_allocator: std.mem.Allocator, output_code_allocator: std.mem.Allocator, esm_record_allocator: std.mem.Allocator) void { this.output_code.deinit(output_code_allocator); if (this.sourcemap.len > 0) { sourcemap_allocator.free(this.sourcemap); } + if (this.esm_record.len > 0) { + esm_record_allocator.free(this.esm_record); + } } pub fn save( @@ -156,6 +174,7 @@ pub const RuntimeTranspilerCache = struct { input_hash: u64, features_hash: u64, sourcemap: []const u8, + esm_record: []const u8, output_code: OutputCode, exports_kind: bun.ast.ExportsKind, ) !void { @@ -201,6 +220,8 @@ pub const RuntimeTranspilerCache = struct { .output_byte_offset = Metadata.size, .output_byte_length = output_bytes.len, .sourcemap_byte_offset = Metadata.size + output_bytes.len, + .esm_record_byte_offset = Metadata.size + output_bytes.len + sourcemap.len, + .esm_record_byte_length = esm_record.len, }; metadata.output_hash = hash(output_bytes); @@ -219,20 +240,26 @@ pub const RuntimeTranspilerCache = struct { break :brk metadata_buf[0..metadata_stream.pos]; }; - const vecs: []const bun.PlatformIOVecConst = if (output_bytes.len > 0) - &.{ - bun.platformIOVecConstCreate(metadata_bytes), - bun.platformIOVecConstCreate(output_bytes), - bun.platformIOVecConstCreate(sourcemap), - } - else - &.{ - bun.platformIOVecConstCreate(metadata_bytes), - bun.platformIOVecConstCreate(sourcemap), - }; + var vecs_buf: [4]bun.PlatformIOVecConst = undefined; + var vecs_i: usize = 0; + vecs_buf[vecs_i] = bun.platformIOVecConstCreate(metadata_bytes); + vecs_i += 1; + if (output_bytes.len > 0) { + vecs_buf[vecs_i] = bun.platformIOVecConstCreate(output_bytes); + vecs_i += 1; + } + if (sourcemap.len > 0) { + vecs_buf[vecs_i] = bun.platformIOVecConstCreate(sourcemap); + vecs_i += 1; + } + if (esm_record.len > 0) { + vecs_buf[vecs_i] = bun.platformIOVecConstCreate(esm_record); + vecs_i += 1; + } + const vecs: []const bun.PlatformIOVecConst = vecs_buf[0..vecs_i]; var position: isize = 0; - const end_position = Metadata.size + output_bytes.len + sourcemap.len; + const end_position = Metadata.size + output_bytes.len + sourcemap.len + esm_record.len; if (bun.Environment.allow_assert) { var total: usize = 0; @@ -242,7 +269,7 @@ pub const RuntimeTranspilerCache = struct { } bun.assert(end_position == total); } - bun.assert(end_position == @as(i64, @intCast(sourcemap.len + output_bytes.len + Metadata.size))); + bun.assert(end_position == @as(i64, @intCast(sourcemap.len + output_bytes.len + esm_record.len + Metadata.size))); bun.sys.preallocate_file(tmpfile.fd.cast(), 0, @intCast(end_position)) catch {}; while (position < end_position) { @@ -263,6 +290,7 @@ pub const RuntimeTranspilerCache = struct { file: std.fs.File, sourcemap_allocator: std.mem.Allocator, output_code_allocator: std.mem.Allocator, + esm_record_allocator: std.mem.Allocator, ) !void { const stat_size = try file.getEndPos(); if (stat_size < Metadata.size + this.metadata.output_byte_length + this.metadata.sourcemap_byte_length) { @@ -338,6 +366,17 @@ pub const RuntimeTranspilerCache = struct { this.sourcemap = sourcemap; } + + if (this.metadata.esm_record_byte_length > 0) { + const esm_record = try esm_record_allocator.alloc(u8, this.metadata.esm_record_byte_length); + errdefer esm_record_allocator.free(esm_record); + const read_bytes = try file.preadAll(esm_record, this.metadata.esm_record_byte_offset); + if (read_bytes != this.metadata.esm_record_byte_length) { + return error.MissingData; + } + + this.esm_record = esm_record; + } } }; @@ -455,6 +494,7 @@ pub const RuntimeTranspilerCache = struct { input_stat_size: u64, sourcemap_allocator: std.mem.Allocator, output_code_allocator: std.mem.Allocator, + esm_record_allocator: std.mem.Allocator, ) !Entry { var tracer = bun.perf.trace("RuntimeTranspilerCache.fromFile"); defer tracer.end(); @@ -469,6 +509,7 @@ pub const RuntimeTranspilerCache = struct { input_stat_size, sourcemap_allocator, output_code_allocator, + esm_record_allocator, ); } @@ -479,6 +520,7 @@ pub const RuntimeTranspilerCache = struct { input_stat_size: u64, sourcemap_allocator: std.mem.Allocator, output_code_allocator: std.mem.Allocator, + esm_record_allocator: std.mem.Allocator, ) !Entry { var metadata_bytes_buf: [Metadata.size * 2]u8 = undefined; const cache_fd = try bun.sys.open(cache_file_path.sliceAssumeZ(), bun.O.RDONLY, 0).unwrap(); @@ -510,7 +552,7 @@ pub const RuntimeTranspilerCache = struct { return error.MismatchedFeatureHash; } - try entry.load(file, sourcemap_allocator, output_code_allocator); + try entry.load(file, sourcemap_allocator, output_code_allocator, esm_record_allocator); return entry; } @@ -527,6 +569,7 @@ pub const RuntimeTranspilerCache = struct { input_hash: u64, features_hash: u64, sourcemap: []const u8, + esm_record: []const u8, source_code: bun.String, exports_kind: bun.ast.ExportsKind, ) !void { @@ -566,6 +609,7 @@ pub const RuntimeTranspilerCache = struct { input_hash, features_hash, sourcemap, + esm_record, output_code, exports_kind, ); @@ -599,7 +643,7 @@ pub const RuntimeTranspilerCache = struct { parser_options.hashForRuntimeTranspiler(&features_hasher, used_jsx); this.features_hash = features_hasher.final(); - this.entry = fromFile(input_hash, this.features_hash.?, source.contents.len, this.sourcemap_allocator, this.output_code_allocator) catch |err| { + this.entry = fromFile(input_hash, this.features_hash.?, source.contents.len, this.sourcemap_allocator, this.output_code_allocator, this.esm_record_allocator) catch |err| { debug("get(\"{s}\") = {s}", .{ source.path.text, @errorName(err) }); return false; }; @@ -615,7 +659,7 @@ pub const RuntimeTranspilerCache = struct { if (comptime bun.Environment.isDebug) { if (!bun_debug_restore_from_cache) { if (this.entry) |*entry| { - entry.deinit(this.sourcemap_allocator, this.output_code_allocator); + entry.deinit(this.sourcemap_allocator, this.output_code_allocator, this.esm_record_allocator); this.entry = null; } } @@ -624,7 +668,7 @@ pub const RuntimeTranspilerCache = struct { return this.entry != null; } - pub fn put(this: *RuntimeTranspilerCache, output_code_bytes: []const u8, sourcemap: []const u8) void { + pub fn put(this: *RuntimeTranspilerCache, output_code_bytes: []const u8, sourcemap: []const u8, esm_record: []const u8) void { if (comptime !bun.FeatureFlags.runtime_transpiler_cache) @compileError("RuntimeTranspilerCache is disabled"); @@ -635,7 +679,7 @@ pub const RuntimeTranspilerCache = struct { const output_code = bun.String.cloneLatin1(output_code_bytes); this.output_code = output_code; - toFile(this.input_byte_length.?, this.input_hash.?, this.features_hash.?, sourcemap, output_code, this.exports_kind) catch |err| { + toFile(this.input_byte_length.?, this.input_hash.?, this.features_hash.?, sourcemap, esm_record, output_code, this.exports_kind) catch |err| { debug("put() = {s}", .{@errorName(err)}); return; }; diff --git a/src/bun.js/RuntimeTranspilerStore.zig b/src/bun.js/RuntimeTranspilerStore.zig index b43de4516a..3968a93e16 100644 --- a/src/bun.js/RuntimeTranspilerStore.zig +++ b/src/bun.js/RuntimeTranspilerStore.zig @@ -315,6 +315,7 @@ pub const RuntimeTranspilerStore = struct { var cache = jsc.RuntimeTranspilerCache{ .output_code_allocator = allocator, .sourcemap_allocator = bun.default_allocator, + .esm_record_allocator = bun.default_allocator, }; var log = logger.Log.init(allocator); defer { @@ -471,6 +472,14 @@ pub const RuntimeTranspilerStore = struct { dumpSourceString(vm, specifier, entry.output_code.byteSlice()); } + var module_info: ?*analyze_transpiled_module.ModuleInfoDeserialized = null; + if (entry.esm_record.len > 0) { + if (entry.metadata.module_type == .cjs) { + @panic("TranspilerCache contained cjs module with module info"); + } + module_info = bun.handleOom(analyze_transpiled_module.ModuleInfoDeserialized.create(entry.esm_record, bun.default_allocator)) catch null; + } + this.resolved_source = ResolvedSource{ .allocator = null, .source_code = switch (entry.output_code) { @@ -483,6 +492,7 @@ pub const RuntimeTranspilerStore = struct { }, }, .is_commonjs_module = entry.metadata.module_type == .cjs, + .module_info = module_info, .tag = this.resolved_source.tag, }; @@ -541,6 +551,10 @@ pub const RuntimeTranspilerStore = struct { printer = source_code_printer.?.*; } + const is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs; + const is_typescript = loader == .ts or loader == .tsx; + const module_info: ?*analyze_transpiled_module.ModuleInfo = if (is_commonjs_module) null else bun.handleOom(analyze_transpiled_module.ModuleInfo.create(bun.default_allocator, is_typescript)); + { var mapper = vm.sourceMapHandler(&printer); defer source_code_printer.?.* = printer; @@ -550,12 +564,22 @@ pub const RuntimeTranspilerStore = struct { &printer, .esm_ascii, mapper.get(), + module_info, ) catch |err| { this.parse_error = err; return; }; } + if (module_info) |mi| { + if (!mi.finalized) { + mi.finalize() catch { + mi.destroy(); + return; + }; + } + } + if (comptime Environment.dump_source) { dumpSource(this.vm, specifier, &printer); } @@ -589,13 +613,15 @@ pub const RuntimeTranspilerStore = struct { this.resolved_source = ResolvedSource{ .allocator = null, .source_code = source_code, - .is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs, + .is_commonjs_module = is_commonjs_module, + .module_info = if (module_info) |mi| mi.asDeserialized() else null, .tag = this.resolved_source.tag, }; } }; }; +const analyze_transpiled_module = @import("../analyze_transpiled_module.zig"); const Fs = @import("../fs.zig"); const node_fallbacks = @import("../node_fallbacks.zig"); const std = @import("std"); diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 57a8fe763a..d65c75918a 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -542,8 +542,6 @@ pub const JSBundler = struct { this.bytecode = bytecode; if (bytecode) { - // Default to CJS for bytecode, since esm doesn't really work yet. - this.format = .cjs; if (did_set_target and this.target != .bun and this.bytecode) { return globalThis.throwInvalidArguments("target must be 'bun' when bytecode is true", .{}); } @@ -670,13 +668,13 @@ pub const JSBundler = struct { if (try config.getOptionalEnum(globalThis, "format", options.Format)) |format| { this.format = format; - if (this.bytecode and format != .cjs) { - return globalThis.throwInvalidArguments("format must be 'cjs' when bytecode is true. Eventually we'll add esm support as well.", .{}); + if (this.bytecode and format != .cjs and format != .esm) { + return globalThis.throwInvalidArguments("format must be 'cjs' or 'esm' when bytecode is true.", .{}); } } - if (try config.getBooleanLoose(globalThis, "splitting")) |hot| { - this.code_splitting = hot; + if (try config.getBooleanLoose(globalThis, "splitting")) |splitting| { + this.code_splitting = splitting; } if (try config.getTruthy(globalThis, "minify")) |minify| { @@ -1666,9 +1664,10 @@ pub const BuildArtifact = struct { @"entry-point", sourcemap, bytecode, + module_info, pub fn isFileInStandaloneMode(this: OutputKind) bool { - return this != .sourcemap and this != .bytecode; + return this != .sourcemap and this != .bytecode and this != .module_info; } }; diff --git a/src/bun.js/bindings/ResolvedSource.zig b/src/bun.js/bindings/ResolvedSource.zig index a105592b36..23f1ba16e5 100644 --- a/src/bun.js/bindings/ResolvedSource.zig +++ b/src/bun.js/bindings/ResolvedSource.zig @@ -27,6 +27,10 @@ pub const ResolvedSource = extern struct { bytecode_cache: ?[*]u8 = null, bytecode_cache_size: usize = 0, + /// - for esm: null means to use jsc's regular parsing step. more info: https://github.com/oven-sh/bun/pull/15758 + /// - for cjs: must be null + module_info: ?*@import("../../analyze_transpiled_module.zig").ModuleInfoDeserialized = null, + pub const Tag = @import("ResolvedSourceTag").ResolvedSourceTag; }; diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 546d804181..59ba61258e 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -118,6 +118,7 @@ typedef struct ResolvedSource { bool already_bundled; uint8_t* bytecode_cache; size_t bytecode_cache_size; + void* module_info; // *analyze_transpiled_module.ModuleInfoDeserialized, null for CJS or fallback } ResolvedSource; static const uint32_t ResolvedSourceTagPackageJSONTypeModule = 1; typedef union ErrorableResolvedSourceResult { diff --git a/src/bundler/Chunk.zig b/src/bundler/Chunk.zig index ed3ae6c45a..8e2881dbd3 100644 --- a/src/bundler/Chunk.zig +++ b/src/bundler/Chunk.zig @@ -502,6 +502,9 @@ pub const Chunk = struct { /// /// Mutated while sorting chunks in `computeChunks` css_chunks: []u32 = &.{}, + + /// Serialized ModuleInfo for ESM bytecode (--compile --bytecode --format=esm) + module_info_bytes: []const u8 = "", }; pub const CssChunk = struct { diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index e8b93bb4d4..c3a437b824 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1957,6 +1957,7 @@ pub const BundleV2 = struct { transpiler.options.output_format = config.format; transpiler.options.bytecode = config.bytecode; transpiler.options.compile = config.compile != null; + transpiler.resolver.opts.compile = config.compile != null; // For compile mode, set the public_path to the target-specific base path // This ensures embedded resources like yoga.wasm are correctly found diff --git a/src/bundler/linker_context/OutputFileListBuilder.zig b/src/bundler/linker_context/OutputFileListBuilder.zig index 47d1b6f1b9..b9adbfab97 100644 --- a/src/bundler/linker_context/OutputFileListBuilder.zig +++ b/src/bundler/linker_context/OutputFileListBuilder.zig @@ -94,7 +94,10 @@ pub fn calculateOutputFileListCapacity(c: *const bun.bundle_v2.LinkerContext, ch break :bytecode_count bytecode_count; } else 0; - return .{ @intCast(chunks.len + source_map_count + bytecode_count + c.parse_graph.additional_output_files.items.len), @intCast(source_map_count + bytecode_count) }; + // module_info is generated for ESM bytecode in --compile builds + const module_info_count = if (c.options.generate_bytecode_cache and c.options.output_format == .esm and c.resolver.opts.compile) bytecode_count else 0; + + return .{ @intCast(chunks.len + source_map_count + bytecode_count + module_info_count + c.parse_graph.additional_output_files.items.len), @intCast(source_map_count + bytecode_count + module_info_count) }; } pub fn insertForChunk(this: *OutputFileList, output_file: options.OutputFile) u32 { diff --git a/src/cli/Arguments.zig b/src/cli/Arguments.zig index f3c3c299da..a5e0894162 100644 --- a/src/cli/Arguments.zig +++ b/src/cli/Arguments.zig @@ -1260,8 +1260,9 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C } ctx.bundler_options.output_format = format; - if (format != .cjs and ctx.bundler_options.bytecode) { - Output.errGeneric("format must be 'cjs' when bytecode is true. Eventually we'll add esm support as well.", .{}); + // ESM bytecode is supported for --compile builds (module_info is embedded in binary) + if (format != .cjs and format != .esm and ctx.bundler_options.bytecode) { + Output.errGeneric("format must be 'cjs' or 'esm' when bytecode is true.", .{}); Global.exit(1); } } diff --git a/src/cli/build_command.zig b/src/cli/build_command.zig index 73e31338d3..c1d02fb53d 100644 --- a/src/cli/build_command.zig +++ b/src/cli/build_command.zig @@ -554,6 +554,7 @@ pub const BuildCommand = struct { .asset => Output.prettyFmt("", true), .sourcemap => Output.prettyFmt("", true), .bytecode => Output.prettyFmt("", true), + .module_info => Output.prettyFmt("", true), }); try writer.writeAll(rel_path); @@ -584,6 +585,7 @@ pub const BuildCommand = struct { .asset => "asset", .sourcemap => "source map", .bytecode => "bytecode", + .module_info => "module info", }}); if (Output.enable_ansi_colors_stdout) try writer.writeAll("\x1b[0m"); diff --git a/src/transpiler.zig b/src/transpiler.zig index 3ea5becc15..21cd3efcb1 100644 --- a/src/transpiler.zig +++ b/src/transpiler.zig @@ -783,6 +783,7 @@ pub const Transpiler = struct { comptime enable_source_map: bool, source_map_context: ?js_printer.SourceMapHandler, runtime_transpiler_cache: ?*bun.jsc.RuntimeTranspilerCache, + module_info: ?*analyze_transpiled_module.ModuleInfo, ) !usize { const tracer = if (enable_source_map) bun.perf.trace("JSPrinter.printWithSourceMap") @@ -872,6 +873,7 @@ pub const Transpiler = struct { .inline_require_and_import_errors = false, .import_meta_ref = ast.import_meta_ref, .runtime_transpiler_cache = runtime_transpiler_cache, + .module_info = module_info, .target = transpiler.options.target, .print_dce_annotations = transpiler.options.emit_dce_annotations, .hmr_ref = ast.wrapper_ref, @@ -900,6 +902,7 @@ pub const Transpiler = struct { false, null, null, + null, ); } @@ -910,6 +913,7 @@ pub const Transpiler = struct { writer: Writer, comptime format: js_printer.Format, handler: js_printer.SourceMapHandler, + module_info: ?*analyze_transpiled_module.ModuleInfo, ) !usize { if (bun.feature_flag.BUN_FEATURE_FLAG_DISABLE_SOURCE_MAPS.get()) { return transpiler.printWithSourceMapMaybe( @@ -921,6 +925,7 @@ pub const Transpiler = struct { false, handler, result.runtime_transpiler_cache, + module_info, ); } return transpiler.printWithSourceMapMaybe( @@ -932,6 +937,7 @@ pub const Transpiler = struct { true, handler, result.runtime_transpiler_cache, + module_info, ); } @@ -1581,6 +1587,7 @@ pub const ResolveQueue = bun.LinearFifo( const string = []const u8; +const analyze_transpiled_module = @import("./analyze_transpiled_module.zig"); const DotEnv = @import("./env_loader.zig"); const Fs = @import("./fs.zig"); const MimeType = @import("./http/MimeType.zig");