Compare commits

...

2 Commits

Author SHA1 Message Date
Claude Bot
3ae3a1393a Remove forced sourcemap override in JSBundler.zig for consistency
Ensures that JSBundler API respects the user's sourcemap choice when using
compile mode, matching the behavior of the CLI build command.
2025-11-06 13:47:33 +00:00
Claude Bot
81a211b522 Add --sourcemap=external support for bun build --compile
This implements external sourcemap support for compiled executables,
allowing sourcemaps to be written to disk for remote upload to services
like Sentry instead of being embedded in the executable.

Changes:
- Modified build_command.zig to allow --outdir with --compile when using --sourcemap=external
- Updated StandaloneModuleGraph.toBytes to skip embedding sourcemaps when source_map is .external
- Added writeSourceMapsToExternalFiles function to write sourcemaps to disk after compilation
- Updated toExecutable signature to accept source_map and outdir parameters
- Removed forced override that set all --compile sourcemaps to external
- Updated compile output to show sourcemap filename when external mode is used

Usage:
  bun build --compile --sourcemap=external --outdir=./dist ./index.ts

This will create:
  - ./dist/index (executable)
  - ./dist/index.map (sourcemap)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 13:40:24 +00:00
5 changed files with 73 additions and 17 deletions

View File

@@ -349,7 +349,7 @@ pub const StandaloneModuleGraph = struct {
return bytes[ptr.offset..][0..ptr.length :0];
}
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format, compile_exec_argv: []const u8) ![]u8 {
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format, compile_exec_argv: []const u8, source_map: bun.options.SourceMapOption) ![]u8 {
var serialize_trace = bun.perf.trace("StandaloneModuleGraph.serialize");
defer serialize_trace.end();
@@ -475,7 +475,7 @@ pub const StandaloneModuleGraph = struct {
},
};
if (output_file.source_map_index != std.math.maxInt(u32)) {
if (output_file.source_map_index != std.math.maxInt(u32) and source_map != .external) {
defer source_map_header_list.clearRetainingCapacity();
defer source_map_string_list.clearRetainingCapacity();
_ = source_map_arena.reset(.retain_capacity);
@@ -921,6 +921,48 @@ pub const StandaloneModuleGraph = struct {
pub const CompileTarget = @import("./compile_target.zig");
fn writeSourceMapsToExternalFiles(
allocator: std.mem.Allocator,
output_files: []const bun.options.OutputFile,
outdir: []const u8,
outfile_basename: []const u8,
) !void {
// Find the entry point sourcemap
for (output_files) |output_file| {
if (output_file.output_kind == .@"entry-point" and output_file.source_map_index != std.math.maxInt(u32)) {
const sourcemap_file = &output_files[output_file.source_map_index];
if (sourcemap_file.value == .buffer) {
// Build the sourcemap filename based on the executable name
const sourcemap_filename = try std.fmt.allocPrint(
allocator,
"{s}.map",
.{outfile_basename},
);
defer allocator.free(sourcemap_filename);
// Build the full path
const sourcemap_path = try std.fs.path.join(
allocator,
&[_][]const u8{ outdir, sourcemap_filename },
);
defer allocator.free(sourcemap_path);
// Write the sourcemap to disk
const file = std.fs.cwd().createFile(sourcemap_path, .{}) catch |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to create sourcemap file {s}: {s}", .{ sourcemap_path, @errorName(err) });
return err;
};
defer file.close();
file.writeAll(sourcemap_file.value.buffer.bytes) catch |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write sourcemap file {s}: {s}", .{ sourcemap_path, @errorName(err) });
return err;
};
}
}
}
}
pub fn download(allocator: std.mem.Allocator, target: *const CompileTarget, env: *bun.DotEnv.Loader) ![:0]const u8 {
var exe_path_buf: bun.PathBuffer = undefined;
var version_str_buf: [1024]u8 = undefined;
@@ -982,8 +1024,10 @@ pub const StandaloneModuleGraph = struct {
windows_options: bun.options.WindowsOptions,
compile_exec_argv: []const u8,
self_exe_path: ?[]const u8,
source_map: bun.options.SourceMapOption,
outdir: []const u8,
) !CompileResult {
const bytes = toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv) catch |err| {
const bytes = toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv, source_map) catch |err| {
return CompileResult.failFmt("failed to generate module graph bytes: {s}", .{@errorName(err)});
};
if (bytes.len == 0) return CompileResult.fail(.no_output_files);
@@ -1110,6 +1154,12 @@ pub const StandaloneModuleGraph = struct {
return CompileResult.failFmt("Failed to set Windows metadata: {s}", .{@errorName(err)});
};
}
// Write sourcemaps to disk if using external mode
if (source_map == .external and outdir.len > 0) {
try writeSourceMapsToExternalFiles(allocator, output_files, outdir, outfile);
}
return .success;
}
@@ -1144,6 +1194,11 @@ pub const StandaloneModuleGraph = struct {
}
};
// Write sourcemaps to disk if using external mode
if (source_map == .external and outdir.len > 0) {
try writeSourceMapsToExternalFiles(allocator, output_files, outdir, outfile);
}
return .success;
}

View File

@@ -694,11 +694,8 @@ pub const JSBundler = struct {
const base_public_path = bun.StandaloneModuleGraph.targetBasePublicPath(this.compile.?.compile_target.os, "root/");
try this.public_path.append(base_public_path);
// When using --compile, only `external` sourcemaps work, as we do not
// look at the source map comment. Override any other sourcemap type.
if (this.source_map != .none) {
this.source_map = .external;
}
// When using --compile with --sourcemap=external, sourcemaps are written to disk
// Other sourcemap modes are embedded in the executable (inline, linked) or disabled (none)
if (compile.outfile.isEmpty()) {
const entry_point = this.entry_points.keys()[0];

View File

@@ -2043,6 +2043,8 @@ pub const BundleV2 = struct {
compile_options.executable_path.slice()
else
null,
this.config.source_map,
this.config.outdir.slice(),
) catch |err| {
return bun.StandaloneModuleGraph.CompileResult.failFmt("{s}", .{@errorName(err)});
};

View File

@@ -1189,12 +1189,8 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
Global.crash();
}
// when using --compile, only `external` works, as we do not
// look at the source map comment. so after we validate the
// user's choice was in the list, we secretly override it
if (ctx.bundler_options.compile) {
opts.source_map = .external;
}
// when using --compile with --sourcemap=external, sourcemaps are written to disk
// other sourcemap modes are embedded in the executable (inline, linked) or disabled (none)
}
}

View File

@@ -53,7 +53,7 @@ pub const BuildCommand = struct {
this_transpiler.options.compile = ctx.bundler_options.compile;
if (this_transpiler.options.source_map == .external and ctx.bundler_options.outdir.len == 0 and !ctx.bundler_options.compile) {
if (this_transpiler.options.source_map == .external and ctx.bundler_options.outdir.len == 0) {
Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use an external source map without --outdir", .{});
Global.exit(1);
return;
@@ -96,8 +96,8 @@ pub const BuildCommand = struct {
var was_renamed_from_index = false;
if (ctx.bundler_options.compile) {
if (ctx.bundler_options.outdir.len > 0) {
Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use --compile with --outdir", .{});
if (ctx.bundler_options.outdir.len > 0 and this_transpiler.options.source_map != .external) {
Output.prettyErrorln("<r><red>error<r><d>:<r> cannot use --compile with --outdir (unless using --sourcemap=external)", .{});
Global.exit(1);
return;
}
@@ -440,6 +440,8 @@ pub const BuildCommand = struct {
ctx.bundler_options.windows,
ctx.bundler_options.compile_exec_argv orelse "",
null,
this_transpiler.options.source_map,
ctx.bundler_options.outdir,
) catch |err| {
Output.printErrorln("failed to create executable: {s}", .{@errorName(err)});
Global.exit(1);
@@ -469,6 +471,10 @@ pub const BuildCommand = struct {
if (compile_target.os == .windows and !strings.hasSuffixComptime(outfile, ".exe")) ".exe" else "",
});
if (this_transpiler.options.source_map == .external and ctx.bundler_options.outdir.len > 0) {
Output.pretty(", <d>{s}.map<r>", .{outfile});
}
if (is_cross_compile) {
Output.pretty(" <r><d>{s}<r>\n", .{compile_target});
} else {