Allow reading config from within plugins, and partially implement esbuild initialOptions (#2861)

* Implement plugin build.config and initialOptions

* update types

* default initialoptions entrypoints
This commit is contained in:
dave caruso
2023-05-11 22:58:41 -04:00
committed by GitHub
parent 136b50c746
commit dfd0f3e252
10 changed files with 300 additions and 157 deletions

View File

@@ -83,6 +83,108 @@ pub const JSBundler = struct {
errdefer this.deinit(allocator);
errdefer if (plugins.*) |plugin| plugin.deinit();
// Plugins must be resolved first as they are allowed to mutate the config JSValue
if (try config.getArray(globalThis, "plugins")) |array| {
var iter = array.arrayIterator(globalThis);
while (iter.next()) |plugin| {
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) {
this.server_components.router.set(globalThis, router_value);
} else {
globalThis.throwInvalidArguments("Expected router to be a Bun.FileSystemRouter", .{});
return error.JSError;
}
}
const directive_object = (try internals.getObject(globalThis, "directive")) orelse {
globalThis.throwInvalidArguments("Expected directive to be an object", .{});
return error.JSError;
};
if (try directive_object.getArray(globalThis, "client")) |client_names_array| {
var array_iter = client_names_array.arrayIterator(globalThis);
while (array_iter.next()) |client_name| {
var slice = client_name.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected directive.client to be an array of strings", .{});
return error.JSException;
};
defer slice.deinit();
try this.server_components.client.append(allocator, OwnedString.initCopy(allocator, slice.slice()) catch unreachable);
}
} else {
globalThis.throwInvalidArguments("Expected directive.client to be an array of strings", .{});
return error.JSException;
}
if (try directive_object.getArray(globalThis, "server")) |server_names_array| {
var array_iter = server_names_array.arrayIterator(globalThis);
while (array_iter.next()) |server_name| {
var slice = server_name.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected directive.server to be an array of strings", .{});
return error.JSException;
};
defer slice.deinit();
try this.server_components.server.append(allocator, OwnedString.initCopy(allocator, slice.slice()) catch unreachable);
}
} else {
globalThis.throwInvalidArguments("Expected directive.server to be an array of strings", .{});
return error.JSException;
}
continue;
}
// var decl = PluginDeclaration{
// .name = OwnedString.initEmpty(allocator),
// .setup = .{},
// };
// defer decl.deinit();
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 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,
switch (this.target) {
.bun, .bun_macro => JSC.JSGlobalObject.BunPluginTarget.bun,
.node => JSC.JSGlobalObject.BunPluginTarget.node,
else => .browser,
},
);
break :brk plugins.*.?;
};
var plugin_result = bun_plugins.addPlugin(function, config);
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);
return error.JSError;
}
}
}
if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| {
this.target = target;
}
@@ -289,107 +391,6 @@ pub const JSBundler = struct {
};
}
if (try config.getArray(globalThis, "plugins")) |array| {
var iter = array.arrayIterator(globalThis);
while (iter.next()) |plugin| {
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) {
this.server_components.router.set(globalThis, router_value);
} else {
globalThis.throwInvalidArguments("Expected router to be a Bun.FileSystemRouter", .{});
return error.JSError;
}
}
const directive_object = (try internals.getObject(globalThis, "directive")) orelse {
globalThis.throwInvalidArguments("Expected directive to be an object", .{});
return error.JSError;
};
if (try directive_object.getArray(globalThis, "client")) |client_names_array| {
var array_iter = client_names_array.arrayIterator(globalThis);
while (array_iter.next()) |client_name| {
var slice = client_name.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected directive.client to be an array of strings", .{});
return error.JSException;
};
defer slice.deinit();
try this.server_components.client.append(allocator, OwnedString.initCopy(allocator, slice.slice()) catch unreachable);
}
} else {
globalThis.throwInvalidArguments("Expected directive.client to be an array of strings", .{});
return error.JSException;
}
if (try directive_object.getArray(globalThis, "server")) |server_names_array| {
var array_iter = server_names_array.arrayIterator(globalThis);
while (array_iter.next()) |server_name| {
var slice = server_name.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected directive.server to be an array of strings", .{});
return error.JSException;
};
defer slice.deinit();
try this.server_components.server.append(allocator, OwnedString.initCopy(allocator, slice.slice()) catch unreachable);
}
} else {
globalThis.throwInvalidArguments("Expected directive.server to be an array of strings", .{});
return error.JSException;
}
continue;
}
// var decl = PluginDeclaration{
// .name = OwnedString.initEmpty(allocator),
// .setup = .{},
// };
// defer decl.deinit();
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 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,
switch (this.target) {
.bun, .bun_macro => JSC.JSGlobalObject.BunPluginTarget.bun,
.node => JSC.JSGlobalObject.BunPluginTarget.node,
else => .browser,
},
);
break :brk plugins.*.?;
};
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);
return error.JSError;
}
}
}
return this;
}
@@ -911,11 +912,12 @@ pub const JSBundler = struct {
pub fn addPlugin(
this: *Plugin,
object: JSC.JSValue,
config: JSC.JSValue,
) JSValue {
JSC.markBinding(@src());
const tracer = bun.tracy.traceNamed(@src(), "JSBundler.addPlugin");
defer tracer.end();
return JSBundlerPlugin__runSetupFunction(this, object);
return JSBundlerPlugin__runSetupFunction(this, object, config);
}
pub fn deinit(this: *Plugin) void {
@@ -934,6 +936,7 @@ pub const JSBundler = struct {
extern fn JSBundlerPlugin__runSetupFunction(
*Plugin,
JSC.JSValue,
JSC.JSValue,
) JSValue;
pub export fn JSBundlerPlugin__addError(

View File

@@ -375,7 +375,8 @@ extern "C" Bun::JSBundlerPlugin* JSBundlerPlugin__create(Zig::GlobalObject* glob
extern "C" EncodedJSValue JSBundlerPlugin__runSetupFunction(
Bun::JSBundlerPlugin* plugin,
EncodedJSValue encodedSetupFunction)
EncodedJSValue encodedSetupFunction,
EncodedJSValue encodedConfig)
{
auto& vm = plugin->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
@@ -390,6 +391,7 @@ extern "C" EncodedJSValue JSBundlerPlugin__runSetupFunction(
MarkedArgumentBuffer arguments;
arguments.append(JSValue::decode(encodedSetupFunction));
arguments.append(JSValue::decode(encodedConfig));
auto* lexicalGlobalObject = jsCast<JSFunction*>(JSValue::decode(encodedSetupFunction))->globalObject();
auto result = JSC::call(lexicalGlobalObject, setupFunction, callData, plugin, arguments);

View File

@@ -202,10 +202,10 @@ const char* const s_bundlerPluginRunOnResolvePluginsCode =
const JSC::ConstructAbility s_bundlerPluginRunSetupFunctionCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
const JSC::ConstructorKind s_bundlerPluginRunSetupFunctionCodeConstructorKind = JSC::ConstructorKind::None;
const JSC::ImplementationVisibility s_bundlerPluginRunSetupFunctionCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
const int s_bundlerPluginRunSetupFunctionCodeLength = 3794;
const int s_bundlerPluginRunSetupFunctionCodeLength = 4551;
static const JSC::Intrinsic s_bundlerPluginRunSetupFunctionCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_bundlerPluginRunSetupFunctionCode =
"(function (setup) {\n" \
"(function (setup, config) {\n" \
" \"use strict\";\n" \
" var onLoadPlugins = new Map(),\n" \
" onResolvePlugins = new Map();\n" \
@@ -271,6 +271,10 @@ const char* const s_bundlerPluginRunSetupFunctionCode =
" @throwTypeError(\"On-dispose callbacks are not implemented yet. See https:/\\/github.com/oven-sh/bun/issues/2771\");\n" \
" }\n" \
"\n" \
" function onDispose(callback) {\n" \
" @throwTypeError(\"build.resolve() is not implemented yet. See https:/\\/github.com/oven-sh/bun/issues/2771\");\n" \
" }\n" \
"\n" \
" const processSetupResult = () => {\n" \
" var anyOnLoad = false,\n" \
" anyOnResolve = false;\n" \
@@ -327,11 +331,27 @@ const char* const s_bundlerPluginRunSetupFunctionCode =
" };\n" \
"\n" \
" var setupResult = setup({\n" \
" config,\n" \
" onDispose,\n" \
" onEnd,\n" \
" onLoad,\n" \
" onResolve,\n" \
" onStart,\n" \
" resolve,\n" \
" //\n" \
" initialOptions: {\n" \
" ...config,\n" \
" bundle: true,\n" \
" entryPoints: config.entrypoints ?? config.entryPoints ?? [],\n" \
" minify: typeof config.minify === 'boolean' ? config.minify : false,\n" \
" minifyIdentifiers: config.minify === true || config.minify?.identifiers,\n" \
" minifyWhitespace: config.minify === true || config.minify?.whitespace,\n" \
" minifySyntax: config.minify === true || config.minify?.syntax,\n" \
" outbase: config.root,\n" \
" platform: config.target === 'bun' ? 'node' : config.target,\n" \
" root: undefined,\n" \
" },\n" \
" esbuild: {},\n" \
" });\n" \
"\n" \
" if (setupResult && @isPromise(setupResult)) {\n" \

View File

@@ -65,7 +65,7 @@ extern const JSC::ImplementationVisibility s_bundlerPluginRunOnLoadPluginsCodeIm
#define WEBCORE_FOREACH_BUNDLERPLUGIN_BUILTIN_DATA(macro) \
macro(runOnResolvePlugins, bundlerPluginRunOnResolvePlugins, 5) \
macro(runSetupFunction, bundlerPluginRunSetupFunction, 1) \
macro(runSetupFunction, bundlerPluginRunSetupFunction, 2) \
macro(runOnLoadPlugins, bundlerPluginRunOnLoadPlugins, 4) \
#define WEBCORE_BUILTIN_BUNDLERPLUGIN_RUNONRESOLVEPLUGINS 1

View File

@@ -178,7 +178,7 @@ function runOnResolvePlugins(
}
}
function runSetupFunction(setup) {
function runSetupFunction(setup, config) {
"use strict";
var onLoadPlugins = new Map(),
onResolvePlugins = new Map();
@@ -244,6 +244,10 @@ function runSetupFunction(setup) {
@throwTypeError("On-dispose callbacks are not implemented yet. See https:/\/github.com/oven-sh/bun/issues/2771");
}
function onDispose(callback) {
@throwTypeError("build.resolve() is not implemented yet. See https:/\/github.com/oven-sh/bun/issues/2771");
}
const processSetupResult = () => {
var anyOnLoad = false,
anyOnResolve = false;
@@ -300,11 +304,27 @@ function runSetupFunction(setup) {
};
var setupResult = setup({
config,
onDispose,
onEnd,
onLoad,
onResolve,
onStart,
resolve,
// esbuild's options argument is different, we provide some interop
initialOptions: {
...config,
bundle: true,
entryPoints: config.entrypoints ?? config.entryPoints ?? [],
minify: typeof config.minify === 'boolean' ? config.minify : false,
minifyIdentifiers: config.minify === true || config.minify?.identifiers,
minifyWhitespace: config.minify === true || config.minify?.whitespace,
minifySyntax: config.minify === true || config.minify?.syntax,
outbase: config.root,
platform: config.target === 'bun' ? 'node' : config.target,
root: undefined,
},
esbuild: {},
});
if (setupResult && @isPromise(setupResult)) {