aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/api/JSBundler.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/api/JSBundler.zig')
-rw-r--r--src/bun.js/api/JSBundler.zig334
1 files changed, 213 insertions, 121 deletions
diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig
index c53d21f12..ae21e7acc 100644
--- a/src/bun.js/api/JSBundler.zig
+++ b/src/bun.js/api/JSBundler.zig
@@ -230,18 +230,22 @@ pub const JSBundler = struct {
// };
// defer decl.deinit();
- // if (try plugin.getOptional(globalThis, "name", ZigString.Slice)) |slice| {
- // defer slice.deinit();
- // decl.name.appendSliceExact(slice.slice()) catch unreachable;
- // }
-
- if (try plugin.getFunction(globalThis, "setup")) |_| {
- // decl.setup.set(globalThis, setup);
+ if (plugin.getOptional(globalThis, "name", ZigString.Slice) catch null) |slice| {
+ defer slice.deinit();
+ if (slice.len == 0) {
+ globalThis.throwInvalidArguments("Expected plugin to have a non-empty name", .{});
+ return error.JSError;
+ }
} else {
- globalThis.throwInvalidArguments("Expected plugin to have a setup() function", .{});
+ globalThis.throwInvalidArguments("Expected plugin to have a name", .{});
return error.JSError;
}
+ const function = (plugin.getFunction(globalThis, "setup") catch null) orelse {
+ globalThis.throwInvalidArguments("Expected plugin to have a setup() function", .{});
+ return error.JSError;
+ };
+
var bun_plugins: *Plugin = plugins.* orelse brk: {
plugins.* = Plugin.create(
globalThis,
@@ -254,11 +258,17 @@ pub const JSBundler = struct {
break :brk plugins.*.?;
};
- const plugin_result = bun_plugins.addPlugin(globalThis, plugin);
+ var plugin_result = bun_plugins.addPlugin(function);
+
+ if (!plugin_result.isEmptyOrUndefinedOrNull()) {
+ if (plugin_result.asAnyPromise()) |promise| {
+ globalThis.bunVM().waitForPromise(promise);
+ plugin_result = promise.result(globalThis.vm());
+ }
+ }
if (plugin_result.toError()) |err| {
globalThis.throwValue(err);
- bun_plugins.deinit();
return error.JSError;
}
}
@@ -356,20 +366,65 @@ pub const JSBundler = struct {
}
pub const Resolve = struct {
- import_record: *bun.ImportRecord,
- source_file: string = "",
- default_namespace: string = "",
+ import_record: MiniImportRecord,
/// Null means the Resolve is aborted
completion: ?*bun.BundleV2.JSBundleCompletionTask = null,
- value: Value,
+ value: Value = .{ .pending = {} },
+
+ js_task: JSC.AnyTask = undefined,
+ task: JSC.AnyEventLoop.Task = undefined,
+
+ pub const MiniImportRecord = struct {
+ kind: bun.ImportKind,
+ source_file: string = "",
+ namespace: string = "",
+ specifier: string = "",
+ importer_source_index: ?u32 = null,
+ import_record_index: u32 = 0,
+ range: logger.Range = logger.Range.None,
+ original_platform: options.Platform,
+ };
+
+ pub fn create(
+ from: union(enum) {
+ MiniImportRecord: MiniImportRecord,
+ ImportRecord: struct {
+ importer_source_index: u32,
+ import_record_index: u32,
+ source_file: []const u8 = "",
+ original_platform: options.Platform,
+ record: *const bun.ImportRecord,
+ },
+ },
+ completion: *bun.BundleV2.JSBundleCompletionTask,
+ ) Resolve {
+ return Resolve{
+ .import_record = switch (from) {
+ .MiniImportRecord => from.MiniImportRecord,
+ .ImportRecord => |file| MiniImportRecord{
+ .kind = file.record.kind,
+ .source_file = file.source_file,
+ .namespace = file.record.path.namespace,
+ .specifier = file.record.path.text,
+ .importer_source_index = file.importer_source_index,
+ .import_record_index = file.import_record_index,
+ .range = file.record.range,
+ .original_platform = file.original_platform,
+ },
+ },
+ .completion = completion,
+ .value = .{ .pending = {} },
+ };
+ }
pub const Value = union(enum) {
err: logger.Msg,
success: struct {
path: []const u8 = "",
namespace: []const u8 = "",
+ external: bool = false,
pub fn deinit(this: *@This()) void {
bun.default_allocator.destroy(this.path);
@@ -377,68 +432,24 @@ pub const JSBundler = struct {
}
},
no_match: void,
- pending: JSC.JSPromise.Strong,
+ pending: void,
consumed: void,
- fn badPluginError() Value {
- return .{
- .err = logger.Msg{
- .data = .{
- .text = bun.default_allocator.dupe(u8, "onResolve plugin returned an invalid value") catch unreachable,
- },
- },
- };
- }
-
pub fn consume(this: *Value) Value {
const result = this.*;
this.* = .{ .consumed = {} };
return result;
}
- pub fn fromJS(globalObject: *JSC.JSGlobalObject, source_file: []const u8, default_namespace: string, value: JSC.JSValue) Value {
- if (value.isEmptyOrUndefinedOrNull()) {
- return .{ .no_match = {} };
- }
-
- if (value.toError(globalObject)) |err| {
- return .{ .err = logger.Msg.fromJS(bun.default_allocator, globalObject, source_file, err) catch unreachable };
- }
-
- // I think we already do this check?
- if (!value.isObject()) return badPluginError();
-
- var namespace = ZigString.Slice.fromUTF8NeverFree(default_namespace);
-
- if (value.getOptional(globalObject, "namespace", ZigString.Slice) catch return badPluginError()) |namespace_slice| {
- namespace = namespace_slice;
- }
-
- const path = value.getOptional(globalObject, "path", ZigString.Slice) catch {
- namespace.deinit();
- return badPluginError();
- };
-
- return .{
- .success = .{
- .path = path.cloneWithAllocator(bun.default_allocator).slice(),
- .namespace = namespace.slice(),
- },
- };
- }
-
pub fn deinit(this: *Resolve.Value) void {
switch (this.*) {
- .pending => |*pending| {
- pending.deinit();
- },
.success => |*success| {
success.deinit();
},
.err => |*err| {
err.deinit(bun.default_allocator);
},
- .consumed => {},
+ .no_match, .pending, .consumed => {},
}
this.* = .{ .consumed = {} };
}
@@ -452,22 +463,73 @@ pub const JSBundler = struct {
const AnyTask = JSC.AnyTask.New(@This(), runOnJSThread);
- pub fn runOnJSThread(this: *Load) void {
+ pub fn dispatch(this: *Resolve) void {
var completion = this.completion orelse {
this.deinit();
return;
};
+ completion.ref();
- const result = completion.plugins.?.matchOnResolve(
+ this.js_task = AnyTask.init(this);
+ var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch {
+ completion.deref();
+ this.deinit();
+ return;
+ };
+ concurrent_task.* = JSC.ConcurrentTask{
+ .auto_delete = true,
+ .task = this.js_task.task(),
+ };
+ completion.jsc_event_loop.enqueueTaskConcurrent(concurrent_task);
+ }
+
+ pub fn runOnJSThread(this: *Resolve) void {
+ var completion = this.completion orelse {
+ this.deinit();
+ return;
+ };
+
+ completion.plugins.?.matchOnResolve(
completion.globalThis,
- this.path,
- this.namespace,
+ this.import_record.specifier,
+ this.import_record.namespace,
+ this.import_record.source_file,
this,
+ this.import_record.kind,
);
+ }
+
+ export fn JSBundlerPlugin__onResolveAsync(
+ this: *Resolve,
+ _: *anyopaque,
+ path_value: JSValue,
+ namespace_value: JSValue,
+ external_value: JSValue,
+ ) void {
+ var completion = this.completion orelse {
+ this.deinit();
+ return;
+ };
+ if (path_value.isEmptyOrUndefinedOrNull() or namespace_value.isEmptyOrUndefinedOrNull()) {
+ this.value = .{ .no_match = {} };
+ } else {
+ const path = path_value.toSliceClone(completion.globalThis) orelse @panic("Unexpected: path is not a string");
+ const namespace = namespace_value.toSliceClone(completion.globalThis) orelse @panic("Unexpected: namespace is not a string");
+ this.value = .{
+ .success = .{
+ .path = path.slice(),
+ .namespace = namespace.slice(),
+ .external = external_value.to(bool),
+ },
+ };
+ }
- this.value = Value.fromJS(completion.globalThis, this.source_file, this.default_namespace, result);
completion.bundler.onResolveAsync(this);
}
+
+ comptime {
+ _ = JSBundlerPlugin__onResolveAsync;
+ }
};
pub const Load = struct {
@@ -484,6 +546,9 @@ pub const JSBundler = struct {
task: JSC.AnyEventLoop.Task = undefined,
parse_task: *bun.ParseTask = undefined,
+ /// Faster path: skip the extra threadpool dispatch when the file is not found
+ was_file: bool = false,
+
pub fn create(
completion: *bun.BundleV2.JSBundleCompletionTask,
source_index: Index,
@@ -494,7 +559,7 @@ pub const JSBundler = struct {
.source_index = source_index,
.default_loader = default_loader,
.completion = completion,
- .value = .{ .pending = .{} },
+ .value = .{ .pending = {} },
.path = path.text,
.namespace = path.namespace,
};
@@ -506,21 +571,19 @@ pub const JSBundler = struct {
source_code: []const u8 = "",
loader: options.Loader = options.Loader.file,
},
- pending: JSC.JSPromise.Strong,
+ pending: void,
+ no_match: void,
consumed: void,
pub fn deinit(this: *Value) void {
switch (this.*) {
- .pending => |*pending| {
- pending.strong.deinit();
- },
.success => |success| {
bun.default_allocator.destroy(success.source_code);
},
.err => |*err| {
err.deinit(bun.default_allocator);
},
- .consumed => {},
+ .no_match, .pending, .consumed => {},
}
this.* = .{ .consumed = {} };
}
@@ -546,19 +609,13 @@ pub const JSBundler = struct {
return;
};
- const err = completion.plugins.?.matchOnLoad(
+ completion.plugins.?.matchOnLoad(
completion.globalThis,
this.path,
this.namespace,
this,
+ this.default_loader,
);
-
- if (this.value == .pending) {
- if (!err.isEmptyOrUndefinedOrNull()) {
- var code = ZigString.Empty;
- JSBundlerPlugin__OnLoadAsync(this, err, &code, .js);
- }
- }
}
pub fn dispatch(this: *Load) void {
@@ -581,51 +638,56 @@ pub const JSBundler = struct {
completion.jsc_event_loop.enqueueTaskConcurrent(concurrent_task);
}
- export fn JSBundlerPlugin__getDefaultLoader(this: *Load) options.Loader {
- return this.default_loader;
- }
-
- export fn JSBundlerPlugin__OnLoadAsync(
+ export fn JSBundlerPlugin__onLoadAsync(
this: *Load,
- error_value: JSC.JSValue,
- source_code: *ZigString,
- loader: options.Loader,
+ _: *anyopaque,
+ source_code_value: JSValue,
+ loader_as_int: JSValue,
) void {
- if (this.completion) |completion| {
- if (error_value.toError()) |err| {
- if (this.value == .pending) this.value.pending.strong.deinit();
- this.value = .{
- .err = logger.Msg.fromJS(bun.default_allocator, completion.globalThis, this.path, err) catch unreachable,
- };
- } else if (!error_value.isEmptyOrUndefinedOrNull() and error_value.isCell() and error_value.jsType() == .JSPromise) {
- this.value.pending.strong.set(completion.globalThis, error_value);
+ var completion = this.completion orelse {
+ this.deinit();
+ return;
+ };
+ if (source_code_value.isEmptyOrUndefinedOrNull() or loader_as_int.isEmptyOrUndefinedOrNull()) {
+ this.value = .{ .no_match = {} };
+
+ if (this.was_file) {
+ // Faster path: skip the extra threadpool dispatch
+ completion.bundler.graph.pool.pool.schedule(bun.ThreadPool.Batch.from(&this.parse_task.task));
+ this.deinit();
return;
- } else {
- if (this.value == .pending) this.value.pending.strong.deinit();
- this.value = .{
- .success = .{
- .source_code = source_code.toSliceClone(bun.default_allocator).slice(),
- .loader = loader,
- },
- };
}
-
- completion.bundler.onLoadAsync(this);
} else {
- this.deinit();
+ var buffer_or_string: JSC.Node.SliceOrBuffer = JSC.Node.SliceOrBuffer.fromJS(completion.globalThis, bun.default_allocator, source_code_value) orelse
+ @panic("expected buffer or string");
+
+ const source_code = switch (buffer_or_string) {
+ .buffer => |arraybuffer| bun.default_allocator.dupe(u8, arraybuffer.slice()) catch @panic("Out of memory in onLoad callback"),
+ .string => |slice| (slice.cloneIfNeeded(bun.default_allocator) catch @panic("Out of memory in onLoad callback")).slice(),
+ };
+
+ this.value = .{
+ .success = .{
+ .loader = @intToEnum(options.Loader, @intCast(u8, loader_as_int.to(i32))),
+ .source_code = source_code,
+ },
+ };
}
+
+ completion.bundler.onLoadAsync(this);
}
comptime {
- _ = JSBundlerPlugin__getDefaultLoader;
- _ = JSBundlerPlugin__OnLoadAsync;
+ _ = JSBundlerPlugin__onLoadAsync;
}
};
pub const Plugin = opaque {
extern fn JSBundlerPlugin__create(*JSC.JSGlobalObject, JSC.JSGlobalObject.BunPluginTarget) *Plugin;
pub fn create(globalObject: *JSC.JSGlobalObject, target: JSC.JSGlobalObject.BunPluginTarget) *Plugin {
- return JSBundlerPlugin__create(globalObject, target);
+ var plugin = JSBundlerPlugin__create(globalObject, target);
+ JSC.JSValue.fromCell(plugin).protect();
+ return plugin;
}
extern fn JSBundlerPlugin__tombestone(*Plugin) void;
@@ -643,7 +705,8 @@ pub const JSBundler = struct {
namespaceString: *const ZigString,
path: *const ZigString,
context: *anyopaque,
- ) JSValue;
+ u8,
+ ) void;
extern fn JSBundlerPlugin__matchOnResolve(
*JSC.JSGlobalObject,
@@ -652,7 +715,8 @@ pub const JSBundler = struct {
path: *const ZigString,
importer: *const ZigString,
context: *anyopaque,
- ) JSValue;
+ u8,
+ ) void;
pub fn hasAnyMatches(
this: *Plugin,
@@ -673,13 +737,14 @@ pub const JSBundler = struct {
path: []const u8,
namespace: []const u8,
context: *anyopaque,
- ) JSC.JSValue {
- const namespace_string = if (strings.eqlComptime(namespace, "file"))
- ZigString.Empty
+ default_loader: options.Loader,
+ ) void {
+ const namespace_string = if (namespace.len == 0)
+ ZigString.init("file")
else
ZigString.fromUTF8(namespace);
const path_string = ZigString.fromUTF8(path);
- return JSBundlerPlugin__matchOnLoad(globalThis, this, &namespace_string, &path_string, context);
+ JSBundlerPlugin__matchOnLoad(globalThis, this, &namespace_string, &path_string, context, @enumToInt(default_loader));
}
pub fn matchOnResolve(
@@ -689,26 +754,27 @@ pub const JSBundler = struct {
namespace: []const u8,
importer: []const u8,
context: *anyopaque,
- ) JSC.JSValue {
+ import_record_kind: bun.ImportKind,
+ ) void {
const namespace_string = if (strings.eqlComptime(namespace, "file"))
ZigString.Empty
else
ZigString.fromUTF8(namespace);
const path_string = ZigString.fromUTF8(path);
const importer_string = ZigString.fromUTF8(importer);
- return JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context);
+ JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind));
}
pub fn addPlugin(
this: *Plugin,
- globalObject: *JSC.JSGlobalObject,
object: JSC.JSValue,
) JSValue {
- return setupJSBundlerPlugin(this, globalObject, object);
+ return JSBundlerPlugin__runSetupFunction(this, object);
}
pub fn deinit(this: *Plugin) void {
JSBundlerPlugin__tombestone(this);
+ JSC.JSValue.fromCell(this).unprotect();
}
pub fn setConfig(this: *Plugin, config: *anyopaque) void {
@@ -717,10 +783,36 @@ pub const JSBundler = struct {
extern fn JSBundlerPlugin__setConfig(*Plugin, *anyopaque) void;
- extern fn setupJSBundlerPlugin(
+ extern fn JSBundlerPlugin__runSetupFunction(
*Plugin,
- *JSC.JSGlobalObject,
JSC.JSValue,
) JSValue;
+
+ pub export fn JSBundlerPlugin__addError(
+ ctx: *anyopaque,
+ _: *Plugin,
+ exception: JSValue,
+ which: JSValue,
+ ) void {
+ switch (which.to(i32)) {
+ 1 => {
+ var this: *JSBundler.Resolve = bun.cast(*Resolve, ctx);
+ var completion = this.completion orelse return;
+ this.value = .{
+ .err = logger.Msg.fromJS(bun.default_allocator, completion.globalThis, this.import_record.source_file, exception) catch @panic("Out of memory in addError callback"),
+ };
+ completion.bundler.onResolveAsync(this);
+ },
+ 0 => {
+ var this: *Load = bun.cast(*Load, ctx);
+ var completion = this.completion orelse return;
+ this.value = .{
+ .err = logger.Msg.fromJS(bun.default_allocator, completion.globalThis, this.path, exception) catch @panic("Out of memory in addError callback"),
+ };
+ completion.bundler.onLoadAsync(this);
+ },
+ else => @panic("invalid error type"),
+ }
+ }
};
};