diff options
author | 2023-04-25 07:27:18 -0700 | |
---|---|---|
committer | 2023-04-25 07:27:18 -0700 | |
commit | 126885e1fe509b69be947d79aacb3ed6efdf666a (patch) | |
tree | bef407938525d69132824ffd3b0b54796035009d /src/bun.js/bindings/JSBundlerPlugin.cpp | |
parent | 5353d4101493632cb25d0cdddfef94f62bc5902d (diff) | |
download | bun-126885e1fe509b69be947d79aacb3ed6efdf666a.tar.gz bun-126885e1fe509b69be947d79aacb3ed6efdf666a.tar.zst bun-126885e1fe509b69be947d79aacb3ed6efdf666a.zip |
Implement `onResolve` plugins in `Bun.build()`, support multiple onLoad and onResolve plugins (#2739)
* its 2023
* WIP `onResolve` plugins
* more progress
* it compiles
* Lots of small fixes
* Seems to work excluding entry points
* Update BundlerPluginBuiltins.cpp
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js/bindings/JSBundlerPlugin.cpp')
-rw-r--r-- | src/bun.js/bindings/JSBundlerPlugin.cpp | 692 |
1 files changed, 284 insertions, 408 deletions
diff --git a/src/bun.js/bindings/JSBundlerPlugin.cpp b/src/bun.js/bindings/JSBundlerPlugin.cpp index 2aaa40270..1ac8b0c30 100644 --- a/src/bun.js/bindings/JSBundlerPlugin.cpp +++ b/src/bun.js/bindings/JSBundlerPlugin.cpp @@ -19,508 +19,384 @@ #include "BunClientData.h" #include "ModuleLoader.h" #include "JavaScriptCore/RegularExpression.h" +#include <JavaScriptCore/LazyProperty.h> +#include <JavaScriptCore/LazyPropertyInlines.h> +#include <JavaScriptCore/VMTrapsInlines.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())) +#define WRAP_BUNDLER_PLUGIN(argName) jsNumber(bitwise_cast<double>(reinterpret_cast<uintptr_t>(argName))) +#define UNWRAP_BUNDLER_PLUGIN(callFrame) reinterpret_cast<void*>(bitwise_cast<uintptr_t>(callFrame->argument(0).asDouble())) -WTF_MAKE_ISO_ALLOCATED_IMPL(JSBundlerPlugin); +extern "C" void JSBundlerPlugin__addError(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue); +extern "C" void JSBundlerPlugin__onLoadAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue); +extern "C" void JSBundlerPlugin__onResolveAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue); -static bool isValidNamespaceString(String& namespaceString) +JSC_DECLARE_HOST_FUNCTION(jsBundlerPluginFunction_addFilter); +JSC_DECLARE_HOST_FUNCTION(jsBundlerPluginFunction_addError); +JSC_DECLARE_HOST_FUNCTION(jsBundlerPluginFunction_onLoadAsync); +JSC_DECLARE_HOST_FUNCTION(jsBundlerPluginFunction_onResolveAsync); + +void BundlerPlugin::NamespaceList::append(JSC::VM& vm, JSC::RegExp* filter, String& namespaceString) { - static JSC::Yarr::RegularExpression* namespaceRegex = nullptr; - if (!namespaceRegex) { - namespaceRegex = new JSC::Yarr::RegularExpression("^([/@a-zA-Z0-9_\\-]+)$"_s); + auto* nsGroup = group(namespaceString); + + if (nsGroup == nullptr) { + namespaces.append(namespaceString); + groups.append(Vector<Yarr::RegularExpression> {}); + nsGroup = &groups.last(); } - return namespaceRegex->match(namespaceString) > -1; + + 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); + + nsGroup->append(WTFMove(regex)); } -static EncodedJSValue jsFunctionAppendOnLoadPluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, JSBundlerPlugin& plugin) +bool BundlerPlugin::anyMatchesCrossThread(const ZigString* namespaceStr, const ZigString* path, bool isOnLoad) { - 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 namespaceString = namespaceStr ? Zig::toString(*namespaceStr) : String(); + auto pathString = Zig::toString(*path); - 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 (isOnLoad) { + auto* group = this->onLoad.group(namespaceString); + if (group == nullptr) { + return false; + } - if (!filter) { - throwException(globalObject, scope, createError(globalObject, "onLoad() expects first argument to be an object with a filter RegExp"_s)); - return JSValue::encode(jsUndefined()); - } + auto& filters = *group; - 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()); + for (auto& filter : filters) { + if (filter.match(pathString) > -1) { + return true; } } - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - } - auto func = callframe->uncheckedArgument(1); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); + } else { + auto* group = this->onResolve.group(namespaceString); + if (group == nullptr) { + return false; + } - if (!func.isCell() || !func.isCallable()) { - throwException(globalObject, scope, createError(globalObject, "onLoad() expects second argument to be a function"_s)); - return JSValue::encode(jsUndefined()); - } + auto& filters = *group; - plugin.onLoad.append(vm, filter->regExp(), jsCast<JSFunction*>(func), namespaceString); + for (auto& filter : filters) { + if (filter.match(pathString) > -1) { + return true; + } + } + } - return JSValue::encode(jsUndefined()); + return false; } -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()); +static const HashTableValue JSBundlerPluginHashTable[] = { + { "addFilter"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_addFilter, 3 } }, + { "addError"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_addError, 3 } }, + { "onLoadAsync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onLoadAsync, 3 } }, + { "onResolveAsync"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBundlerPluginFunction_onResolveAsync, 4 } }, +}; + +class JSBundlerPlugin final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static JSBundlerPlugin* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* config, BunPluginTarget target) + { + JSBundlerPlugin* ptr = new (NotNull, JSC::allocateCell<JSBundlerPlugin>(vm)) JSBundlerPlugin(vm, globalObject, structure, config, target); + ptr->finishCreation(vm); + return ptr; } - 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); + DECLARE_INFO; + template<typename, SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSBundlerPlugin, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForBundlerPlugin.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBundlerPlugin = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForBundlerPlugin.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForBundlerPlugin = std::forward<decltype(space)>(space); }); } - - if (!filter) { - throwException(globalObject, scope, createError(globalObject, "onResolve() expects first argument to be an object with a filter RegExp"_s)); - return JSValue::encode(jsUndefined()); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } - 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()); - } + DECLARE_VISIT_CHILDREN; - auto func = callframe->uncheckedArgument(1); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); + Bun::BundlerPlugin plugin; + JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction> onLoadFunction; + JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction> onResolveFunction; + JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction> setupFunction; - if (!func.isCell() || !func.isCallable()) { - throwException(globalObject, scope, createError(globalObject, "onResolve() expects second argument to be a function"_s)); - return JSValue::encode(jsUndefined()); +private: + JSBundlerPlugin(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure, void* config, BunPluginTarget target) + : JSC::JSNonFinalObject(vm, structure) + , plugin(BundlerPlugin(config, target)) + { } - plugin.onResolve.append(vm, filter->regExp(), jsCast<JSFunction*>(func), namespaceString); + void finishCreation(JSC::VM&); +}; - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnLoadJSBundlerPlugin, (JSGlobalObject * globalObject, CallFrame* callframe)) +template<typename Visitor> +void JSBundlerPlugin::visitChildrenImpl(JSCell* cell, Visitor& visitor) { - auto& plugin = *UNWRAP_BUNDLER_PLUGIN(callframe); - return jsFunctionAppendOnLoadPluginBody(globalObject, callframe, plugin); + JSBundlerPlugin* thisObject = jsCast<JSBundlerPlugin*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + thisObject->onLoadFunction.visit(visitor); + thisObject->onResolveFunction.visit(visitor); + thisObject->setupFunction.visit(visitor); } +DEFINE_VISIT_CHILDREN(JSBundlerPlugin); -JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnResolveJSBundlerPlugin, (JSGlobalObject * globalObject, CallFrame* callframe)) -{ - auto& plugin = *UNWRAP_BUNDLER_PLUGIN(callframe); - return jsFunctionAppendOnResolvePluginBody(globalObject, callframe, plugin); -} +const JSC::ClassInfo JSBundlerPlugin::s_info = { "BundlerPlugin"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBundlerPlugin) }; -extern "C" EncodedJSValue setupJSBundlerPlugin(JSBundlerPlugin* bundlerPlugin, JSC::JSGlobalObject* globalObject, JSValue objValue) +JSC_DEFINE_HOST_FUNCTION(jsBundlerPluginFunction_addFilter, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - 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()); + JSBundlerPlugin* thisObject = jsCast<JSBundlerPlugin*>(callFrame->thisValue()); + if (thisObject->plugin.tombstoned) { + return JSC::JSValue::encode(JSC::jsUndefined()); } - 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()); + JSC::RegExpObject* regExp = jsCast<JSC::RegExpObject*>(callFrame->argument(0)); + WTF::String namespaceStr = callFrame->argument(1).toWTFString(globalObject); + if (namespaceStr == "file"_s) { + namespaceStr = String(); } - 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())); + bool isOnLoad = callFrame->argument(2).toNumber(globalObject) == 1; + auto& vm = globalObject->vm(); - if (auto* promise = JSC::jsDynamicCast<JSC::JSPromise*>(result)) { - RELEASE_AND_RETURN(throwScope, JSValue::encode(promise)); + if (isOnLoad) { + thisObject->plugin.onLoad.append(vm, regExp->regExp(), namespaceStr); + } else { + thisObject->plugin.onResolve.append(vm, regExp->regExp(), namespaceStr); } - RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined())); + return JSC::JSValue::encode(JSC::jsUndefined()); } -void JSBundlerPlugin::Group::append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func) +JSC_DEFINE_HOST_FUNCTION(jsBundlerPluginFunction_addError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - 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 }); -} + JSBundlerPlugin* thisObject = jsCast<JSBundlerPlugin*>(callFrame->thisValue()); + if (!thisObject->plugin.tombstoned) { + JSBundlerPlugin__addError( + UNWRAP_BUNDLER_PLUGIN(callFrame), + thisObject->plugin.config, + JSValue::encode(callFrame->argument(1)), + JSValue::encode(callFrame->argument(2))); + } -void JSBundlerPlugin::Base::append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func, String& namespaceString) + return JSC::JSValue::encode(JSC::jsUndefined()); +} +JSC_DEFINE_HOST_FUNCTION(jsBundlerPluginFunction_onLoadAsync, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - 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); + JSBundlerPlugin* thisObject = jsCast<JSBundlerPlugin*>(callFrame->thisValue()); + if (!thisObject->plugin.tombstoned) { + JSBundlerPlugin__onLoadAsync( + UNWRAP_BUNDLER_PLUGIN(callFrame), + thisObject->plugin.config, + JSValue::encode(callFrame->argument(1)), + JSValue::encode(callFrame->argument(2))); } -} -JSFunction* JSBundlerPlugin::Group::find(String& path) + return JSC::JSValue::encode(JSC::jsUndefined()); +} +JSC_DEFINE_HOST_FUNCTION(jsBundlerPluginFunction_onResolveAsync, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - 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(); - } + JSBundlerPlugin* thisObject = jsCast<JSBundlerPlugin*>(callFrame->thisValue()); + if (!thisObject->plugin.tombstoned) { + JSBundlerPlugin__onResolveAsync( + UNWRAP_BUNDLER_PLUGIN(callFrame), + thisObject->plugin.config, + JSValue::encode(callFrame->argument(1)), + JSValue::encode(callFrame->argument(2)), + JSValue::encode(callFrame->argument(3))); } - return nullptr; + return JSC::JSValue::encode(JSC::jsUndefined()); } -EncodedJSValue JSBundlerPlugin::OnResolve::run(const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context) +void JSBundlerPlugin::finishCreation(JSC::VM& vm) { - Group* groupPtr = this->group(namespaceString ? Zig::toString(*namespaceString) : String()); - if (groupPtr == nullptr) { - return JSValue::encode(jsUndefined()); - } - Group& group = *groupPtr; - - auto pathString = Zig::toString(*path); + Base::finishCreation(vm); + ASSERT(inherits(vm, info())); + this->onLoadFunction.initLater( + [](const JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction>::Initializer& init) { + auto& vm = init.vm; + auto* globalObject = init.owner->globalObject(); + + init.set( + JSC::JSFunction::create(vm, WebCore::bundlerPluginRunOnLoadPluginsCodeGenerator(vm), globalObject)); + }); + + this->onResolveFunction.initLater( + [](const JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction>::Initializer& init) { + auto& vm = init.vm; + auto* globalObject = init.owner->globalObject(); + + init.set( + JSC::JSFunction::create(vm, WebCore::bundlerPluginRunOnResolvePluginsCodeGenerator(vm), globalObject)); + }); + + this->setupFunction.initLater( + [](const JSC::LazyProperty<JSBundlerPlugin, JSC::JSFunction>::Initializer& init) { + auto& vm = init.vm; + auto* globalObject = init.owner->globalObject(); + + init.set( + JSC::JSFunction::create(vm, WebCore::bundlerPluginRunSetupFunctionCodeGenerator(vm), globalObject)); + }); + + auto* clientData = WebCore::clientData(vm); + + this->putDirect(vm, Identifier::fromString(vm, String("onLoad"_s)), jsUndefined(), 0); + this->putDirect(vm, Identifier::fromString(vm, String("onResolve"_s)), jsUndefined(), 0); + reifyStaticProperties(vm, JSBundlerPlugin::info(), JSBundlerPluginHashTable, *this); +} - JSC::JSFunction* function = group.find(pathString); - if (!function) { - return JSValue::encode(JSC::jsUndefined()); - } +extern "C" bool JSBundlerPlugin__anyMatches(Bun::JSBundlerPlugin* pluginObject, const ZigString* namespaceString, const ZigString* path, bool isOnLoad) +{ + return pluginObject->plugin.anyMatchesCrossThread(namespaceString, path, isOnLoad); +} - JSC::MarkedArgumentBuffer arguments; - JSC::JSGlobalObject* globalObject = function->globalObject(); - auto& vm = globalObject->vm(); +extern "C" void JSBundlerPlugin__matchOnLoad(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, void* context, uint8_t defaultLoaderId) +{ + WTF::String namespaceStringStr = namespaceString ? Zig::toStringCopy(*namespaceString) : WTF::String(); + WTF::String pathStr = path ? Zig::toStringCopy(*path) : WTF::String(); - 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(); + JSFunction* function = plugin->onLoadFunction.get(plugin); + if (UNLIKELY(!function)) + return; 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 (UNLIKELY(callData.type == JSC::CallData::Type::None)) + return; - 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; - } + auto scope = DECLARE_CATCH_SCOPE(plugin->vm()); + JSC::MarkedArgumentBuffer arguments; + arguments.append(WRAP_BUNDLER_PLUGIN(context)); + arguments.append(JSC::jsString(plugin->vm(), pathStr)); + arguments.append(JSC::jsString(plugin->vm(), namespaceStringStr)); + arguments.append(JSC::jsNumber(defaultLoaderId)); + + auto result = call(globalObject, function, callData, plugin, arguments); + + if (scope.exception()) { + auto exception = scope.exception(); + scope.clearException(); + if (!plugin->plugin.tombstoned) { + JSBundlerPlugin__addError( + context, + plugin->plugin.config, + JSC::JSValue::encode(exception), + JSValue::encode(jsNumber(0))); } } - - 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) +extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context, uint8_t kindId) { - Group* groupPtr = this->group(namespaceString ? Zig::toString(*namespaceString) : String()); - if (groupPtr == nullptr) { - return JSValue::encode(jsUndefined()); + WTF::String namespaceStringStr = namespaceString ? Zig::toStringCopy(*namespaceString) : WTF::String("file"_s); + if (namespaceStringStr.length() == 0) { + namespaceStringStr = WTF::String("file"_s); } - 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(); + WTF::String pathStr = path ? Zig::toStringCopy(*path) : WTF::String(); + WTF::String importerStr = importer ? Zig::toStringCopy(*importer) : WTF::String(); 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); + JSFunction* function = plugin->onResolveFunction.get(plugin); + if (UNLIKELY(!function)) + return; - 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); - } + JSC::CallData callData = JSC::getCallData(function); - 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 (UNLIKELY(callData.type == JSC::CallData::Type::None)) + return; - if (!result || result.isUndefined()) { - RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined())); - } + auto scope = DECLARE_CATCH_SCOPE(vm); + JSC::MarkedArgumentBuffer arguments; + arguments.append(JSC::jsString(vm, pathStr)); + arguments.append(JSC::jsString(vm, namespaceStringStr)); + arguments.append(JSC::jsString(vm, importerStr)); + arguments.append(WRAP_BUNDLER_PLUGIN(context)); + arguments.append(JSC::jsNumber(kindId)); - 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; - } - } - } + auto result = call(globalObject, function, callData, plugin, arguments); - if (!result.isObject()) { - JSC::throwTypeError(globalObject, throwScope, "onResolve() expects an object returned"_s); - JSC::Exception* exception = scope.exception(); - scope.clearException(); - return JSValue::encode(exception); + if (UNLIKELY(scope.exception())) { + auto exception = JSValue(scope.exception()); + scope.clearException(); + if (!plugin->plugin.tombstoned) { + JSBundlerPlugin__addError( + context, + plugin->plugin.config, + JSC::JSValue::encode(exception), + JSValue::encode(jsNumber(1))); } - - RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); + return; } - - return JSValue::encode(JSC::jsUndefined()); } -bool JSBundlerPlugin::anyMatchesCrossThread(const ZigString* namespaceStr, const ZigString* path, bool isOnLoad) +extern "C" Bun::JSBundlerPlugin* JSBundlerPlugin__create(Zig::GlobalObject* globalObject, BunPluginTarget target) { - 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; - } + return JSBundlerPlugin::create( + globalObject->vm(), + globalObject, + // TODO: cache this structure on the global object + JSBundlerPlugin::createStructure( + globalObject->vm(), + globalObject, + globalObject->objectPrototype()), + nullptr, + target); +} - auto& filters = group->filters; +extern "C" EncodedJSValue JSBundlerPlugin__runSetupFunction( + Bun::JSBundlerPlugin* plugin, + EncodedJSValue encodedSetupFunction) +{ + auto& vm = plugin->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); - for (auto& filter : filters) { - if (filter.match(pathString) > -1) { - return true; - } - } + auto* setupFunction = jsCast<JSFunction*>(plugin->setupFunction.get(plugin)); + if (UNLIKELY(!setupFunction)) + return JSValue::encode(jsUndefined()); - } else { - auto* group = this->onResolve.group(namespaceString); - if (group == nullptr) { - return false; - } + JSC::CallData callData = JSC::getCallData(setupFunction); + if (UNLIKELY(callData.type == JSC::CallData::Type::None)) + return JSValue::encode(jsUndefined()); - auto& filters = group->filters; + MarkedArgumentBuffer arguments; + arguments.append(JSValue::decode(encodedSetupFunction)); + auto* lexicalGlobalObject = jsCast<JSFunction*>(JSValue::decode(encodedSetupFunction))->globalObject(); - for (auto& filter : filters) { - if (filter.match(pathString) > -1) { - return true; - } - } + auto result = JSC::call(lexicalGlobalObject, setupFunction, callData, plugin, arguments); + if (UNLIKELY(scope.exception())) { + auto exception = scope.exception(); + scope.clearException(); + return JSValue::encode(exception); } - 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(); + return JSValue::encode(result); } extern "C" void JSBundlerPlugin__setConfig(Bun::JSBundlerPlugin* plugin, void* config) { - plugin->config = config; + plugin->plugin.config = config; } extern "C" void JSBundlerPlugin__tombestone(Bun::JSBundlerPlugin* plugin) { - plugin->tombstone(); - plugin->deref(); + plugin->plugin.tombstone(); } + +} // namespace Bun |