Add sourcemap support to Bun's bundler plugin system

Co-authored-by: jarred <jarred@bun.sh>
This commit is contained in:
Cursor Agent
2025-06-30 04:26:25 +00:00
parent 6d03bdfc03
commit 9ff17e5660
5 changed files with 30 additions and 20 deletions

View File

@@ -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<any> | Array<SQLQuery>;
/**
* 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<Data = undefined>(options: UnixSocketOptions<Data>): UnixSocketListener<Data>;
/**
* @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]>;
}
}
}

View File

@@ -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,
},
};
}

View File

@@ -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<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_addFilter, 3 } },
{ "addError"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_addError, 3 } },
{ "onLoadAsync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onLoadAsync, 3 } },
{ "onLoadAsync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onLoadAsync, 4 } },
{ "onResolveAsync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onResolveAsync, 4 } },
{ "onBeforeParse"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onBeforeParse, 4 } },
{ "generateDeferPromise"_s, static_cast<unsigned>(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());

View File

@@ -9,7 +9,7 @@
#include <JavaScriptCore/Yarr.h>
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*);

View File

@@ -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);