aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-04-24 14:11:59 -0700
committerGravatar GitHub <noreply@github.com> 2023-04-24 14:11:59 -0700
commit923ac39c0b718ac5d488f65232f0dcd7161423d4 (patch)
treec4a35c8e7c8f16a5139a4622b8a26ff2331f85b6
parent98209b8e101c8c0199f1360f7c1781938f502ed8 (diff)
downloadbun-923ac39c0b718ac5d488f65232f0dcd7161423d4.tar.gz
bun-923ac39c0b718ac5d488f65232f0dcd7161423d4.tar.zst
bun-923ac39c0b718ac5d488f65232f0dcd7161423d4.zip
Support plugins in `Bun.build` (#2720)
* wip * Implement `onLoad` plugins * Support exceptions and async `onLoad` plugins * Fix filtering * Handle empty files * Fix JSON loader --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--src/bun.js/api/JSBundler.zig444
-rw-r--r--src/bun.js/bindings/JSBundlerPlugin.cpp526
-rw-r--r--src/bun.js/bindings/JSBundlerPlugin.h103
-rw-r--r--src/bun.js/bindings/ModuleLoader.cpp139
-rw-r--r--src/bun.js/bindings/ModuleLoader.h29
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp4
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h6
-rw-r--r--src/bun.js/bindings/headers-handwritten.h36
-rw-r--r--src/bun.js/module_loader.zig12
-rw-r--r--src/bun.zig1
-rw-r--r--src/bundler/bundle_v2.zig150
-rw-r--r--src/logger.zig17
-rw-r--r--src/options.zig11
13 files changed, 1372 insertions, 106 deletions
diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig
index 4bb5cad3d..c53d21f12 100644
--- a/src/bun.js/api/JSBundler.zig
+++ b/src/bun.js/api/JSBundler.zig
@@ -41,13 +41,9 @@ const Mimalloc = @import("../../mimalloc_arena.zig");
const Runtime = @import("../../runtime.zig").Runtime;
const JSLexer = bun.js_lexer;
const Expr = JSAst.Expr;
+const Index = @import("../../ast/base.zig").Index;
pub const JSBundler = struct {
- heap: Mimalloc.Arena,
- allocator: std.mem.Allocator,
- configs: Config.List = .{},
- has_pending_activity: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(true),
-
const OwnedString = bun.MutableString;
pub const Config = struct {
@@ -62,7 +58,6 @@ pub const JSBundler = struct {
code_splitting: bool = false,
minify: Minify = .{},
server_components: ServerComponents = ServerComponents{},
- plugins: PluginDeclaration.List = .{},
names: Names = .{},
label: OwnedString = OwnedString.initEmpty(bun.default_allocator),
@@ -71,21 +66,7 @@ pub const JSBundler = struct {
pub const List = bun.StringArrayHashMapUnmanaged(Config);
- ///
- /// { name: "", setup: (build) {} }
- pub const PluginDeclaration = struct {
- name: OwnedString = OwnedString.initEmpty(bun.default_allocator),
- setup: JSC.Strong = .{},
-
- pub const List = std.ArrayListUnmanaged(PluginDeclaration);
-
- pub fn deinit(this: *PluginDeclaration) void {
- this.name.deinit();
- this.setup.deinit();
- }
- };
-
- pub fn fromJS(globalThis: *JSC.JSGlobalObject, config: JSC.JSValue, allocator: std.mem.Allocator) !Config {
+ pub fn fromJS(globalThis: *JSC.JSGlobalObject, config: JSC.JSValue, plugins: *?*Plugin, allocator: std.mem.Allocator) !Config {
var this = Config{
.entry_points = bun.StringSet.init(allocator),
.external = bun.StringSet.init(allocator),
@@ -99,6 +80,7 @@ pub const JSBundler = struct {
},
};
errdefer this.deinit(allocator);
+ errdefer if (plugins.*) |plugin| plugin.deinit();
if (try config.getOptionalEnum(globalThis, "target", options.Platform)) |target| {
this.target = target;
@@ -194,12 +176,6 @@ pub const JSBundler = struct {
if (try config.getArray(globalThis, "plugins")) |array| {
var iter = array.arrayIterator(globalThis);
while (iter.next()) |plugin| {
- var decl = PluginDeclaration{
- .name = OwnedString.initEmpty(allocator),
- .setup = .{},
- };
- errdefer decl.deinit();
-
if (try plugin.getObject(globalThis, "SECRET_SERVER_COMPONENTS_INTERNALS")) |internals| {
if (internals.get(globalThis, "router")) |router_value| {
if (router_value.as(JSC.API.FileSystemRouter) != null) {
@@ -244,21 +220,47 @@ pub const JSBundler = struct {
globalThis.throwInvalidArguments("Expected directive.server to be an array of strings", .{});
return error.JSException;
}
- }
- if (try plugin.getOptional(globalThis, "name", ZigString.Slice)) |slice| {
- defer slice.deinit();
- decl.name.appendSliceExact(slice.slice()) catch unreachable;
+ continue;
}
- if (try plugin.getFunction(globalThis, "setup")) |setup| {
- decl.setup.set(globalThis, setup);
+ // var decl = PluginDeclaration{
+ // .name = OwnedString.initEmpty(allocator),
+ // .setup = .{},
+ // };
+ // 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);
} else {
globalThis.throwInvalidArguments("Expected plugin to have a setup() function", .{});
return error.JSError;
}
- try this.plugins.append(allocator, decl);
+ var bun_plugins: *Plugin = plugins.* orelse brk: {
+ plugins.* = Plugin.create(
+ globalThis,
+ switch (this.target) {
+ .bun, .bun_macro => JSC.JSGlobalObject.BunPluginTarget.bun,
+ .node => JSC.JSGlobalObject.BunPluginTarget.node,
+ else => .browser,
+ },
+ );
+ break :brk plugins.*.?;
+ };
+
+ const plugin_result = bun_plugins.addPlugin(globalThis, plugin);
+
+ if (plugin_result.toError()) |err| {
+ globalThis.throwValue(err);
+ bun_plugins.deinit();
+ return error.JSError;
+ }
}
}
@@ -312,7 +314,6 @@ pub const JSBundler = struct {
self.define.deinit();
self.dir.deinit();
self.serve.deinit(allocator);
- self.plugins.deinit(allocator);
self.server_components.deinit(allocator);
self.names.deinit();
self.label.deinit();
@@ -324,12 +325,14 @@ pub const JSBundler = struct {
globalThis: *JSC.JSGlobalObject,
arguments: []const JSC.JSValue,
) JSC.JSValue {
- const config = Config.fromJS(globalThis, arguments[0], globalThis.allocator()) catch {
+ var plugins: ?*Plugin = null;
+ const config = Config.fromJS(globalThis, arguments[0], &plugins, globalThis.allocator()) catch {
return JSC.JSValue.jsUndefined();
};
return bun.BundleV2.generateFromJavaScript(
config,
+ plugins,
globalThis,
globalThis.bunVM().eventLoop(),
bun.default_allocator,
@@ -351,4 +354,373 @@ pub const JSBundler = struct {
) js.JSValueRef {
return build(globalThis, @ptrCast([]const JSC.JSValue, arguments_)).asObjectRef();
}
+
+ pub const Resolve = struct {
+ import_record: *bun.ImportRecord,
+ source_file: string = "",
+ default_namespace: string = "",
+
+ /// Null means the Resolve is aborted
+ completion: ?*bun.BundleV2.JSBundleCompletionTask = null,
+
+ value: Value,
+
+ pub const Value = union(enum) {
+ err: logger.Msg,
+ success: struct {
+ path: []const u8 = "",
+ namespace: []const u8 = "",
+
+ pub fn deinit(this: *@This()) void {
+ bun.default_allocator.destroy(this.path);
+ bun.default_allocator.destroy(this.namespace);
+ }
+ },
+ no_match: void,
+ pending: JSC.JSPromise.Strong,
+ 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 => {},
+ }
+ this.* = .{ .consumed = {} };
+ }
+ };
+
+ pub fn deinit(this: *Resolve) void {
+ this.value.deinit();
+ if (this.completion) |completion|
+ completion.deref();
+ }
+
+ const AnyTask = JSC.AnyTask.New(@This(), runOnJSThread);
+
+ pub fn runOnJSThread(this: *Load) void {
+ var completion = this.completion orelse {
+ this.deinit();
+ return;
+ };
+
+ const result = completion.plugins.?.matchOnResolve(
+ completion.globalThis,
+ this.path,
+ this.namespace,
+ this,
+ );
+
+ this.value = Value.fromJS(completion.globalThis, this.source_file, this.default_namespace, result);
+ completion.bundler.onResolveAsync(this);
+ }
+ };
+
+ pub const Load = struct {
+ source_index: Index,
+ default_loader: options.Loader,
+ path: []const u8 = "",
+ namespace: []const u8 = "",
+
+ /// Null means the task was aborted.
+ completion: ?*bun.BundleV2.JSBundleCompletionTask = null,
+
+ value: Value,
+ js_task: JSC.AnyTask = undefined,
+ task: JSC.AnyEventLoop.Task = undefined,
+ parse_task: *bun.ParseTask = undefined,
+
+ pub fn create(
+ completion: *bun.BundleV2.JSBundleCompletionTask,
+ source_index: Index,
+ default_loader: options.Loader,
+ path: Fs.Path,
+ ) Load {
+ return Load{
+ .source_index = source_index,
+ .default_loader = default_loader,
+ .completion = completion,
+ .value = .{ .pending = .{} },
+ .path = path.text,
+ .namespace = path.namespace,
+ };
+ }
+
+ pub const Value = union(enum) {
+ err: logger.Msg,
+ success: struct {
+ source_code: []const u8 = "",
+ loader: options.Loader = options.Loader.file,
+ },
+ pending: JSC.JSPromise.Strong,
+ 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 => {},
+ }
+ this.* = .{ .consumed = {} };
+ }
+
+ pub fn consume(this: *Value) Value {
+ const result = this.*;
+ this.* = .{ .consumed = {} };
+ return result;
+ }
+ };
+
+ pub fn deinit(this: *Load) void {
+ this.value.deinit();
+ if (this.completion) |completion|
+ completion.deref();
+ }
+
+ const AnyTask = JSC.AnyTask.New(@This(), runOnJSThread);
+
+ pub fn runOnJSThread(this: *Load) void {
+ var completion = this.completion orelse {
+ this.deinit();
+ return;
+ };
+
+ const err = completion.plugins.?.matchOnLoad(
+ completion.globalThis,
+ this.path,
+ this.namespace,
+ this,
+ );
+
+ if (this.value == .pending) {
+ if (!err.isEmptyOrUndefinedOrNull()) {
+ var code = ZigString.Empty;
+ JSBundlerPlugin__OnLoadAsync(this, err, &code, .js);
+ }
+ }
+ }
+
+ pub fn dispatch(this: *Load) void {
+ var completion = this.completion orelse {
+ this.deinit();
+ return;
+ };
+ completion.ref();
+
+ 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);
+ }
+
+ export fn JSBundlerPlugin__getDefaultLoader(this: *Load) options.Loader {
+ return this.default_loader;
+ }
+
+ export fn JSBundlerPlugin__OnLoadAsync(
+ this: *Load,
+ error_value: JSC.JSValue,
+ source_code: *ZigString,
+ loader: options.Loader,
+ ) 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);
+ 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();
+ }
+ }
+
+ comptime {
+ _ = JSBundlerPlugin__getDefaultLoader;
+ _ = 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);
+ }
+
+ extern fn JSBundlerPlugin__tombestone(*Plugin) void;
+
+ extern fn JSBundlerPlugin__anyMatches(
+ *Plugin,
+ namespaceString: *const ZigString,
+ path: *const ZigString,
+ bool,
+ ) bool;
+
+ extern fn JSBundlerPlugin__matchOnLoad(
+ *JSC.JSGlobalObject,
+ *Plugin,
+ namespaceString: *const ZigString,
+ path: *const ZigString,
+ context: *anyopaque,
+ ) JSValue;
+
+ extern fn JSBundlerPlugin__matchOnResolve(
+ *JSC.JSGlobalObject,
+ *Plugin,
+ namespaceString: *const ZigString,
+ path: *const ZigString,
+ importer: *const ZigString,
+ context: *anyopaque,
+ ) JSValue;
+
+ pub fn hasAnyMatches(
+ this: *Plugin,
+ path: *const Fs.Path,
+ is_onLoad: bool,
+ ) bool {
+ const namespace_string = if (strings.eqlComptime(path.namespace, "file"))
+ ZigString.Empty
+ else
+ ZigString.fromUTF8(path.namespace);
+ const path_string = ZigString.fromUTF8(path.text);
+ return JSBundlerPlugin__anyMatches(this, &namespace_string, &path_string, is_onLoad);
+ }
+
+ pub fn matchOnLoad(
+ this: *Plugin,
+ globalThis: *JSC.JSGlobalObject,
+ path: []const u8,
+ namespace: []const u8,
+ context: *anyopaque,
+ ) JSC.JSValue {
+ const namespace_string = if (strings.eqlComptime(namespace, "file"))
+ ZigString.Empty
+ else
+ ZigString.fromUTF8(namespace);
+ const path_string = ZigString.fromUTF8(path);
+ return JSBundlerPlugin__matchOnLoad(globalThis, this, &namespace_string, &path_string, context);
+ }
+
+ pub fn matchOnResolve(
+ this: *Plugin,
+ globalThis: *JSC.JSGlobalObject,
+ path: []const u8,
+ namespace: []const u8,
+ importer: []const u8,
+ context: *anyopaque,
+ ) JSC.JSValue {
+ 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);
+ }
+
+ pub fn addPlugin(
+ this: *Plugin,
+ globalObject: *JSC.JSGlobalObject,
+ object: JSC.JSValue,
+ ) JSValue {
+ return setupJSBundlerPlugin(this, globalObject, object);
+ }
+
+ pub fn deinit(this: *Plugin) void {
+ JSBundlerPlugin__tombestone(this);
+ }
+
+ pub fn setConfig(this: *Plugin, config: *anyopaque) void {
+ JSBundlerPlugin__setConfig(this, config);
+ }
+
+ extern fn JSBundlerPlugin__setConfig(*Plugin, *anyopaque) void;
+
+ extern fn setupJSBundlerPlugin(
+ *Plugin,
+ *JSC.JSGlobalObject,
+ JSC.JSValue,
+ ) JSValue;
+ };
};
diff --git a/src/bun.js/bindings/JSBundlerPlugin.cpp b/src/bun.js/bindings/JSBundlerPlugin.cpp
new file mode 100644
index 000000000..2aaa40270
--- /dev/null
+++ b/src/bun.js/bindings/JSBundlerPlugin.cpp
@@ -0,0 +1,526 @@
+#include "JSBundlerPlugin.h"
+
+#include "headers-handwritten.h"
+#include "JavaScriptCore/CatchScope.h"
+#include "JavaScriptCore/JSGlobalObject.h"
+#include "JavaScriptCore/JSTypeInfo.h"
+#include "JavaScriptCore/Structure.h"
+#include "helpers.h"
+#include "ZigGlobalObject.h"
+#include "JavaScriptCore/JavaScript.h"
+#include "JavaScriptCore/JSObjectInlines.h"
+#include "wtf/text/WTFString.h"
+#include "JavaScriptCore/JSCInlines.h"
+
+#include "JavaScriptCore/ObjectConstructor.h"
+#include "JavaScriptCore/SubspaceInlines.h"
+#include "JavaScriptCore/RegExpObject.h"
+#include "JavaScriptCore/JSPromise.h"
+#include "BunClientData.h"
+#include "ModuleLoader.h"
+#include "JavaScriptCore/RegularExpression.h"
+
+namespace Bun {
+
+#define WRAP_BUNDLER_PLUGIN(argName) JSValue(bitwise_cast<double>(reinterpret_cast<uintptr_t>(argName)))
+#define UNWRAP_BUNDLER_PLUGIN(callFrame) reinterpret_cast<JSBundlerPlugin*>(bitwise_cast<uintptr_t>(callFrame->thisValue().asDouble()))
+
+WTF_MAKE_ISO_ALLOCATED_IMPL(JSBundlerPlugin);
+
+static bool isValidNamespaceString(String& namespaceString)
+{
+ static JSC::Yarr::RegularExpression* namespaceRegex = nullptr;
+ if (!namespaceRegex) {
+ namespaceRegex = new JSC::Yarr::RegularExpression("^([/@a-zA-Z0-9_\\-]+)$"_s);
+ }
+ return namespaceRegex->match(namespaceString) > -1;
+}
+
+static EncodedJSValue jsFunctionAppendOnLoadPluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, JSBundlerPlugin& plugin)
+{
+ JSC::VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ Ref protect(plugin);
+
+ if (callframe->argumentCount() < 2) {
+ throwException(globalObject, scope, createError(globalObject, "onLoad() requires at least 2 arguments"_s));
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto* filterObject = callframe->uncheckedArgument(0).toObject(globalObject);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ auto clientData = WebCore::clientData(vm);
+ auto& builtinNames = clientData->builtinNames();
+ JSC::RegExpObject* filter = nullptr;
+ if (JSValue filterValue = filterObject->getIfPropertyExists(globalObject, builtinNames.filterPublicName())) {
+ if (filterValue.isCell() && filterValue.asCell()->inherits<JSC::RegExpObject>())
+ filter = jsCast<JSC::RegExpObject*>(filterValue);
+ }
+
+ if (!filter) {
+ throwException(globalObject, scope, createError(globalObject, "onLoad() expects first argument to be an object with a filter RegExp"_s));
+ return JSValue::encode(jsUndefined());
+ }
+
+ String namespaceString = String();
+ if (JSValue namespaceValue = filterObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "namespace"_s))) {
+ if (namespaceValue.isString()) {
+ namespaceString = namespaceValue.toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ if (!isValidNamespaceString(namespaceString)) {
+ throwException(globalObject, scope, createError(globalObject, "namespace can only contain letters, numbers, dashes, or underscores"_s));
+ return JSValue::encode(jsUndefined());
+ }
+ }
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ }
+
+ auto func = callframe->uncheckedArgument(1);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+ if (!func.isCell() || !func.isCallable()) {
+ throwException(globalObject, scope, createError(globalObject, "onLoad() expects second argument to be a function"_s));
+ return JSValue::encode(jsUndefined());
+ }
+
+ plugin.onLoad.append(vm, filter->regExp(), jsCast<JSFunction*>(func), namespaceString);
+
+ return JSValue::encode(jsUndefined());
+}
+
+static EncodedJSValue jsFunctionAppendOnResolvePluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, JSBundlerPlugin& plugin)
+{
+ Ref protect(plugin);
+ JSC::VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ if (callframe->argumentCount() < 2) {
+ throwException(globalObject, scope, createError(globalObject, "onResolve() requires at least 2 arguments"_s));
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto* filterObject = callframe->uncheckedArgument(0).toObject(globalObject);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ auto clientData = WebCore::clientData(vm);
+ auto& builtinNames = clientData->builtinNames();
+ JSC::RegExpObject* filter = nullptr;
+ if (JSValue filterValue = filterObject->getIfPropertyExists(globalObject, builtinNames.filterPublicName())) {
+ if (filterValue.isCell() && filterValue.asCell()->inherits<JSC::RegExpObject>())
+ filter = jsCast<JSC::RegExpObject*>(filterValue);
+ }
+
+ if (!filter) {
+ throwException(globalObject, scope, createError(globalObject, "onResolve() expects first argument to be an object with a filter RegExp"_s));
+ return JSValue::encode(jsUndefined());
+ }
+
+ String namespaceString = String();
+ if (JSValue namespaceValue = filterObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "namespace"_s))) {
+ if (namespaceValue.isString()) {
+ namespaceString = namespaceValue.toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ if (!isValidNamespaceString(namespaceString)) {
+ throwException(globalObject, scope, createError(globalObject, "namespace can only contain letters, numbers, dashes, or underscores"_s));
+ return JSValue::encode(jsUndefined());
+ }
+ }
+
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ }
+
+ auto func = callframe->uncheckedArgument(1);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+ if (!func.isCell() || !func.isCallable()) {
+ throwException(globalObject, scope, createError(globalObject, "onResolve() expects second argument to be a function"_s));
+ return JSValue::encode(jsUndefined());
+ }
+
+ plugin.onResolve.append(vm, filter->regExp(), jsCast<JSFunction*>(func), namespaceString);
+
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnLoadJSBundlerPlugin, (JSGlobalObject * globalObject, CallFrame* callframe))
+{
+ auto& plugin = *UNWRAP_BUNDLER_PLUGIN(callframe);
+ return jsFunctionAppendOnLoadPluginBody(globalObject, callframe, plugin);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnResolveJSBundlerPlugin, (JSGlobalObject * globalObject, CallFrame* callframe))
+{
+ auto& plugin = *UNWRAP_BUNDLER_PLUGIN(callframe);
+ return jsFunctionAppendOnResolvePluginBody(globalObject, callframe, plugin);
+}
+
+extern "C" EncodedJSValue setupJSBundlerPlugin(JSBundlerPlugin* bundlerPlugin, JSC::JSGlobalObject* globalObject, JSValue objValue)
+{
+ JSC::VM& vm = globalObject->vm();
+ auto clientData = WebCore::clientData(vm);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ if (!objValue || !objValue.isObject()) {
+ JSC::throwTypeError(globalObject, throwScope, "plugin needs to be an object"_s);
+ return JSValue::encode(throwScope.exception());
+ }
+
+ JSC::JSObject* obj = objValue.toObject(globalObject);
+
+ JSC::JSValue setupFunctionValue = obj->getIfPropertyExists(globalObject, Identifier::fromString(vm, "setup"_s));
+ if (!setupFunctionValue || setupFunctionValue.isUndefinedOrNull() || !setupFunctionValue.isCell() || !setupFunctionValue.isCallable()) {
+ JSC::throwTypeError(globalObject, throwScope, "plugin needs a setup() function"_s);
+ return JSValue::encode(throwScope.exception());
+ }
+
+ JSFunction* setupFunction = jsCast<JSFunction*>(setupFunctionValue);
+ JSObject* builderObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 3);
+
+ JSC::JSFunction* onLoadFunction = JSC::JSFunction::create(vm, globalObject, 1, "onLoad"_s, jsFunctionAppendOnLoadJSBundlerPlugin, ImplementationVisibility::Public);
+ JSC::JSFunction* onResolveFunction = JSC::JSFunction::create(vm, globalObject, 1, "onResolve"_s, jsFunctionAppendOnResolveJSBundlerPlugin, ImplementationVisibility::Public);
+ JSC::JSBoundFunction* boundOnLoadFunction = JSC::JSBoundFunction::create(
+ vm,
+ globalObject,
+ onLoadFunction,
+ WRAP_BUNDLER_PLUGIN(bundlerPlugin),
+ JSC::ArgList(),
+ 1,
+ jsString(vm, String("onLoad"_s)));
+
+ JSC::JSBoundFunction* boundOnResolveFunction = JSC::JSBoundFunction::create(
+ vm,
+ globalObject,
+ onResolveFunction,
+ WRAP_BUNDLER_PLUGIN(bundlerPlugin),
+ JSC::ArgList(),
+ 1,
+ jsString(vm, String("onResolve"_s)));
+
+ bundlerPlugin->ref();
+ vm.heap.addFinalizer(boundOnLoadFunction, [bundlerPlugin](JSC::JSCell* cell) {
+ bundlerPlugin->deref();
+ });
+
+ bundlerPlugin->ref();
+ vm.heap.addFinalizer(boundOnResolveFunction, [bundlerPlugin](JSC::JSCell* cell) {
+ bundlerPlugin->deref();
+ });
+
+ builderObject->putDirect(
+ vm,
+ JSC::Identifier::fromString(vm, "onLoad"_s),
+ boundOnLoadFunction,
+ JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
+ builderObject->putDirect(
+ vm,
+ JSC::Identifier::fromString(vm, "onResolve"_s),
+ boundOnResolveFunction,
+ JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
+
+ JSC::MarkedArgumentBuffer args;
+ args.append(builderObject);
+
+ JSFunction* function = jsCast<JSFunction*>(setupFunctionValue);
+ JSC::CallData callData = JSC::getCallData(function);
+ JSValue result = call(globalObject, function, callData, JSC::jsUndefined(), args);
+
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(throwScope.exception()));
+
+ if (auto* promise = JSC::jsDynamicCast<JSC::JSPromise*>(result)) {
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(promise));
+ }
+
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
+}
+
+void JSBundlerPlugin::Group::append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func)
+{
+ Yarr::RegularExpression regex(
+ StringView(filter->pattern()),
+ filter->flags().contains(Yarr::Flags::IgnoreCase) ? Yarr::TextCaseSensitivity::TextCaseInsensitive : Yarr::TextCaseSensitivity::TextCaseInsensitive,
+ filter->multiline() ? Yarr::MultilineMode::MultilineEnabled : Yarr::MultilineMode::MultilineDisabled,
+ filter->eitherUnicode() ? Yarr::UnicodeMode::UnicodeAwareMode : Yarr::UnicodeMode::UnicodeUnawareMode);
+ filters.append(WTFMove(regex));
+ callbacks.append(JSC::Strong<JSC::JSFunction> { vm, func });
+}
+
+void JSBundlerPlugin::Base::append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func, String& namespaceString)
+{
+ if (namespaceString.isEmpty() || namespaceString == "file"_s) {
+ this->fileNamespace.append(vm, filter, func);
+ } else if (auto found = this->group(namespaceString)) {
+ found->append(vm, filter, func);
+ } else {
+ Group newGroup;
+ newGroup.append(vm, filter, func);
+ this->groups.append(WTFMove(newGroup));
+ this->namespaces.append(namespaceString);
+ }
+}
+
+JSFunction* JSBundlerPlugin::Group::find(String& path)
+{
+ size_t count = filters.size();
+ for (size_t i = 0; i < count; i++) {
+ int matchLength = 0;
+ if (filters[i].match(path, 0, &matchLength)) {
+ return callbacks[i].get();
+ }
+ }
+
+ return nullptr;
+}
+
+EncodedJSValue JSBundlerPlugin::OnResolve::run(const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context)
+{
+ Group* groupPtr = this->group(namespaceString ? Zig::toString(*namespaceString) : String());
+ if (groupPtr == nullptr) {
+ return JSValue::encode(jsUndefined());
+ }
+ Group& group = *groupPtr;
+
+ auto pathString = Zig::toString(*path);
+
+ JSC::JSFunction* function = group.find(pathString);
+ if (!function) {
+ return JSValue::encode(JSC::jsUndefined());
+ }
+
+ JSC::MarkedArgumentBuffer arguments;
+ JSC::JSGlobalObject* globalObject = function->globalObject();
+ auto& vm = globalObject->vm();
+
+ JSC::JSObject* paramsObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2);
+ auto clientData = WebCore::clientData(vm);
+ auto& builtinNames = clientData->builtinNames();
+ paramsObject->putDirect(
+ vm, clientData->builtinNames().pathPublicName(),
+ Zig::toJSStringValue(*path, globalObject));
+ paramsObject->putDirect(
+ vm, clientData->builtinNames().importerPublicName(),
+ Zig::toJSStringValue(*importer, globalObject));
+ arguments.append(paramsObject);
+
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto scope = DECLARE_CATCH_SCOPE(vm);
+ scope.assertNoExceptionExceptTermination();
+
+ JSC::CallData callData = JSC::getCallData(function);
+
+ auto result = call(globalObject, function, callData, JSC::jsUndefined(), arguments);
+ if (UNLIKELY(scope.exception())) {
+ return JSValue::encode(scope.exception());
+ }
+
+ if (auto* promise = JSC::jsDynamicCast<JSPromise*>(result)) {
+ switch (promise->status(vm)) {
+ case JSPromise::Status::Pending: {
+ return JSValue::encode(promise);
+ }
+ case JSPromise::Status::Rejected: {
+ promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(static_cast<unsigned>(JSC::JSPromise::Status::Fulfilled)));
+ result = promise->result(vm);
+ return JSValue::encode(result);
+ }
+ case JSPromise::Status::Fulfilled: {
+ result = promise->result(vm);
+ break;
+ }
+ }
+ }
+
+ if (!result.isObject()) {
+ JSC::throwTypeError(globalObject, throwScope, "onLoad() expects an object returned"_s);
+ return JSValue::encode({});
+ }
+
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+}
+
+EncodedJSValue JSBundlerPlugin::OnLoad::run(const ZigString* namespaceString, const ZigString* path, void* context)
+{
+ Group* groupPtr = this->group(namespaceString ? Zig::toString(*namespaceString) : String());
+ if (groupPtr == nullptr) {
+ return JSValue::encode(jsUndefined());
+ }
+ Group& group = *groupPtr;
+
+ auto pathString = Zig::toString(*path);
+
+ JSC::JSFunction* function = group.find(pathString);
+ if (!function) {
+ return JSValue::encode(JSC::jsUndefined());
+ }
+
+ JSC::MarkedArgumentBuffer arguments;
+ JSC::JSGlobalObject* globalObject = function->globalObject();
+ auto& vm = globalObject->vm();
+
+ auto& callbacks = group.callbacks;
+
+ auto& filters = group.filters;
+
+ for (size_t i = 0; i < filters.size(); i++) {
+ if (!filters[i].match(pathString)) {
+ continue;
+ }
+ JSC::JSFunction* function = callbacks[i].get();
+ if (UNLIKELY(!function)) {
+ continue;
+ }
+
+ JSC::MarkedArgumentBuffer arguments;
+ JSC::VM& vm = globalObject->vm();
+
+ JSC::JSObject* paramsObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 1);
+ auto clientData = WebCore::clientData(vm);
+ auto& builtinNames = clientData->builtinNames();
+ paramsObject->putDirect(
+ vm, clientData->builtinNames().pathPublicName(),
+ Zig::toJSStringValue(*path, globalObject));
+ arguments.append(paramsObject);
+
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto scope = DECLARE_CATCH_SCOPE(vm);
+ scope.assertNoExceptionExceptTermination();
+
+ JSC::CallData callData = JSC::getCallData(function);
+
+ auto result = call(globalObject, function, callData, JSC::jsUndefined(), arguments);
+
+ if (UNLIKELY(!scope.exception() && result && !result.isUndefinedOrNull() && !result.isCell())) {
+ throwTypeError(globalObject, throwScope, "onLoad() expects an object returned"_s);
+ }
+
+ if (UNLIKELY(scope.exception())) {
+ JSC::Exception* exception = scope.exception();
+ scope.clearException();
+ return JSValue::encode(exception);
+ }
+
+ result = Bun::handleVirtualModuleResultForJSBundlerPlugin(
+ reinterpret_cast<Zig::GlobalObject*>(globalObject),
+ result,
+ path,
+ nullptr,
+ context);
+
+ if (UNLIKELY(scope.exception())) {
+ JSC::Exception* exception = scope.exception();
+ scope.clearException();
+ return JSValue::encode(exception);
+ }
+
+ if (!result || result.isUndefined()) {
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
+ }
+
+ if (auto* promise = JSC::jsDynamicCast<JSPromise*>(result)) {
+ switch (promise->status(vm)) {
+ case JSPromise::Status::Pending: {
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+ }
+ case JSPromise::Status::Rejected: {
+ promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(static_cast<unsigned>(JSC::JSPromise::Status::Fulfilled)));
+ result = promise->result(vm);
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+ }
+ case JSPromise::Status::Fulfilled: {
+ result = promise->result(vm);
+ break;
+ }
+ }
+ }
+
+ if (!result.isObject()) {
+ JSC::throwTypeError(globalObject, throwScope, "onResolve() expects an object returned"_s);
+ JSC::Exception* exception = scope.exception();
+ scope.clearException();
+ return JSValue::encode(exception);
+ }
+
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+ }
+
+ return JSValue::encode(JSC::jsUndefined());
+}
+
+bool JSBundlerPlugin::anyMatchesCrossThread(const ZigString* namespaceStr, const ZigString* path, bool isOnLoad)
+{
+ auto namespaceString = namespaceStr ? Zig::toString(*namespaceStr) : String();
+ auto pathString = Zig::toString(*path);
+
+ if (isOnLoad) {
+ auto* group = this->onLoad.group(namespaceString);
+ if (group == nullptr) {
+ return false;
+ }
+
+ auto& filters = group->filters;
+
+ for (auto& filter : filters) {
+ if (filter.match(pathString) > -1) {
+ return true;
+ }
+ }
+
+ } else {
+ auto* group = this->onResolve.group(namespaceString);
+ if (group == nullptr) {
+ return false;
+ }
+
+ auto& filters = group->filters;
+
+ for (auto& filter : filters) {
+ if (filter.match(pathString) > -1) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+} // namespace Bun
+
+extern "C" bool JSBundlerPlugin__anyMatches(Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, bool isOnLoad)
+{
+ return plugin->anyMatchesCrossThread(namespaceString, path, isOnLoad);
+}
+
+extern "C" JSC::EncodedJSValue JSBundlerPlugin__matchOnLoad(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, void* context)
+{
+ Ref protect(*plugin);
+ return plugin->onLoad.run(
+ namespaceString,
+ path,
+ context);
+}
+
+extern "C" JSC::EncodedJSValue JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context)
+{
+ Ref protect(*plugin);
+ return plugin->onResolve.run(
+ namespaceString,
+ path,
+ importer,
+ context);
+}
+
+extern "C" Bun::JSBundlerPlugin* JSBundlerPlugin__create(Zig::GlobalObject* globalObject, BunPluginTarget target)
+{
+ RefPtr<Bun::JSBundlerPlugin> plugin = adoptRef(*new Bun::JSBundlerPlugin(target, nullptr));
+ plugin->ref();
+ return plugin.leakRef();
+}
+
+extern "C" void JSBundlerPlugin__setConfig(Bun::JSBundlerPlugin* plugin, void* config)
+{
+ plugin->config = config;
+}
+
+extern "C" void JSBundlerPlugin__tombestone(Bun::JSBundlerPlugin* plugin)
+{
+ plugin->tombstone();
+ plugin->deref();
+}
diff --git a/src/bun.js/bindings/JSBundlerPlugin.h b/src/bun.js/bindings/JSBundlerPlugin.h
new file mode 100644
index 000000000..fde124ccf
--- /dev/null
+++ b/src/bun.js/bindings/JSBundlerPlugin.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "root.h"
+#include "headers-handwritten.h"
+#include "JavaScriptCore/JSGlobalObject.h"
+#include "JavaScriptCore/Strong.h"
+#include "JavaScriptCore/RegularExpression.h"
+#include "helpers.h"
+#include <JavaScriptCore/Yarr.h>
+
+namespace Bun {
+
+using namespace JSC;
+
+class JSBundlerPlugin final : public WTF::RefCounted<JSBundlerPlugin> {
+public:
+ WTF_MAKE_ISO_ALLOCATED(JSBundlerPlugin);
+ static JSBundlerPlugin* create(JSC::JSGlobalObject* globalObject, BunPluginTarget target, void* config = nullptr);
+
+ // This is a list of pairs of regexps and functions to match against
+ class Group {
+
+ public:
+ Vector<Yarr::RegularExpression> filters = {};
+ Vector<JSC::Strong<JSC::JSFunction>> callbacks = {};
+
+ void append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func);
+ JSFunction* find(String& path);
+ void clear()
+ {
+ filters.clear();
+ callbacks.clear();
+ }
+ };
+
+ class Base {
+ public:
+ Group fileNamespace = {};
+ Vector<String> namespaces = {};
+ Vector<Group> groups = {};
+ BunPluginTarget target { BunPluginTargetBun };
+
+ Group* group(const String& namespaceStr)
+ {
+ if (namespaceStr.isEmpty()) {
+ return &fileNamespace;
+ }
+
+ for (size_t i = 0; i < namespaces.size(); i++) {
+ if (namespaces[i] == namespaceStr) {
+ return &groups[i];
+ }
+ }
+
+ return nullptr;
+ }
+
+ void append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func, String& namespaceString);
+ };
+
+ class OnLoad final : public Base {
+
+ public:
+ OnLoad()
+ : Base()
+ {
+ }
+
+ EncodedJSValue run(const ZigString* namespaceString, const ZigString* path, void* context);
+ };
+
+ class OnResolve final : public Base {
+
+ public:
+ OnResolve()
+ : Base()
+ {
+ }
+
+ EncodedJSValue run(const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context);
+ };
+
+public:
+ bool anyMatchesCrossThread(const ZigString* namespaceStr, const ZigString* path, bool isOnLoad);
+ void tombstone() { tombstoned = true; }
+
+ JSBundlerPlugin(BunPluginTarget target, void* config)
+ {
+ this->target = target;
+ this->config = config;
+ }
+
+ OnLoad onLoad = {};
+ OnResolve onResolve = {};
+ BunPluginTarget target { BunPluginTargetBrowser };
+ void* config { nullptr };
+ bool tombstoned { false };
+
+ using RefCounted::deref;
+ using RefCounted::ref;
+};
+
+} // namespace Zig \ No newline at end of file
diff --git a/src/bun.js/bindings/ModuleLoader.cpp b/src/bun.js/bindings/ModuleLoader.cpp
index 40e41b083..00b975d88 100644
--- a/src/bun.js/bindings/ModuleLoader.cpp
+++ b/src/bun.js/bindings/ModuleLoader.cpp
@@ -40,7 +40,56 @@ namespace Bun {
using namespace Zig;
using namespace WebCore;
-extern "C" BunLoaderType Bun__getDefaultLoader(JSC::JSGlobalObject*, ZigString* specifier);
+extern "C" BunLoaderType Bun__getDefaultLoader(JSC::JSGlobalObject*, const ZigString* specifier);
+extern "C" BunLoaderType JSBundlerPlugin__getDefaultLoader(void* context);
+extern "C" void JSBundlerPlugin__OnLoadAsync(void* ctx, EncodedJSValue errorValue, ZigString* sourceCode, BunLoaderType loader);
+OnLoadResult handleOnLoadResult(Zig::GlobalObject* globalObject, JSC::JSValue objectValue, const ZigString* specifier, void* context);
+
+JSValue handleVirtualModuleResultForJSBundlerPlugin(
+ Zig::GlobalObject* globalObject,
+ JSValue virtualModuleResult,
+ const ZigString* specifier,
+ const ZigString* referrer,
+ void* bundlerPluginContext)
+{
+ auto onLoadResult = handleOnLoadResult(globalObject, virtualModuleResult, specifier, bundlerPluginContext);
+ JSC::VM& vm = globalObject->vm();
+
+ switch (onLoadResult.type) {
+ case OnLoadResultTypeCode: {
+ JSBundlerPlugin__OnLoadAsync(bundlerPluginContext, JSValue::encode({}), &onLoadResult.value.sourceText.string, onLoadResult.value.sourceText.loader);
+ return jsUndefined();
+ }
+ case OnLoadResultTypeError: {
+ JSBundlerPlugin__OnLoadAsync(bundlerPluginContext, JSValue::encode(onLoadResult.value.error), nullptr, BunLoaderTypeNone);
+ return jsUndefined();
+ }
+
+ case OnLoadResultTypePromise: {
+ JSFunction* performPromiseThenFunction = globalObject->performPromiseThenFunction();
+ auto callData = JSC::getCallData(performPromiseThenFunction);
+ ASSERT(callData.type != CallData::Type::None);
+ auto specifierString = Zig::toString(*specifier);
+ auto referrerString = referrer ? Zig::toString(*referrer) : String();
+ PendingVirtualModuleResult* pendingModule = PendingVirtualModuleResult::create(globalObject, specifierString, referrerString, bundlerPluginContext);
+ pendingModule->internalField(2).set(vm, pendingModule, virtualModuleResult);
+ JSC::JSPromise* promise = pendingModule->promise();
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(promise);
+ arguments.append(globalObject->thenable(jsFunctionOnLoadObjectResultResolveForJSBundlerPlugin));
+ arguments.append(globalObject->thenable(jsFunctionOnLoadObjectResultRejectForJSBundlerPlugin));
+ arguments.append(jsUndefined());
+ arguments.append(pendingModule);
+ ASSERT(!arguments.hasOverflowed());
+ JSC::call(globalObject, performPromiseThenFunction, callData, jsUndefined(), arguments);
+ return promise;
+ }
+ default: {
+ __builtin_unreachable();
+ }
+ }
+}
static JSC::JSInternalPromise* rejectedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value)
{
@@ -88,11 +137,16 @@ JSC::JSInternalPromise* PendingVirtualModuleResult::internalPromise()
return jsCast<JSC::JSInternalPromise*>(internalField(2).get());
}
+JSC::JSPromise* PendingVirtualModuleResult::promise()
+{
+ return jsCast<JSC::JSPromise*>(internalField(2).get());
+}
+
const ClassInfo PendingVirtualModuleResult::s_info = { "PendingVirtualModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(PendingVirtualModuleResult) };
-PendingVirtualModuleResult* PendingVirtualModuleResult::create(VM& vm, Structure* structure)
+PendingVirtualModuleResult* PendingVirtualModuleResult::create(VM& vm, Structure* structure, void* bundlerPluginContext)
{
- PendingVirtualModuleResult* mod = new (NotNull, allocateCell<PendingVirtualModuleResult>(vm)) PendingVirtualModuleResult(vm, structure);
+ PendingVirtualModuleResult* mod = new (NotNull, allocateCell<PendingVirtualModuleResult>(vm)) PendingVirtualModuleResult(vm, structure, bundlerPluginContext);
return mod;
}
Structure* PendingVirtualModuleResult::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
@@ -100,8 +154,9 @@ Structure* PendingVirtualModuleResult::createStructure(VM& vm, JSGlobalObject* g
return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info());
}
-PendingVirtualModuleResult::PendingVirtualModuleResult(VM& vm, Structure* structure)
+PendingVirtualModuleResult::PendingVirtualModuleResult(VM& vm, Structure* structure, void* bundlerPluginContext)
: Base(vm, structure)
+ , m_bundlerPluginContext(bundlerPluginContext)
{
}
@@ -110,7 +165,9 @@ void PendingVirtualModuleResult::finishCreation(VM& vm, const WTF::String& speci
Base::finishCreation(vm);
Base::internalField(0).set(vm, this, JSC::jsString(vm, specifier));
Base::internalField(1).set(vm, this, JSC::jsString(vm, referrer));
- Base::internalField(2).set(vm, this, JSC::JSInternalPromise::create(vm, globalObject()->internalPromiseStructure()));
+ if (!this->m_bundlerPluginContext) {
+ Base::internalField(2).set(vm, this, JSC::JSInternalPromise::create(vm, globalObject()->internalPromiseStructure()));
+ }
}
template<typename Visitor>
@@ -123,21 +180,22 @@ void PendingVirtualModuleResult::visitChildrenImpl(JSCell* cell, Visitor& visito
DEFINE_VISIT_CHILDREN(PendingVirtualModuleResult);
-PendingVirtualModuleResult* PendingVirtualModuleResult::create(JSC::JSGlobalObject* globalObject, const WTF::String& specifier, const WTF::String& referrer)
+PendingVirtualModuleResult* PendingVirtualModuleResult::create(JSC::JSGlobalObject* globalObject, const WTF::String& specifier, const WTF::String& referrer, void* bundlerPluginContext)
{
- auto* virtualModule = create(globalObject->vm(), reinterpret_cast<Zig::GlobalObject*>(globalObject)->pendingVirtualModuleResultStructure());
+ auto* virtualModule = create(globalObject->vm(), reinterpret_cast<Zig::GlobalObject*>(globalObject)->pendingVirtualModuleResultStructure(), bundlerPluginContext);
virtualModule->finishCreation(globalObject->vm(), specifier, referrer);
return virtualModule;
}
-OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::JSValue objectValue, ZigString* specifier)
+OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::JSValue objectValue, const ZigString* specifier, void* bunPluginContext)
{
OnLoadResult result = {};
result.type = OnLoadResultTypeError;
+ result.bundlerPluginContext = bunPluginContext;
JSC::VM& vm = globalObject->vm();
result.value.error = JSC::jsUndefined();
auto scope = DECLARE_THROW_SCOPE(vm);
- BunLoaderType loader = Bun__getDefaultLoader(globalObject, specifier);
+ BunLoaderType loader = bunPluginContext ? JSBundlerPlugin__getDefaultLoader(bunPluginContext) : Bun__getDefaultLoader(globalObject, specifier);
if (JSC::Exception* exception = JSC::jsDynamicCast<JSC::Exception*>(objectValue)) {
result.value.error = exception->value();
@@ -211,16 +269,17 @@ OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::
return result;
}
-static OnLoadResult handleOnLoadResult(Zig::GlobalObject* globalObject, JSC::JSValue objectValue, ZigString* specifier)
+OnLoadResult handleOnLoadResult(Zig::GlobalObject* globalObject, JSC::JSValue objectValue, const ZigString* specifier, void* context)
{
if (JSC::JSPromise* promise = JSC::jsDynamicCast<JSC::JSPromise*>(objectValue)) {
OnLoadResult result = {};
result.type = OnLoadResultTypePromise;
result.value.promise = objectValue;
+ result.bundlerPluginContext = context;
return result;
}
- return handleOnLoadResultNotPromise(globalObject, objectValue, specifier);
+ return handleOnLoadResultNotPromise(globalObject, objectValue, specifier, context);
}
template<bool allowPromise>
@@ -228,10 +287,10 @@ static JSValue handleVirtualModuleResult(
Zig::GlobalObject* globalObject,
JSValue virtualModuleResult,
ErrorableResolvedSource* res,
- ZigString* specifier,
- ZigString* referrer)
+ const ZigString* specifier,
+ const ZigString* referrer)
{
- auto onLoadResult = handleOnLoadResult(globalObject, virtualModuleResult, specifier);
+ auto onLoadResult = handleOnLoadResult(globalObject, virtualModuleResult, specifier, nullptr);
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
@@ -353,8 +412,8 @@ template<bool allowPromise>
static JSValue fetchSourceCode(
Zig::GlobalObject* globalObject,
ErrorableResolvedSource* res,
- ZigString* specifier,
- ZigString* referrer)
+ const ZigString* specifier,
+ const ZigString* referrer)
{
void* bunVM = globalObject->bunVM();
auto& vm = globalObject->vm();
@@ -486,6 +545,46 @@ static JSValue fetchSourceCode(
return rejectOrResolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(WTFMove(provider))));
}
+extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultResolveForJSBundlerPlugin(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
+{
+ JSC::VM& vm = globalObject->vm();
+ ErrorableResolvedSource res = {};
+ res.success = false;
+ JSC::JSValue objectResult = callFrame->argument(0);
+ PendingVirtualModuleResult* pendingModule = JSC::jsCast<PendingVirtualModuleResult*>(callFrame->argument(1));
+ JSC::JSValue specifierString = pendingModule->internalField(0).get();
+ JSC::JSValue referrerString = pendingModule->internalField(1).get();
+ pendingModule->internalField(0).set(vm, pendingModule, JSC::jsUndefined());
+ pendingModule->internalField(1).set(vm, pendingModule, JSC::jsUndefined());
+ void* bunPluginContext = pendingModule->m_bundlerPluginContext;
+ JSC::JSPromise* promise = pendingModule->promise();
+
+ ZigString specifier = Zig::toZigString(specifierString, globalObject);
+ ZigString referrer = Zig::toZigString(referrerString, globalObject);
+ return JSC::JSValue::encode(
+ handleVirtualModuleResultForJSBundlerPlugin(reinterpret_cast<Zig::GlobalObject*>(globalObject), objectResult, &specifier, &referrer, bunPluginContext));
+}
+
+extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultRejectForJSBundlerPlugin(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
+{
+ JSC::VM& vm = globalObject->vm();
+ ErrorableResolvedSource res = {};
+ JSC::JSValue reason = callFrame->argument(0);
+ PendingVirtualModuleResult* pendingModule = JSC::jsCast<PendingVirtualModuleResult*>(callFrame->argument(1));
+ JSC::JSValue specifierString = pendingModule->internalField(0).get();
+ JSC::JSValue referrerString = pendingModule->internalField(1).get();
+ pendingModule->internalField(0).set(vm, pendingModule, JSC::jsUndefined());
+ pendingModule->internalField(1).set(vm, pendingModule, JSC::jsUndefined());
+
+ ZigString specifier = Zig::toZigString(specifierString, globalObject);
+ ZigString referrer = Zig::toZigString(referrerString, globalObject);
+ pendingModule->internalField(2).set(vm, pendingModule, JSC::jsUndefined());
+
+ JSBundlerPlugin__OnLoadAsync(pendingModule->m_bundlerPluginContext, JSValue::encode(reason), nullptr, BunLoaderTypeNone);
+
+ return JSValue::encode(reason);
+}
+
extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultResolve(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
{
JSC::VM& vm = globalObject->vm();
@@ -544,8 +643,8 @@ extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultReject(JSC::JSGlobalO
JSValue fetchSourceCodeSync(
Zig::GlobalObject* globalObject,
ErrorableResolvedSource* res,
- ZigString* specifier,
- ZigString* referrer)
+ const ZigString* specifier,
+ const ZigString* referrer)
{
return fetchSourceCode<false>(globalObject, res, specifier, referrer);
}
@@ -553,8 +652,8 @@ JSValue fetchSourceCodeSync(
JSValue fetchSourceCodeAsync(
Zig::GlobalObject* globalObject,
ErrorableResolvedSource* res,
- ZigString* specifier,
- ZigString* referrer)
+ const ZigString* specifier,
+ const ZigString* referrer)
{
return fetchSourceCode<true>(globalObject, res, specifier, referrer);
}
diff --git a/src/bun.js/bindings/ModuleLoader.h b/src/bun.js/bindings/ModuleLoader.h
index 9090abcdc..f24a8fdb1 100644
--- a/src/bun.js/bindings/ModuleLoader.h
+++ b/src/bun.js/bindings/ModuleLoader.h
@@ -37,6 +37,7 @@ union OnLoadResultValue {
struct OnLoadResult {
OnLoadResultValue value;
OnLoadResultType type;
+ void* bundlerPluginContext { nullptr };
};
class PendingVirtualModuleResult : public JSC::JSInternalFieldObjectImpl<3> {
@@ -55,12 +56,13 @@ public:
[](auto& spaces, auto&& space) { spaces.m_subspaceForPendingVirtualModuleResult = std::forward<decltype(space)>(space); });
}
- JS_EXPORT_PRIVATE static PendingVirtualModuleResult* create(VM&, Structure*);
- static PendingVirtualModuleResult* create(JSC::JSGlobalObject* globalObject, const WTF::String& specifier, const WTF::String& referrer);
+ JS_EXPORT_PRIVATE static PendingVirtualModuleResult* create(VM&, Structure*, void* bundlerPluginContext = nullptr);
+ static PendingVirtualModuleResult* create(JSC::JSGlobalObject* globalObject, const WTF::String& specifier, const WTF::String& referrer, void* bundlerPluginContext = nullptr);
static PendingVirtualModuleResult* createWithInitialValues(VM&, Structure*);
static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
JSC::JSInternalPromise* internalPromise();
+ JSC::JSPromise* promise();
static std::array<JSValue, numberOfInternalFields> initialValues()
{
@@ -74,21 +76,32 @@ public:
DECLARE_EXPORT_INFO;
DECLARE_VISIT_CHILDREN;
- PendingVirtualModuleResult(JSC::VM&, JSC::Structure*);
+ void* m_bundlerPluginContext { nullptr };
+
+ PendingVirtualModuleResult(JSC::VM&, JSC::Structure*, void* bundlerPluginContext = nullptr);
void finishCreation(JSC::VM&, const WTF::String& specifier, const WTF::String& referrer);
};
-OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::JSValue objectValue);
+OnLoadResult handleOnLoadResult(Zig::GlobalObject* globalObject, JSC::JSValue objectValue, ZigString* specifier, void* bunPluginContext = nullptr);
+OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::JSValue objectValue, void* bunPluginContext = nullptr);
+
+JSValue handleVirtualModuleResultForJSBundlerPlugin(
+ Zig::GlobalObject* globalObject,
+ JSValue virtualModuleResult,
+ const ZigString* specifier,
+ const ZigString* referrer,
+ void* bundlerPluginContext);
+
JSValue fetchSourceCodeSync(
Zig::GlobalObject* globalObject,
ErrorableResolvedSource* res,
- ZigString* specifier,
- ZigString* referrer);
+ const ZigString* specifier,
+ const ZigString* referrer);
JSValue fetchSourceCodeAsync(
Zig::GlobalObject* globalObject,
ErrorableResolvedSource* res,
- ZigString* specifier,
- ZigString* referrer);
+ const ZigString* specifier,
+ const ZigString* referrer);
} // namespace Bun \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index b969acf4c..a00d49343 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -3944,6 +3944,10 @@ GlobalObject::PromiseFunctions GlobalObject::promiseHandlerID(EncodedJSValue (*h
return GlobalObject::PromiseFunctions::Bun__TestScope__onReject;
} else if (handler == Bun__TestScope__onResolve) {
return GlobalObject::PromiseFunctions::Bun__TestScope__onResolve;
+ } else if (handler == jsFunctionOnLoadObjectResultRejectForJSBundlerPlugin) {
+ return GlobalObject::PromiseFunctions::jsFunctionOnLoadObjectResultResolveForJSBundlerPlugin;
+ } else if (handler == jsFunctionOnLoadObjectResultResolveForJSBundlerPlugin) {
+ return GlobalObject::PromiseFunctions::jsFunctionOnLoadObjectResultRejectForJSBundlerPlugin;
} else if (handler == CallbackJob__onResolve) {
return GlobalObject::PromiseFunctions::CallbackJob__onResolve;
} else if (handler == CallbackJob__onReject) {
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 0bdfd1d82..e16230b32 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -48,6 +48,8 @@ extern "C" void Bun__reportUnhandledError(JSC__JSGlobalObject*, JSC::EncodedJSVa
// defined in ModuleLoader.cpp
extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultResolve(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame);
extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultReject(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame);
+extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultResolveForJSBundlerPlugin(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame);
+extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultRejectForJSBundlerPlugin(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame);
// #include "EventTarget.h"
// namespace WebCore {
@@ -292,6 +294,8 @@ public:
jsFunctionOnLoadObjectResultResolve,
jsFunctionOnLoadObjectResultReject,
+ jsFunctionOnLoadObjectResultResolveForJSBundlerPlugin,
+ jsFunctionOnLoadObjectResultRejectForJSBundlerPlugin,
Bun__TestScope__onReject,
Bun__TestScope__onResolve,
@@ -299,7 +303,7 @@ public:
CallbackJob__onResolve,
CallbackJob__onReject,
};
- static constexpr size_t promiseFunctionsSize = 22;
+ static constexpr size_t promiseFunctionsSize = 24;
static PromiseFunctions promiseHandlerID(EncodedJSValue (*handler)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1));
diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h
index d4e1e72af..24a540320 100644
--- a/src/bun.js/bindings/headers-handwritten.h
+++ b/src/bun.js/bindings/headers-handwritten.h
@@ -119,17 +119,17 @@ const JSErrorCode JSErrorCodeStackOverflow = 253;
const JSErrorCode JSErrorCodeUserErrorCode = 254;
typedef uint8_t BunLoaderType;
-const BunLoaderType BunLoaderTypeNone = 0;
-const BunLoaderType BunLoaderTypeJSX = 1;
-const BunLoaderType BunLoaderTypeJS = 2;
-const BunLoaderType BunLoaderTypeTS = 3;
-const BunLoaderType BunLoaderTypeTSX = 4;
-const BunLoaderType BunLoaderTypeCSS = 5;
-const BunLoaderType BunLoaderTypeFILE = 6;
-const BunLoaderType BunLoaderTypeJSON = 7;
-const BunLoaderType BunLoaderTypeTOML = 8;
-const BunLoaderType BunLoaderTypeWASM = 9;
-const BunLoaderType BunLoaderTypeNAPI = 10;
+const BunLoaderType BunLoaderTypeNone = 254;
+const BunLoaderType BunLoaderTypeJSX = 0;
+const BunLoaderType BunLoaderTypeJS = 1;
+const BunLoaderType BunLoaderTypeTS = 2;
+const BunLoaderType BunLoaderTypeTSX = 3;
+const BunLoaderType BunLoaderTypeCSS = 4;
+const BunLoaderType BunLoaderTypeFILE = 5;
+const BunLoaderType BunLoaderTypeJSON = 6;
+const BunLoaderType BunLoaderTypeTOML = 7;
+const BunLoaderType BunLoaderTypeWASM = 8;
+const BunLoaderType BunLoaderTypeNAPI = 9;
#pragma mark - Stream
@@ -220,21 +220,21 @@ extern "C" void Microtask__run_default(void* ptr, void* global);
extern "C" bool Bun__transpileVirtualModule(
JSC::JSGlobalObject* global,
- ZigString* specifier,
- ZigString* referrer,
+ const ZigString* specifier,
+ const ZigString* referrer,
ZigString* sourceCode,
BunLoaderType loader,
ErrorableResolvedSource* result);
extern "C" JSC::EncodedJSValue Bun__runVirtualModule(
JSC::JSGlobalObject* global,
- ZigString* specifier);
+ const ZigString* specifier);
extern "C" void* Bun__transpileFile(
void* bunVM,
JSC::JSGlobalObject* global,
- ZigString* specifier,
- ZigString* referrer,
+ const ZigString* specifier,
+ const ZigString* referrer,
ErrorableResolvedSource* result, bool allowPromise);
extern "C" JSC::EncodedJSValue CallbackJob__onResolve(JSC::JSGlobalObject*, JSC::CallFrame*);
@@ -243,8 +243,8 @@ extern "C" JSC::EncodedJSValue CallbackJob__onReject(JSC::JSGlobalObject*, JSC::
extern "C" bool Bun__fetchBuiltinModule(
void* bunVM,
JSC::JSGlobalObject* global,
- ZigString* specifier,
- ZigString* referrer,
+ const ZigString* specifier,
+ const ZigString* referrer,
ErrorableResolvedSource* result);
// Used in process.version
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index ede5622dc..7e20e61bc 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -849,7 +849,7 @@ pub const ModuleLoader = struct {
) void;
};
- pub export fn Bun__getDefaultLoader(global: *JSC.JSGlobalObject, str: *ZigString) Api.Loader {
+ pub export fn Bun__getDefaultLoader(global: *JSC.JSGlobalObject, str: *const ZigString) Api.Loader {
var jsc_vm = global.bunVM();
const filename = str.toSlice(jsc_vm.allocator);
defer filename.deinit();
@@ -1468,8 +1468,8 @@ pub const ModuleLoader = struct {
pub export fn Bun__transpileFile(
jsc_vm: *VirtualMachine,
globalObject: *JSC.JSGlobalObject,
- specifier_ptr: *ZigString,
- referrer: *ZigString,
+ specifier_ptr: *const ZigString,
+ referrer: *const ZigString,
ret: *ErrorableResolvedSource,
allow_promise: bool,
) ?*anyopaque {
@@ -1522,7 +1522,7 @@ pub const ModuleLoader = struct {
return promise;
}
- export fn Bun__runVirtualModule(globalObject: *JSC.JSGlobalObject, specifier_ptr: *ZigString) JSValue {
+ export fn Bun__runVirtualModule(globalObject: *JSC.JSGlobalObject, specifier_ptr: *const ZigString) JSValue {
JSC.markBinding(@src());
if (globalObject.bunVM().plugin_runner == null) return JSValue.zero;
@@ -2084,8 +2084,8 @@ pub const ModuleLoader = struct {
export fn Bun__transpileVirtualModule(
globalObject: *JSC.JSGlobalObject,
- specifier_ptr: *ZigString,
- referrer_ptr: *ZigString,
+ specifier_ptr: *const ZigString,
+ referrer_ptr: *const ZigString,
source_code: *ZigString,
loader_: Api.Loader,
ret: *ErrorableResolvedSource,
diff --git a/src/bun.zig b/src/bun.zig
index e6349fee9..336757ded 100644
--- a/src/bun.zig
+++ b/src/bun.zig
@@ -1348,6 +1348,7 @@ pub const StringMap = struct {
pub const DotEnv = @import("./env_loader.zig");
pub const BundleV2 = @import("./bundler/bundle_v2.zig").BundleV2;
+pub const ParseTask = @import("./bundler/bundle_v2.zig").ParseTask;
pub const Lock = @import("./lock.zig").Lock;
pub const UnboundedQueue = @import("./bun.js/unbounded_queue.zig").UnboundedQueue;
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig
index edfbc50eb..335edc033 100644
--- a/src/bundler/bundle_v2.zig
+++ b/src/bundler/bundle_v2.zig
@@ -267,6 +267,11 @@ pub const BundleV2 = struct {
graph: Graph = Graph{},
linker: LinkerContext = LinkerContext{ .loop = undefined },
bun_watcher: ?*Watcher.Watcher = null,
+ plugins: ?*JSC.API.JSBundler.Plugin = null,
+ completion: ?*JSBundleCompletionTask = null,
+
+ /// Allocations not tracked by a threadlocal heap
+ free_list: std.ArrayList(string) = std.ArrayList(string).init(bun.default_allocator),
const debug = Output.scoped(.Bundle, false);
@@ -399,7 +404,12 @@ pub const BundleV2 = struct {
task.jsx = this.bundler.options.jsx;
task.task.node.next = null;
task.tree_shaking = this.linker.options.tree_shaking;
- batch.push(ThreadPoolLib.Batch.from(&task.task));
+
+ // Handle onLoad plugins as entry points
+ if (!this.enqueueOnLoadPluginIfNeeded(task)) {
+ batch.push(ThreadPoolLib.Batch.from(&task.task));
+ }
+
return source_index.get();
}
@@ -577,6 +587,7 @@ pub const BundleV2 = struct {
pub fn generateFromJavaScript(
config: bun.JSC.API.JSBundler.Config,
+ plugins: ?*bun.JSC.API.JSBundler.Plugin,
globalThis: *JSC.JSGlobalObject,
event_loop: *bun.JSC.EventLoop,
allocator: std.mem.Allocator,
@@ -587,12 +598,16 @@ pub const BundleV2 = struct {
.jsc_event_loop = event_loop,
.promise = JSC.JSPromise.Strong.init(globalThis),
.globalThis = globalThis,
- .ref = JSC.PollRef.init(),
+ .poll_ref = JSC.PollRef.init(),
.env = globalThis.bunVM().bundler.env,
+ .plugins = plugins,
.log = Logger.Log.init(bun.default_allocator),
.task = JSBundleCompletionTask.TaskCompletion.init(completion),
};
+ if (plugins) |plugin|
+ plugin.setConfig(completion);
+
// Ensure this exists before we spawn the thread to prevent any race
// conditions from creating two
_ = JSC.WorkPool.get();
@@ -613,7 +628,7 @@ pub const BundleV2 = struct {
BundleThread.instance.waker.wake() catch {};
}
- completion.ref.ref(globalThis.bunVM());
+ completion.poll_ref.ref(globalThis.bunVM());
return completion.promise.value();
}
@@ -628,13 +643,16 @@ pub const BundleV2 = struct {
task: bun.JSC.AnyTask,
globalThis: *JSC.JSGlobalObject,
promise: JSC.JSPromise.Strong,
- ref: JSC.PollRef = JSC.PollRef.init(),
+ poll_ref: JSC.PollRef = JSC.PollRef.init(),
env: *bun.DotEnv.Loader,
log: Logger.Log,
result: Result = .{ .pending = {} },
next: ?*JSBundleCompletionTask = null,
+ bundler: *BundleV2 = undefined,
+ plugins: ?*bun.JSC.API.JSBundler.Plugin = null,
+ ref_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(1),
pub const Result = union(enum) {
pending: void,
@@ -644,15 +662,22 @@ pub const BundleV2 = struct {
pub const TaskCompletion = bun.JSC.AnyTask.New(JSBundleCompletionTask, onComplete);
- pub fn onComplete(this: *JSBundleCompletionTask) void {
- var globalThis = this.globalThis;
-
- defer {
+ pub fn deref(this: *JSBundleCompletionTask) void {
+ if (this.ref_count.fetchSub(1, .Monotonic) == 1) {
this.config.deinit(bun.default_allocator);
bun.default_allocator.destroy(this);
}
+ }
+
+ pub fn ref(this: *JSBundleCompletionTask) void {
+ _ = this.ref_count.fetchAdd(1, .Monotonic);
+ }
+
+ pub fn onComplete(this: *JSBundleCompletionTask) void {
+ var globalThis = this.globalThis;
+ defer this.deref();
- this.ref.unref(globalThis.bunVM());
+ this.poll_ref.unref(globalThis.bunVM());
const promise = this.promise.swap();
const root_obj = JSC.JSValue.createEmptyObject(globalThis, 2);
@@ -674,6 +699,10 @@ pub const BundleV2 = struct {
.value => |*build| {
var output_files: []options.OutputFile = build.output_files.items;
const output_files_js = JSC.JSValue.createEmptyArray(globalThis, output_files.len);
+ if (output_files_js == .zero) {
+ @panic("Unexpected pending JavaScript exception in JSBundleCompletionTask.onComplete. This is a bug in Bun.");
+ }
+
defer build.output_files.deinit();
for (output_files, 0..) |*output_file, i| {
var obj = JSC.JSValue.createEmptyObject(globalThis, 2);
@@ -710,6 +739,70 @@ pub const BundleV2 = struct {
}
};
+ pub fn onLoadAsync(
+ this: *BundleV2,
+ load: *bun.JSC.API.JSBundler.Load,
+ ) void {
+ this.loop().enqueueTaskConcurrent(
+ bun.JSC.API.JSBundler.Load,
+ BundleV2,
+ load,
+ BundleV2.onLoad,
+ .task,
+ );
+ }
+
+ pub fn onResolveAsync(
+ this: *BundleV2,
+ resolve: *bun.JSC.API.JSBundler.Resolve,
+ ) void {
+ this.loop().enqueueTaskConcurrent(
+ bun.JSC.API.JSBundler.Resolve,
+ BundleV2,
+ resolve,
+ BundleV2.onResolve,
+ .task,
+ );
+ }
+
+ pub fn onLoad(
+ load: *bun.JSC.API.JSBundler.Load,
+ this: *BundleV2,
+ ) void {
+ defer load.deinit();
+
+ switch (load.value.consume()) {
+ .success => |code| {
+ this.graph.input_files.items(.loader)[load.source_index.get()] = code.loader;
+ this.graph.input_files.items(.source)[load.source_index.get()].contents = code.source_code;
+ var parse_task = load.parse_task;
+ parse_task.loader = code.loader;
+ this.free_list.append(code.source_code) catch unreachable;
+ parse_task.contents_or_fd = .{
+ .contents = code.source_code,
+ };
+ this.graph.pool.pool.schedule(ThreadPoolLib.Batch.from(&parse_task.task));
+ },
+ .err => |err| {
+ this.bundler.log.msgs.append(err) catch unreachable;
+ this.bundler.log.errors += @as(usize, @boolToInt(err.kind == .err));
+ this.bundler.log.warnings += @as(usize, @boolToInt(err.kind == .warn));
+
+ // An error ocurred, prevent spinning the event loop forever
+ this.graph.parse_pending -= 1;
+ },
+ .pending, .consumed => unreachable,
+ }
+ }
+
+ pub fn onResolve(
+ this: *BundleV2,
+ resolve: *bun.JSC.API.JSBundler.Resolve,
+ ) void {
+ _ = this;
+ defer resolve.deinit();
+ }
+
pub fn generateInNewThreadWrap(
instance: *BundleThread,
) void {
@@ -793,6 +886,9 @@ pub const BundleV2 = struct {
bundler.resolver.opts = bundler.options;
var this = try BundleV2.init(bundler, allocator, JSC.AnyEventLoop.init(allocator), false, JSC.WorkPool.get(), heap);
+ this.plugins = completion.plugins;
+ this.completion = completion;
+ completion.bundler = this;
defer {
if (this.graph.pool.pool.threadpool_context == @ptrCast(?*anyopaque, this.graph.pool)) {
@@ -831,6 +927,12 @@ pub const BundleV2 = struct {
this.graph.pool.pool.wakeForIdleEvents();
}
+
+ for (this.free_list.items) |free| {
+ bun.default_allocator.free(free);
+ }
+
+ this.free_list.clearAndFree();
}
pub fn runFromJSInNewThread(this: *BundleV2, config: *const bun.JSC.API.JSBundler.Config) !std.ArrayList(options.OutputFile) {
@@ -879,6 +981,26 @@ pub const BundleV2 = struct {
return try this.linker.generateChunksInParallel(chunks);
}
+ pub fn enqueueOnLoadPluginIfNeeded(this: *BundleV2, parse: *ParseTask) bool {
+ if (this.plugins) |plugins| {
+ if (plugins.hasAnyMatches(&parse.path, true)) {
+ // This is where onLoad plugins are enqueued
+ var load = bun.default_allocator.create(JSC.API.JSBundler.Load) catch unreachable;
+ load.* = JSC.API.JSBundler.Load.create(
+ this.completion.?,
+ parse.source_index,
+ parse.path.loader(&this.bundler.options.loaders) orelse options.Loader.js,
+ parse.path,
+ );
+ load.parse_task = parse;
+ load.dispatch();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
pub fn onParseTaskComplete(parse_result: *ParseTask.Result, this: *BundleV2) void {
defer bun.default_allocator.destroy(parse_result);
@@ -979,9 +1101,13 @@ pub const BundleV2 = struct {
new_task.ctx = this;
graph.input_files.append(graph.allocator, new_input_file) catch unreachable;
graph.ast.append(graph.allocator, js_ast.Ast.empty) catch unreachable;
- batch.push(ThreadPoolLib.Batch.from(&new_task.task));
-
diff += 1;
+
+ if (this.enqueueOnLoadPluginIfNeeded(new_task)) {
+ continue;
+ }
+
+ batch.push(ThreadPoolLib.Batch.from(&new_task.task));
} else {
bun.default_allocator.destroy(value);
}
@@ -1041,7 +1167,7 @@ pub const BundleV2 = struct {
const UseDirective = js_ast.UseDirective;
-const ParseTask = struct {
+pub const ParseTask = struct {
path: Fs.Path,
secondary_path_for_commonjs_interop: ?Fs.Path = null,
contents_or_fd: union(enum) {
diff --git a/src/logger.zig b/src/logger.zig
index 13dd98f96..bee10ff31 100644
--- a/src/logger.zig
+++ b/src/logger.zig
@@ -204,7 +204,7 @@ pub const Data = struct {
text: string,
location: ?Location = null,
pub fn deinit(d: *Data, allocator: std.mem.Allocator) void {
- if (d.location) |loc| {
+ if (d.location) |*loc| {
loc.deinit(allocator);
}
@@ -401,6 +401,19 @@ pub const Msg = struct {
metadata: Metadata = .{ .build = 0 },
notes: ?[]Data = null,
+ pub fn fromJS(allocator: std.mem.Allocator, globalObject: *bun.JSC.JSGlobalObject, file: string, err: bun.JSC.JSValue) !Msg {
+ var zig_exception_holder: bun.JSC.ZigException.Holder = bun.JSC.ZigException.Holder.init();
+ err.toZigException(globalObject, zig_exception_holder.zigException());
+ return Msg{
+ .data = .{
+ .text = zig_exception_holder.zigException().message.toSliceClone(allocator).slice(),
+ .location = Location{
+ .file = file,
+ },
+ },
+ };
+ }
+
pub fn count(this: *const Msg, builder: *StringBuilder) void {
this.data.count(builder);
if (this.notes) |notes| {
@@ -490,7 +503,7 @@ pub const Msg = struct {
pub fn deinit(msg: *Msg, allocator: std.mem.Allocator) void {
msg.data.deinit(allocator);
if (msg.notes) |notes| {
- for (notes) |note| {
+ for (notes) |*note| {
note.deinit(allocator);
}
}
diff --git a/src/options.zig b/src/options.zig
index 26485eb70..48ef405a0 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -646,7 +646,7 @@ pub const Platform = enum {
};
};
-pub const Loader = enum {
+pub const Loader = enum(u8) {
jsx,
js,
ts,
@@ -2089,8 +2089,13 @@ pub const OutputFile = struct {
.buffer => |buffer| brk: {
var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
blob.* = JSC.WebCore.Blob.init(@constCast(buffer.bytes), buffer.allocator, globalObject);
- blob.store.?.mime_type = this.loader.toMimeType();
- blob.content_type = blob.store.?.mime_type.value;
+ if (blob.store) |store| {
+ store.mime_type = this.loader.toMimeType();
+ blob.content_type = store.mime_type.value;
+ } else {
+ blob.content_type = this.loader.toMimeType().value;
+ }
+
blob.allocator = bun.default_allocator;
const blob_jsvalue = blob.toJS(globalObject);
blob_jsvalue.ensureStillAlive();