aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-09-03 03:57:43 -0700
committerGravatar GitHub <noreply@github.com> 2022-09-03 03:57:43 -0700
commit0ca42e81f31d31270c44f81d88fff33361817720 (patch)
tree27e27b32d1850a1fada1a002207e3254cbc597e8 /src/bun.js
parent8b2d07e82ee6249e0886109585b63429ccf3ab3a (diff)
downloadbun-0ca42e81f31d31270c44f81d88fff33361817720.tar.gz
bun-0ca42e81f31d31270c44f81d88fff33361817720.tar.zst
bun-0ca42e81f31d31270c44f81d88fff33361817720.zip
Plugin API (#1199)
* Plugin API * Fix the bugs * Implement `"object"` loader Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/bindings/BunJSCModule.cpp1
-rw-r--r--src/bun.js/bindings/BunPlugin.cpp505
-rw-r--r--src/bun.js/bindings/BunPlugin.h85
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp44
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h5
-rw-r--r--src/bun.js/bindings/bindings.zig46
-rw-r--r--src/bun.js/bindings/exports.zig2
-rw-r--r--src/bun.js/bindings/headers-handwritten.h8
-rw-r--r--src/bun.js/builtins/BunBuiltinNames.h3
-rw-r--r--src/bun.js/javascript.zig183
-rw-r--r--src/bun.js/modules/ObjectModule.h32
11 files changed, 905 insertions, 9 deletions
diff --git a/src/bun.js/bindings/BunJSCModule.cpp b/src/bun.js/bindings/BunJSCModule.cpp
index f6054d7f3..39e20d346 100644
--- a/src/bun.js/bindings/BunJSCModule.cpp
+++ b/src/bun.js/bindings/BunJSCModule.cpp
@@ -1,5 +1,4 @@
#include "root.h"
-#include "JavaScriptCore/JSCInlines.h"
#include "JavaScriptCore/JavaScript.h"
#include "wtf/FileSystem.h"
diff --git a/src/bun.js/bindings/BunPlugin.cpp b/src/bun.js/bindings/BunPlugin.cpp
new file mode 100644
index 000000000..0941d2722
--- /dev/null
+++ b/src/bun.js/bindings/BunPlugin.cpp
@@ -0,0 +1,505 @@
+#include "BunPlugin.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/RegularExpression.h"
+
+namespace Zig {
+
+extern "C" void Bun__onDidAppendPlugin(void* bunVM, JSGlobalObject* globalObject);
+
+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, BunPluginTarget target)
+{
+ JSC::VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ 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());
+ }
+
+ Zig::GlobalObject* global = reinterpret_cast<Zig::GlobalObject*>(globalObject);
+ auto& plugins = global->onLoadPlugins[target];
+ plugins.append(vm, filter->regExp(), jsCast<JSFunction*>(func), namespaceString);
+ Bun__onDidAppendPlugin(reinterpret_cast<Zig::GlobalObject*>(globalObject)->bunVM(), globalObject);
+ return JSValue::encode(jsUndefined());
+}
+
+static EncodedJSValue jsFunctionAppendOnResolvePluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, BunPluginTarget target)
+{
+ 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());
+ }
+
+ Zig::GlobalObject* global = reinterpret_cast<Zig::GlobalObject*>(globalObject);
+ auto& plugins = global->onResolvePlugins[target];
+ plugins.append(vm, filter->regExp(), jsCast<JSFunction*>(func), namespaceString);
+
+ Bun__onDidAppendPlugin(reinterpret_cast<Zig::GlobalObject*>(globalObject)->bunVM(), globalObject);
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnLoadPluginNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ return jsFunctionAppendOnLoadPluginBody(globalObject, callframe, BunPluginTargetNode);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnLoadPluginBun, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ return jsFunctionAppendOnLoadPluginBody(globalObject, callframe, BunPluginTargetBun);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnLoadPluginBrowser, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ return jsFunctionAppendOnLoadPluginBody(globalObject, callframe, BunPluginTargetBrowser);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnResolvePluginNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ return jsFunctionAppendOnResolvePluginBody(globalObject, callframe, BunPluginTargetNode);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnResolvePluginBun, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ return jsFunctionAppendOnResolvePluginBody(globalObject, callframe, BunPluginTargetBun);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionAppendOnResolvePluginBrowser, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ return jsFunctionAppendOnResolvePluginBody(globalObject, callframe, BunPluginTargetBrowser);
+}
+
+extern "C" EncodedJSValue jsFunctionBunPluginClear(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe)
+{
+ Zig::GlobalObject* global = reinterpret_cast<Zig::GlobalObject*>(globalObject);
+ for (uint8_t i = 0; i < BunPluginTargetMax + 1; i++) {
+ global->onLoadPlugins[i].fileNamespace.clear();
+ global->onResolvePlugins[i].fileNamespace.clear();
+ global->onLoadPlugins[i].groups.clear();
+ global->onResolvePlugins[i].namespaces.clear();
+ }
+
+ return JSValue::encode(jsUndefined());
+}
+
+extern "C" EncodedJSValue jsFunctionBunPlugin(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe)
+{
+ JSC::VM& vm = globalObject->vm();
+ auto clientData = WebCore::clientData(vm);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ if (callframe->argumentCount() < 1) {
+ JSC::throwTypeError(globalObject, throwScope, "Bun.plugin() needs at least one argument (an object)"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ JSC::JSObject* obj = callframe->uncheckedArgument(0).getObject();
+ if (!obj) {
+ JSC::throwTypeError(globalObject, throwScope, "Bun.plugin() needs an object as first argument"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ JSC::JSValue setupFunctionValue = obj->get(globalObject, Identifier::fromString(vm, "setup"_s));
+ if (!setupFunctionValue || setupFunctionValue.isUndefinedOrNull() || !setupFunctionValue.isCell() || !setupFunctionValue.isCallable()) {
+ JSC::throwTypeError(globalObject, throwScope, "Bun.plugin() needs a setup() function"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ Zig::GlobalObject* global = reinterpret_cast<Zig::GlobalObject*>(globalObject);
+ BunPluginTarget target = global->defaultBunPluginTarget;
+ if (JSValue targetValue = obj->getIfPropertyExists(globalObject, Identifier::fromString(vm, "target"_s))) {
+ if (auto* targetJSString = targetValue.toStringOrNull(globalObject)) {
+ auto targetString = targetJSString->value(globalObject);
+ if (targetString == "node"_s) {
+ target = BunPluginTargetNode;
+ } else if (targetString == "bun"_s) {
+ target = BunPluginTargetBun;
+ } else if (targetString == "browser"_s) {
+ target = BunPluginTargetBrowser;
+ } else {
+ JSC::throwTypeError(globalObject, throwScope, "Bun.plugin() target must be one of 'node', 'bun' or 'browser'"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ }
+ }
+
+ JSFunction* setupFunction = jsCast<JSFunction*>(setupFunctionValue);
+ JSObject* builderObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 3);
+
+ switch (target) {
+ case BunPluginTargetNode: {
+ builderObject->putDirect(vm, Identifier::fromString(vm, "target"_s), jsString(vm, String("node"_s)), 0);
+ builderObject->putDirectNativeFunction(
+ vm,
+ globalObject,
+ JSC::Identifier::fromString(vm, "onLoad"_s),
+ 1,
+ jsFunctionAppendOnLoadPluginNode,
+ ImplementationVisibility::Public,
+ NoIntrinsic,
+ JSC::PropertyAttribute::DontDelete | 0);
+ builderObject->putDirectNativeFunction(
+ vm,
+ globalObject,
+ JSC::Identifier::fromString(vm, "onResolve"_s),
+ 1,
+ jsFunctionAppendOnResolvePluginNode,
+ ImplementationVisibility::Public,
+ NoIntrinsic,
+ JSC::PropertyAttribute::DontDelete | 0);
+ break;
+ }
+ case BunPluginTargetBun: {
+ builderObject->putDirect(vm, Identifier::fromString(vm, "target"_s), jsString(vm, String("bun"_s)), 0);
+ builderObject->putDirectNativeFunction(
+ vm,
+ globalObject,
+ JSC::Identifier::fromString(vm, "onLoad"_s),
+ 1,
+ jsFunctionAppendOnLoadPluginBun,
+ ImplementationVisibility::Public,
+ NoIntrinsic,
+ JSC::PropertyAttribute::DontDelete | 0);
+ builderObject->putDirectNativeFunction(
+ vm,
+ globalObject,
+ JSC::Identifier::fromString(vm, "onResolve"_s),
+ 1,
+ jsFunctionAppendOnResolvePluginBun,
+ ImplementationVisibility::Public,
+ NoIntrinsic,
+ JSC::PropertyAttribute::DontDelete | 0);
+ break;
+ }
+ case BunPluginTargetBrowser: {
+ builderObject->putDirect(vm, Identifier::fromString(vm, "target"_s), jsString(vm, String("browser"_s)), 0);
+ builderObject->putDirectNativeFunction(
+ vm,
+ globalObject,
+ JSC::Identifier::fromString(vm, "onLoad"_s),
+ 1,
+ jsFunctionAppendOnLoadPluginBrowser,
+ ImplementationVisibility::Public,
+ NoIntrinsic,
+ JSC::PropertyAttribute::DontDelete | 0);
+ builderObject->putDirectNativeFunction(
+ vm,
+ globalObject,
+ JSC::Identifier::fromString(vm, "onResolve"_s),
+ 1,
+ jsFunctionAppendOnResolvePluginBrowser,
+ ImplementationVisibility::Public,
+ NoIntrinsic,
+ JSC::PropertyAttribute::DontDelete | 0);
+ break;
+ }
+ }
+
+ 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, encodedJSValue());
+
+ if (auto* promise = JSC::jsDynamicCast<JSC::JSPromise*>(result)) {
+ JSC::throwTypeError(globalObject, throwScope, "setup() does not support promises yet"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
+}
+
+void BunPlugin::Group::append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func)
+{
+ filters.append(JSC::Strong<JSC::RegExp> { vm, filter });
+ callbacks.append(JSC::Strong<JSC::JSFunction> { vm, func });
+}
+
+void BunPlugin::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* BunPlugin::Group::find(JSC::JSGlobalObject* globalObject, String& path)
+{
+ size_t count = filters.size();
+ for (size_t i = 0; i < count; i++) {
+ if (filters[i].get()->match(globalObject, path, 0)) {
+ return callbacks[i].get();
+ }
+ }
+
+ return nullptr;
+}
+
+EncodedJSValue BunPlugin::OnLoad::run(JSC::JSGlobalObject* globalObject, ZigString* namespaceString, ZigString* path)
+{
+ 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(globalObject, pathString);
+ if (!function) {
+ return JSValue::encode(JSC::jsUndefined());
+ }
+
+ 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(),
+ jsString(vm, pathString));
+ 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())) {
+ JSC::Exception* exception = scope.exception();
+ scope.clearException();
+ return JSValue::encode(exception);
+ }
+
+ if (auto* promise = JSC::jsDynamicCast<JSPromise*>(result)) {
+ switch (promise->status(vm)) {
+ case JSPromise::Status::Pending: {
+ JSC::throwTypeError(globalObject, throwScope, "onLoad() doesn't support pending promises yet"_s);
+ return JSValue::encode({});
+ }
+ 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 BunPlugin::OnResolve::run(JSC::JSGlobalObject* globalObject, ZigString* namespaceString, ZigString* path, ZigString* importer)
+{
+ Group* groupPtr = this->group(namespaceString ? Zig::toString(*namespaceString) : String());
+ if (groupPtr == nullptr) {
+ return JSValue::encode(jsUndefined());
+ }
+ Group& group = *groupPtr;
+ auto& filters = group.filters;
+
+ if (filters.size() == 0) {
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto& callbacks = group.callbacks;
+
+ WTF::String pathString = Zig::toString(*path);
+ for (size_t i = 0; i < filters.size(); i++) {
+ if (!filters[i].get()->match(globalObject, pathString, 0)) {
+ 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(), 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())) {
+ JSC::Exception* exception = scope.exception();
+ scope.clearException();
+ return JSValue::encode(exception);
+ }
+
+ if (result.isUndefinedOrNull()) {
+ continue;
+ }
+
+ if (auto* promise = JSC::jsDynamicCast<JSPromise*>(result)) {
+ switch (promise->status(vm)) {
+ case JSPromise::Status::Pending: {
+ JSC::throwTypeError(globalObject, throwScope, "onResolve() doesn't support pending promises yet"_s);
+ return JSValue::encode({});
+ }
+ 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, "onResolve() expects an object returned"_s);
+ return JSValue::encode({});
+ }
+
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+ }
+
+ return JSValue::encode(JSC::jsUndefined());
+}
+
+} // namespace Zig
+
+extern "C" JSC::EncodedJSValue Bun__runOnResolvePlugins(Zig::GlobalObject* globalObject, ZigString* namespaceString, ZigString* path, ZigString* from, BunPluginTarget target)
+{
+ return globalObject->onResolvePlugins[target].run(globalObject, namespaceString, path, from);
+}
+
+extern "C" JSC::EncodedJSValue Bun__runOnLoadPlugins(Zig::GlobalObject* globalObject, ZigString* namespaceString, ZigString* path, BunPluginTarget target)
+{
+ return globalObject->onLoadPlugins[target].run(globalObject, namespaceString, path);
+}
diff --git a/src/bun.js/bindings/BunPlugin.h b/src/bun.js/bindings/BunPlugin.h
new file mode 100644
index 000000000..600705500
--- /dev/null
+++ b/src/bun.js/bindings/BunPlugin.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "root.h"
+#include "headers-handwritten.h"
+#include "JavaScriptCore/JSGlobalObject.h"
+#include "JavaScriptCore/Strong.h"
+#include "helpers.h"
+
+extern "C" JSC_DECLARE_HOST_FUNCTION(jsFunctionBunPlugin);
+extern "C" JSC_DECLARE_HOST_FUNCTION(jsFunctionBunPluginClear);
+
+namespace Zig {
+
+using namespace JSC;
+
+class BunPlugin {
+public:
+ // This is a list of pairs of regexps and functions to match against
+ class Group {
+
+ public:
+ // JavaScriptCore/RegularExpression does exist however it does not JIT
+ // We want JIT!
+ // TODO: evaluate if using JSInternalFieldImpl(2) is faster
+ Vector<JSC::Strong<JSC::RegExp>> filters = {};
+ Vector<JSC::Strong<JSC::JSFunction>> callbacks = {};
+ BunPluginTarget target { BunPluginTargetBun };
+
+ void append(JSC::VM& vm, JSC::RegExp* filter, JSC::JSFunction* func);
+ JSFunction* find(JSC::JSGlobalObject* globalObj, String& path);
+ void clear()
+ {
+ filters.clear();
+ callbacks.clear();
+ }
+ };
+
+ class Base {
+ public:
+ Group fileNamespace = {};
+ Vector<String> namespaces = {};
+ Vector<Group> groups = {};
+
+ 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(JSC::JSGlobalObject* globalObject, ZigString* namespaceString, ZigString* path);
+ };
+
+ class OnResolve final : public Base {
+
+ public:
+ OnResolve()
+ : Base()
+ {
+ }
+
+ EncodedJSValue run(JSC::JSGlobalObject* globalObject, ZigString* namespaceString, ZigString* path, ZigString* importer);
+ };
+};
+
+} // namespace Zig \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 855e212f9..d378a0f4c 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -106,6 +106,8 @@
#include "ZigGeneratedClasses.h"
+#include "BunPlugin.h"
+
#if ENABLE(REMOTE_INSPECTOR)
#include "JavaScriptCore/RemoteInspectorServer.h"
#endif
@@ -162,6 +164,7 @@ using JSBuffer = WebCore::JSBuffer;
#include "../modules/EventsModule.h"
#include "../modules/ProcessModule.h"
#include "../modules/StringDecoderModule.h"
+#include "../modules/ObjectModule.h"
// #include <iostream>
static bool has_loaded_jsc = false;
@@ -2400,6 +2403,14 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
}
+ {
+ JSC::Identifier identifier = JSC::Identifier::fromString(vm, "plugin"_s);
+ JSFunction* pluginFunction = JSFunction::create(vm, this, 1, String("plugin"_s), jsFunctionBunPlugin, ImplementationVisibility::Public, NoIntrinsic);
+ pluginFunction->putDirectNativeFunction(vm, this, JSC::Identifier::fromString(vm, "clearAll"_s), 1, jsFunctionBunPluginClear, ImplementationVisibility::Public, NoIntrinsic,
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
+ object->putDirect(vm, PropertyName(identifier), pluginFunction, JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
+ }
+
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { builtinNames.BunPublicName(),
JSC::JSValue(object), JSC::PropertyAttribute::DontDelete | 0 });
@@ -2655,6 +2666,22 @@ static JSC_DEFINE_HOST_FUNCTION(functionFulfillModuleSync,
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined()));
}
+ case SyntheticModuleType::ObjectModule: {
+ JSC::EncodedJSValue encodedValue = reinterpret_cast<JSC::EncodedJSValue>(
+ bitwise_cast<int64_t>(reinterpret_cast<size_t>(res.result.value.source_code.ptr)));
+ JSC::JSObject* object = JSC::JSValue::decode(encodedValue).getObject();
+ auto function = generateObjectModuleSourceCode(
+ globalObject,
+ object);
+ auto source = JSC::SourceCode(
+ JSC::SyntheticSourceProvider::create(WTFMove(function),
+ JSC::SourceOrigin(), WTFMove(moduleKey)));
+
+ RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
+ globalObject->moduleLoader()->provideFetch(globalObject, key, WTFMove(source));
+ RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
+ RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined()));
+ }
case SyntheticModuleType::Process: {
auto source = JSC::SourceCode(
JSC::SyntheticSourceProvider::create(
@@ -2746,6 +2773,23 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb
globalObject->vm().drainMicrotasks();
return promise;
}
+ case SyntheticModuleType::ObjectModule: {
+ JSC::EncodedJSValue encodedValue = reinterpret_cast<JSC::EncodedJSValue>(
+ bitwise_cast<int64_t>(reinterpret_cast<size_t>(res.result.value.source_code.ptr)));
+ JSC::JSObject* object = JSC::JSValue::decode(encodedValue).getObject();
+ auto source = JSC::SourceCode(
+ JSC::SyntheticSourceProvider::create(generateObjectModuleSourceCode(
+ globalObject,
+ object),
+ JSC::SourceOrigin(), WTFMove(moduleKey)));
+
+ auto sourceCode = JSSourceCode::create(vm, WTFMove(source));
+ RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope));
+
+ promise->resolve(globalObject, sourceCode);
+ scope.release();
+ return promise;
+ }
case SyntheticModuleType::Buffer: {
auto source = JSC::SourceCode(
JSC::SyntheticSourceProvider::create(generateBufferSourceCode,
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 88149434c..144e43bd8 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -32,6 +32,7 @@ class EventLoopTask;
#include "DOMConstructors.h"
#include "DOMWrapperWorld-class.h"
#include "DOMIsoSubspaces.h"
+#include "BunPlugin.h"
// #include "EventTarget.h"
// namespace WebCore {
@@ -303,6 +304,10 @@ public:
this->m_ffiFunctions.append(JSC::Strong<JSC::JSFunction> { vm(), function });
}
+ BunPlugin::OnLoad onLoadPlugins[BunPluginTargetMax + 1] {};
+ BunPlugin::OnResolve onResolvePlugins[BunPluginTargetMax + 1] {};
+ BunPluginTarget defaultBunPluginTarget = BunPluginTargetBun;
+
#include "ZigGeneratedClasses+lazyStructureHeader.h"
private:
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index ea29c99ab..d997bc2cd 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -101,6 +101,23 @@ pub const ZigString = extern struct {
return this;
}
+ pub fn substring(this: ZigString, offset: usize) ZigString {
+ if (this.is16Bit()) {
+ return ZigString.from16Slice(this.utf16SliceAligned()[@minimum(this.len, offset)..]);
+ }
+
+ var out = ZigString.init(this.slice()[@minimum(this.len, offset)..]);
+ if (this.isUTF8()) {
+ out.markUTF8();
+ }
+
+ if (this.isGloballyAllocated()) {
+ out.mark();
+ }
+
+ return out;
+ }
+
pub fn utf8ByteLength(this: ZigString) usize {
if (this.isUTF8()) {
return this.len;
@@ -1773,6 +1790,35 @@ pub const JSGlobalObject = extern struct {
this.vm().throwError(this, err);
}
+ pub const BunPluginTarget = enum(u8) {
+ bun = 0,
+ node = 1,
+ browser = 2,
+ };
+ extern fn Bun__runOnLoadPlugins(*JSC.JSGlobalObject, ?*const ZigString, *const ZigString, BunPluginTarget) JSValue;
+ extern fn Bun__runOnResolvePlugins(*JSC.JSGlobalObject, ?*const ZigString, *const ZigString, *const ZigString, BunPluginTarget) JSValue;
+
+ pub fn runOnLoadPlugins(this: *JSGlobalObject, namespace_: ZigString, path: ZigString, target: BunPluginTarget) ?JSValue {
+ JSC.markBinding();
+ const result = Bun__runOnLoadPlugins(this, if (namespace_.len > 0) &namespace_ else null, &path, target);
+ if (result.isEmptyOrUndefinedOrNull()) {
+ return null;
+ }
+
+ return result;
+ }
+
+ pub fn runOnResolvePlugins(this: *JSGlobalObject, namespace_: ZigString, path: ZigString, source: ZigString, target: BunPluginTarget) ?JSValue {
+ JSC.markBinding();
+
+ const result = Bun__runOnResolvePlugins(this, if (namespace_.len > 0) &namespace_ else null, &path, &source, target);
+ if (result.isEmptyOrUndefinedOrNull()) {
+ return null;
+ }
+
+ return result;
+ }
+
pub fn createSyntheticModule_(this: *JSGlobalObject, export_names: [*]const ZigString, export_len: usize, value_ptrs: [*]const JSValue, values_len: usize) void {
shim.cppFn("createSyntheticModule_", .{ this, export_names, export_len, value_ptrs, values_len });
}
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig
index 2f10e75a4..20bc1195b 100644
--- a/src/bun.js/bindings/exports.zig
+++ b/src/bun.js/bindings/exports.zig
@@ -243,6 +243,8 @@ pub const ResolvedSource = extern struct {
pub const Tag = enum(u64) {
javascript = 0,
wasm = 1,
+ object = 2,
+ file = 3,
@"node:buffer" = 1024,
@"node:process" = 1025,
diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h
index c9ac33e21..69b4934e9 100644
--- a/src/bun.js/bindings/headers-handwritten.h
+++ b/src/bun.js/bindings/headers-handwritten.h
@@ -46,6 +46,12 @@ typedef struct SystemError {
typedef void* ArrayBufferSink;
+typedef uint8_t BunPluginTarget;
+const BunPluginTarget BunPluginTargetBun = 0;
+const BunPluginTarget BunPluginTargetBrowser = 1;
+const BunPluginTarget BunPluginTargetNode = 2;
+const BunPluginTarget BunPluginTargetMax = BunPluginTargetNode;
+
typedef uint8_t ZigStackFrameCode;
const ZigStackFrameCode ZigStackFrameCodeNone = 0;
const ZigStackFrameCode ZigStackFrameCodeEval = 1;
@@ -178,6 +184,8 @@ typedef struct {
} Bun__ArrayBuffer;
enum SyntheticModuleType : uint64_t {
+ ObjectModule = 2,
+
Buffer = 1024,
Process = 1025,
Events = 1026,
diff --git a/src/bun.js/builtins/BunBuiltinNames.h b/src/bun.js/builtins/BunBuiltinNames.h
index 16f7096a6..c8a14a260 100644
--- a/src/bun.js/builtins/BunBuiltinNames.h
+++ b/src/bun.js/builtins/BunBuiltinNames.h
@@ -99,6 +99,7 @@ using namespace JSC;
macro(file) \
macro(filePath) \
macro(fillFromJS) \
+ macro(filter) \
macro(finishConsumingStream) \
macro(flush) \
macro(flushAlgorithm) \
@@ -115,6 +116,7 @@ using namespace JSC;
macro(hostname) \
macro(href) \
macro(ignoreBOM) \
+ macro(importer) \
macro(inFlightCloseRequest) \
macro(inFlightWriteRequest) \
macro(initializeWith) \
@@ -192,6 +194,7 @@ using namespace JSC;
macro(sep) \
macro(setBody) \
macro(setStatus) \
+ macro(setup) \
macro(sink) \
macro(size) \
macro(start) \
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 546ab36aa..6da062b23 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -30,6 +30,7 @@ const logger = @import("../logger.zig");
const Api = @import("../api/schema.zig").Api;
const options = @import("../options.zig");
const Bundler = @import("../bundler.zig").Bundler;
+const PluginRunner = @import("../bundler.zig").PluginRunner;
const ServerEntryPoint = @import("../bundler.zig").ServerEntryPoint;
const js_printer = @import("../js_printer.zig");
const js_parser = @import("../js_parser.zig");
@@ -270,6 +271,7 @@ comptime {
_ = Bun__queueMicrotask;
_ = Bun__handleRejectedPromise;
_ = Bun__readOriginTimer;
+ _ = Bun__onDidAppendPlugin;
}
}
@@ -282,6 +284,18 @@ pub export fn Bun__handleRejectedPromise(global: *JSGlobalObject, promise: *JSC.
global.bunVM().runErrorHandler(result, null);
}
+pub export fn Bun__onDidAppendPlugin(jsc_vm: *VirtualMachine, globalObject: *JSGlobalObject) void {
+ if (jsc_vm.plugin_runner != null) {
+ return;
+ }
+
+ jsc_vm.plugin_runner = PluginRunner{
+ .global_object = globalObject,
+ .allocator = jsc_vm.allocator,
+ };
+ jsc_vm.bundler.linker.plugin_runner = &jsc_vm.plugin_runner.?;
+}
+
// If you read JavascriptCore/API/JSVirtualMachine.mm - https://github.com/WebKit/WebKit/blob/acff93fb303baa670c055cb24c2bad08691a01a0/Source/JavaScriptCore/API/JSVirtualMachine.mm#L101
// We can see that it's sort of like std.mem.Allocator but for JSGlobalContextRef, to support Automatic Reference Counting
// Its unavailable on Linux
@@ -311,6 +325,8 @@ pub const VirtualMachine = struct {
timer: Bun.Timer = Bun.Timer{},
uws_event_loop: ?*uws.Loop = null,
+ plugin_runner: ?PluginRunner = null,
+
/// Do not access this field directly
/// It exists in the VirtualMachine struct so that
/// we don't accidentally make a stack copy of it
@@ -671,7 +687,7 @@ pub const VirtualMachine = struct {
};
fn _fetch(
jsc_vm: *VirtualMachine,
- _: *JSGlobalObject,
+ globalObject: *JSGlobalObject,
_specifier: string,
_: string,
log: *logger.Log,
@@ -1054,18 +1070,153 @@ pub const VirtualMachine = struct {
}
}
- const specifier = normalizeSpecifier(_specifier);
-
- std.debug.assert(std.fs.path.isAbsolute(specifier)); // if this crashes, it means the resolver was skipped.
-
- const path = Fs.Path.init(specifier);
- const loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: {
+ var specifier = normalizeSpecifier(_specifier);
+ var path = Fs.Path.init(specifier);
+ const default_loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: {
if (strings.eqlLong(specifier, jsc_vm.main, true)) {
break :brk options.Loader.js;
}
break :brk options.Loader.file;
};
+ var loader = default_loader;
+ var virtual_source: logger.Source = undefined;
+ var has_virtual_source = false;
+ var source_code_slice: ZigString.Slice = ZigString.Slice.empty;
+ defer source_code_slice.deinit();
+
+ if (jsc_vm.plugin_runner != null) {
+ const namespace = PluginRunner.extractNamespace(_specifier);
+ const after_namespace = if (namespace.len == 0)
+ specifier
+ else
+ _specifier[@minimum(namespace.len + 1, _specifier.len)..];
+
+ if (PluginRunner.couldBePlugin(_specifier)) {
+ if (globalObject.runOnLoadPlugins(ZigString.init(namespace), ZigString.init(after_namespace), .bun)) |plugin_result| {
+ if (plugin_result.isException(globalObject.vm()) or plugin_result.isAnyError(globalObject)) {
+ jsc_vm.runErrorHandler(plugin_result, null);
+ log.addError(null, logger.Loc.Empty, "Failed to run plugin") catch unreachable;
+ return error.PluginError;
+ }
+
+ if (comptime Environment.allow_assert)
+ std.debug.assert(plugin_result.isObject());
+
+ if (plugin_result.get(globalObject, "loader")) |loader_value| {
+ if (!loader_value.isUndefinedOrNull()) {
+ const loader_string = loader_value.getZigString(globalObject);
+ if (comptime Environment.allow_assert)
+ std.debug.assert(loader_string.len > 0);
+
+ if (loader_string.eqlComptime("js")) {
+ loader = options.Loader.js;
+ } else if (loader_string.eqlComptime("jsx")) {
+ loader = options.Loader.jsx;
+ } else if (loader_string.eqlComptime("tsx")) {
+ loader = options.Loader.tsx;
+ } else if (loader_string.eqlComptime("ts")) {
+ loader = options.Loader.ts;
+ } else if (loader_string.eqlComptime("json")) {
+ loader = options.Loader.json;
+ } else if (loader_string.eqlComptime("toml")) {
+ loader = options.Loader.toml;
+ } else if (loader_string.eqlComptime("object")) {
+ const exports_object: JSValue = @as(?JSValue, brk: {
+ const exports_value = plugin_result.get(globalObject, "exports") orelse break :brk null;
+ if (!exports_value.isObject()) {
+ break :brk null;
+ }
+ break :brk exports_value;
+ }) orelse {
+ log.addError(null, logger.Loc.Empty, "Expected object loader to return an \"exports\" object") catch unreachable;
+ return error.PluginError;
+ };
+ return ResolvedSource{
+ .allocator = null,
+ .source_code = ZigString{
+ .ptr = @ptrCast([*]const u8, exports_object.asVoid()),
+ .len = 0,
+ },
+ .specifier = ZigString.init(_specifier),
+ .source_url = ZigString.init(_specifier),
+ .hash = 0,
+ .tag = .object,
+ };
+ } else {
+ log.addErrorFmt(
+ null,
+ logger.Loc.Empty,
+ jsc_vm.allocator,
+ "Expected onLoad() plugin \"loader\" to be one of \"js\", \"jsx\", \"tsx\", \"ts\", \"json\", or \"toml\" but received \"{any}\"",
+ .{loader_string},
+ ) catch unreachable;
+ return error.PluginError;
+ }
+ }
+ }
+
+ if (plugin_result.get(globalObject, "contents")) |code| {
+ if (code.asArrayBuffer(globalObject)) |array_buffer| {
+ virtual_source = .{
+ .path = path,
+ .key_path = path,
+ .contents = array_buffer.byteSlice(),
+ };
+ has_virtual_source = true;
+ } else if (code.isString()) {
+ source_code_slice = code.toSlice(globalObject, jsc_vm.allocator);
+ if (!source_code_slice.allocated) {
+ if (!strings.isAllASCII(source_code_slice.slice())) {
+ var allocated = try strings.allocateLatin1IntoUTF8(jsc_vm.allocator, []const u8, source_code_slice.slice());
+ source_code_slice.ptr = allocated.ptr;
+ source_code_slice.len = @truncate(u32, allocated.len);
+ source_code_slice.allocated = true;
+ source_code_slice.allocator = jsc_vm.allocator;
+ }
+ }
+
+ virtual_source = .{
+ .path = path,
+ .key_path = path,
+ .contents = source_code_slice.slice(),
+ };
+ has_virtual_source = true;
+ }
+ }
+
+ if (!has_virtual_source) {
+ log.addError(null, logger.Loc.Empty, "Expected onLoad() plugin to return \"contents\" as a string or ArrayBufferView") catch unreachable;
+ return error.PluginError;
+ }
+ } else {
+ std.debug.assert(std.fs.path.isAbsolute(specifier)); // if this crashes, it means the resolver was skipped.
+ }
+ }
+ }
+
+ const transpiled_result = transpileSourceCode(
+ jsc_vm,
+ specifier,
+ path,
+ loader,
+ log,
+ if (has_virtual_source) &virtual_source else null,
+ flags,
+ );
+ return transpiled_result;
+ }
+
+ fn transpileSourceCode(
+ jsc_vm: *VirtualMachine,
+ specifier: string,
+ path: Fs.Path,
+ loader: options.Loader,
+ log: *logger.Log,
+ virtual_source: ?*const logger.Source,
+ comptime flags: FetchFlags,
+ ) !ResolvedSource {
+ const disable_transpilying = comptime flags.disableTranspiling();
switch (loader) {
.js, .jsx, .ts, .tsx, .json, .toml => {
@@ -1116,6 +1267,7 @@ pub const VirtualMachine = struct {
.file_hash = hash,
.macro_remappings = macro_remappings,
.jsx = jsc_vm.bundler.options.jsx,
+ .virtual_source = virtual_source,
};
if (is_node_override) {
@@ -1391,12 +1543,27 @@ pub const VirtualMachine = struct {
pub fn resolveMaybeNeedsTrailingSlash(res: *ErrorableZigString, global: *JSGlobalObject, specifier: ZigString, source: ZigString, comptime is_a_file_path: bool, comptime realpath: bool) void {
var result = ResolveFunctionResult{ .path = "", .result = null };
+ var jsc_vm = vm;
+ if (jsc_vm.plugin_runner) |plugin_runner| {
+ if (PluginRunner.couldBePlugin(specifier.slice())) {
+ const namespace = PluginRunner.extractNamespace(specifier.slice());
+ const after_namespace = if (namespace.len == 0)
+ specifier
+ else
+ specifier.substring(namespace.len + 1);
+
+ if (plugin_runner.onResolveJSC(ZigString.init(namespace), after_namespace, source, .bun)) |resolved_path| {
+ res.* = resolved_path;
+ return;
+ }
+ }
+ }
_resolve(&result, global, specifier.slice(), source.slice(), is_a_file_path, realpath) catch |err| {
// This should almost always just apply to dynamic imports
const printed = ResolveError.fmt(
- vm.allocator,
+ jsc_vm.allocator,
specifier.slice(),
source.slice(),
err,
diff --git a/src/bun.js/modules/ObjectModule.h b/src/bun.js/modules/ObjectModule.h
new file mode 100644
index 000000000..42ab4dfa5
--- /dev/null
+++ b/src/bun.js/modules/ObjectModule.h
@@ -0,0 +1,32 @@
+#include "../bindings/ZigGlobalObject.h"
+#include "JavaScriptCore/JSGlobalObject.h"
+
+namespace Zig {
+JSC::SyntheticSourceProvider::SyntheticSourceGenerator
+generateObjectModuleSourceCode(JSC::JSGlobalObject *globalObject,
+ JSC::JSObject *object) {
+ JSC::VM &vm = globalObject->vm();
+
+ return [strongObject = JSC::Strong<JSC::JSObject>(vm, object)](
+ JSC::JSGlobalObject *lexicalGlobalObject,
+ JSC::Identifier moduleKey, Vector<JSC::Identifier, 4> &exportNames,
+ JSC::MarkedArgumentBuffer &exportValues) -> void {
+ JSC::VM &vm = lexicalGlobalObject->vm();
+ GlobalObject *globalObject =
+ reinterpret_cast<GlobalObject *>(lexicalGlobalObject);
+ JSC::JSObject *object = strongObject.get();
+
+ PropertyNameArray properties(vm, PropertyNameMode::Strings,
+ PrivateSymbolMode::Exclude);
+ object->getPropertyNames(globalObject, properties,
+ DontEnumPropertiesMode::Exclude);
+
+ for (auto &entry : properties) {
+ exportNames.append(entry);
+ exportValues.append(object->get(globalObject, entry));
+ }
+ strongObject.clear();
+ };
+}
+
+} // namespace Zig \ No newline at end of file