From 9ff17e56600ee6e3a93e3b8ee347ecfd2dce2eaa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 30 Jun 2025 04:26:25 +0000 Subject: [PATCH] Add sourcemap support to Bun's bundler plugin system Co-authored-by: jarred --- packages/bun-types/bun.d.ts | 19 +++++++------------ src/bun.js/api/JSBundler.zig | 13 +++++++++++++ src/bun.js/bindings/JSBundlerPlugin.cpp | 7 ++++--- src/bun.js/bindings/JSBundlerPlugin.h | 2 +- src/js/builtins/BundlerPlugin.ts | 9 +++++---- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 6d671f760a..55cd954382 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -794,7 +794,6 @@ declare module "bun" { path?: string | undefined; syscall?: string | undefined; } - /** * Concatenate an array of typed arrays into a single `ArrayBuffer`. This is a fast path. * @@ -1419,7 +1418,6 @@ declare module "bun" { * @param sql Function to execute SQL queries within the savepoint */ type SQLSavepointContextCallback = (sql: SavepointSQL) => Promise | Array; - /** * Main SQL client interface providing connection and transaction management */ @@ -2129,7 +2127,6 @@ declare module "bun" { path: string; kind: ImportKind; } - /** * @see [Bun.build API docs](https://bun.sh/docs/bundler#api) */ @@ -2853,7 +2850,6 @@ declare module "bun" { * @link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState */ type WebSocketReadyState = 0 | 1 | 2 | 3; - /** * A fast WebSocket designed for servers. * @@ -3654,7 +3650,6 @@ declare module "bun" { errno?: number; syscall?: string; } - /** * Options for TLS connections */ @@ -4437,7 +4432,6 @@ declare module "bun" { * This can be 3.5x faster than `new Uint8Array(size)`, but if you send uninitialized memory to your users (even unintentionally), it can potentially leak anything recently in memory. */ function allocUnsafe(size: number): Uint8Array; - /** * Options for `Bun.inspect` */ @@ -5222,7 +5216,6 @@ declare module "bun" { * Internally, it calls [nanosleep(2)](https://man7.org/linux/man-pages/man2/nanosleep.2.html) */ function sleepSync(ms: number): void; - /** * Hash `input` using [SHA-2 512/256](https://en.wikipedia.org/wiki/SHA-2#Comparison_of_SHA_functions) * @@ -5549,6 +5542,10 @@ declare module "bun" { * The source code of the module */ contents: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer; + /** + * Optional sourcemap (as a string) for the generated contents. + */ + sourcemap?: string; /** * The loader to use for this file * @@ -5883,7 +5880,6 @@ declare module "bun" { interface HTMLBundle { index: string; } - /** * Represents a TCP or TLS socket connection used for network communication. * This interface provides methods for reading, writing, managing the connection state, @@ -6625,7 +6621,6 @@ declare module "bun" { * @category HTTP & Networking */ function listen(options: UnixSocketOptions): UnixSocketListener; - /** * @category HTTP & Networking */ @@ -7247,7 +7242,7 @@ declare module "bun" { * * @example * ```ts - * const subprocess = Bun.spawn(["echo", "hello"]); + * const subprocess = Bun.spawn({ cmd: ["echo", "hello"] }); * ``` */ cmd: string[]; // to support dynamically constructed commands @@ -7365,7 +7360,7 @@ declare module "bun" { type WritableSubprocess = Subprocess<"pipe", any, any>; /** Utility type for any process from {@link Bun.spawn()} with stdin, stdout, stderr all set to `"pipe"`. A combination of {@link ReadableSubprocess} and {@link WritableSubprocess} */ type PipedSubprocess = Subprocess<"pipe", "pipe", "pipe">; - /** Utility type for any process from {@link Bun.spawn()} with stdin, stdout, stderr all set to `null` or similar. */ + /** Utility type for any process from {@link Bun.spawn()} with stdin, stdout, stderr all set to `null` or similar */ type NullSubprocess = Subprocess< "ignore" | "inherit" | null | undefined, "ignore" | "inherit" | null | undefined, @@ -8042,4 +8037,4 @@ declare module "bun" { */ [Symbol.iterator](): IterableIterator<[string, string]>; } -} +} \ No newline at end of file diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 7ffa49672d..d93b8f1c6e 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -729,6 +729,7 @@ pub const JSBundler = struct { success: struct { source_code: []const u8 = "", loader: options.Loader = .file, + sourcemap: ?[]const u8 = null, }, pending, no_match, @@ -739,6 +740,9 @@ pub const JSBundler = struct { switch (this.*) { .success => |success| { bun.default_allocator.free(success.source_code); + if (success.sourcemap) |sm| { + bun.default_allocator.free(sm); + } }, .err => |*err| { err.deinit(bun.default_allocator); @@ -817,6 +821,7 @@ pub const JSBundler = struct { _: *anyopaque, source_code_value: JSValue, loader_as_int: JSValue, + sourcemap_value: JSValue, ) void { JSC.markBinding(@src()); if (source_code_value.isEmptyOrUndefinedOrNull() or loader_as_int.isEmptyOrUndefinedOrNull()) { @@ -841,10 +846,18 @@ pub const JSBundler = struct { @panic("Unexpected: source_code is not a string"); }; + + var maybe_sourcemap: ?[]const u8 = null; + if (!sourcemap_value.isEmptyOrUndefinedOrNull()) { + if (sourcemap_value.isString()) { + maybe_sourcemap = sourcemap_value.toSliceCloneWithAllocator(global, bun.default_allocator); + } + } this.value = .{ .success = .{ .loader = options.Loader.fromAPI(loader), .source_code = source_code, + .sourcemap = maybe_sourcemap, }, }; } diff --git a/src/bun.js/bindings/JSBundlerPlugin.cpp b/src/bun.js/bindings/JSBundlerPlugin.cpp index 799f731ec6..0a881fbccc 100644 --- a/src/bun.js/bindings/JSBundlerPlugin.cpp +++ b/src/bun.js/bindings/JSBundlerPlugin.cpp @@ -46,7 +46,7 @@ extern "C" void OnBeforeParseResult__reset(OnBeforeParseResult* result); /// These are callbacks defined in Zig and to be run after their associated JS version is run extern "C" void JSBundlerPlugin__addError(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue); -extern "C" void JSBundlerPlugin__onLoadAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue); +extern "C" void JSBundlerPlugin__onLoadAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue); extern "C" void JSBundlerPlugin__onResolveAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue); extern "C" void JSBundlerPlugin__onVirtualModulePlugin(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue); extern "C" JSC::EncodedJSValue JSBundlerPlugin__onDefer(void*, JSC::JSGlobalObject*); @@ -111,7 +111,7 @@ bool BundlerPlugin::anyMatchesCrossThread(JSC::VM& vm, const BunString* namespac static const HashTableValue JSBundlerPluginHashTable[] = { { "addFilter"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_addFilter, 3 } }, { "addError"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_addError, 3 } }, - { "onLoadAsync"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onLoadAsync, 3 } }, + { "onLoadAsync"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onLoadAsync, 4 } }, { "onResolveAsync"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onResolveAsync, 4 } }, { "onBeforeParse"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onBeforeParse, 4 } }, { "generateDeferPromise"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_generateDeferPromise, 0 } }, @@ -402,7 +402,8 @@ JSC_DEFINE_HOST_FUNCTION(jsBundlerPluginFunction_onLoadAsync, (JSC::JSGlobalObje UNWRAP_BUNDLER_PLUGIN(callFrame), thisObject->plugin.config, JSValue::encode(callFrame->argument(1)), - JSValue::encode(callFrame->argument(2))); + JSValue::encode(callFrame->argument(2)), + JSValue::encode(callFrame->argument(3))); } return JSC::JSValue::encode(JSC::jsUndefined()); diff --git a/src/bun.js/bindings/JSBundlerPlugin.h b/src/bun.js/bindings/JSBundlerPlugin.h index 7bef5769fa..be8c8b0ccb 100644 --- a/src/bun.js/bindings/JSBundlerPlugin.h +++ b/src/bun.js/bindings/JSBundlerPlugin.h @@ -9,7 +9,7 @@ #include typedef void (*JSBundlerPluginAddErrorCallback)(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue); -typedef void (*JSBundlerPluginOnLoadAsyncCallback)(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue); +typedef void (*JSBundlerPluginOnLoadAsyncCallback)(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue); typedef void (*JSBundlerPluginOnResolveAsyncCallback)(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue); typedef void (*JSBundlerPluginNativeOnBeforeParseCallback)(const OnBeforeParseArguments*, OnBeforeParseResult*); diff --git a/src/js/builtins/BundlerPlugin.ts b/src/js/builtins/BundlerPlugin.ts index ed427a39a7..311b315aea 100644 --- a/src/js/builtins/BundlerPlugin.ts +++ b/src/js/builtins/BundlerPlugin.ts @@ -12,6 +12,7 @@ interface BundlerPlugin { internalID, sourceCode: string | Uint8Array | ArrayBuffer | DataView | null, loaderKey: number | null, + sourcemap: string | null, ): void; /** Binding to `JSBundlerPlugin__onResolveAsync` */ onResolveAsync(internalID, a, b, c): void; @@ -458,7 +459,7 @@ export function runOnLoadPlugins( var promiseResult = (async (internalID, path, namespace, isServerSide, defaultLoader, generateDefer) => { var results = this.onLoad.$get(namespace); if (!results) { - this.onLoadAsync(internalID, null, null); + this.onLoadAsync(internalID, null, null, null); return null; } @@ -490,7 +491,7 @@ export function runOnLoadPlugins( continue; } - var { contents, loader = defaultLoader } = result as any; + var { contents, loader = defaultLoader, sourcemap } = result as any; if ((loader as any) === "object") { if (!("exports" in result)) { throw new TypeError('onLoad plugin returning loader: "object" must have "exports" property'); @@ -516,12 +517,12 @@ export function runOnLoadPlugins( throw new TypeError(`Loader ${loader} is not supported.`); } - this.onLoadAsync(internalID, contents as any, chosenLoader); + this.onLoadAsync(internalID, contents as any, chosenLoader, sourcemap ?? null); return null; } } - this.onLoadAsync(internalID, null, null); + this.onLoadAsync(internalID, null, null, null); return null; })(internalID, path, namespace, isServerSide, loaderName, generateDefer);