diff options
author | 2022-09-05 23:05:22 -0700 | |
---|---|---|
committer | 2022-09-05 23:05:22 -0700 | |
commit | 11aa17a57cc679d34e8e6f6f7aa665f565cb7305 (patch) | |
tree | ccb9a605cb4bcc42c6b2665ddbf8d04af72d94c7 /src | |
parent | d2397b60e79e4386c6a7b7a9783a6f8e379a5ae0 (diff) | |
download | bun-11aa17a57cc679d34e8e6f6f7aa665f565cb7305.tar.gz bun-11aa17a57cc679d34e8e6f6f7aa665f565cb7305.tar.zst bun-11aa17a57cc679d34e8e6f6f7aa665f565cb7305.zip |
Support async `onLoad` callbacks in `Bun.plugin`
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/bindings/BunPlugin.cpp | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/ModuleLoader.cpp | 520 | ||||
-rw-r--r-- | src/bun.js/bindings/ModuleLoader.h | 94 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 252 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 21 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 30 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-handwritten.h | 39 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 1 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 950 | ||||
-rw-r--r-- | src/bun.js/modules/BufferModule.h | 67 | ||||
-rw-r--r-- | src/bun.js/modules/EventsModule.h | 49 | ||||
-rw-r--r-- | src/bundler.zig | 37 | ||||
-rw-r--r-- | src/env.zig | 1 | ||||
-rw-r--r-- | src/fs.zig | 4 | ||||
-rw-r--r-- | src/js_ast.zig | 9 | ||||
-rw-r--r-- | src/js_parser.zig | 68 | ||||
-rw-r--r-- | src/runtime.zig | 2 | ||||
-rw-r--r-- | src/string_immutable.zig | 37 |
19 files changed, 1504 insertions, 684 deletions
diff --git a/src/bun.js/bindings/BunPlugin.cpp b/src/bun.js/bindings/BunPlugin.cpp index 0941d2722..ba1d40a0b 100644 --- a/src/bun.js/bindings/BunPlugin.cpp +++ b/src/bun.js/bindings/BunPlugin.cpp @@ -303,8 +303,7 @@ extern "C" EncodedJSValue jsFunctionBunPlugin(JSC::JSGlobalObject* globalObject, 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(promise)); } RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined())); @@ -384,8 +383,7 @@ EncodedJSValue BunPlugin::OnLoad::run(JSC::JSGlobalObject* globalObject, ZigStri 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({}); + 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))); diff --git a/src/bun.js/bindings/ModuleLoader.cpp b/src/bun.js/bindings/ModuleLoader.cpp new file mode 100644 index 000000000..dd5ea01ad --- /dev/null +++ b/src/bun.js/bindings/ModuleLoader.cpp @@ -0,0 +1,520 @@ +#include "root.h" +#include "headers-handwritten.h" + +#include "ModuleLoader.h" + +#include "ZigGlobalObject.h" +#include "JavaScriptCore/JSCInlines.h" +#include "JavaScriptCore/JSNativeStdFunction.h" +#include "JavaScriptCore/JSCJSValueInlines.h" +#include "JavaScriptCore/JSInternalPromise.h" +#include "JavaScriptCore/JSInternalFieldObjectImpl.h" + +#include "ZigSourceProvider.h" + +#include "JavaScriptCore/JSSourceCode.h" +#include "JavaScriptCore/JSString.h" +#include "JavaScriptCore/JSValueInternal.h" +#include "JavaScriptCore/JSVirtualMachineInternal.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "JavaScriptCore/OptionsList.h" +#include "JavaScriptCore/ParserError.h" +#include "JavaScriptCore/ScriptExecutable.h" +#include "JavaScriptCore/SourceOrigin.h" +#include "JavaScriptCore/StackFrame.h" +#include "JavaScriptCore/StackVisitor.h" + +#include "EventEmitter.h" +#include "JSEventEmitter.h" + +#include "../modules/BufferModule.h" +#include "../modules/EventsModule.h" +#include "../modules/ProcessModule.h" +#include "../modules/StringDecoderModule.h" +#include "../modules/ObjectModule.h" +#include "../modules/NodeModuleModule.h" + +namespace Bun { +using namespace Zig; +using namespace WebCore; + +static JSC::JSInternalPromise* rejectedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value) +{ + JSC::VM& vm = globalObject->vm(); + JSInternalPromise* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); + promise->internalField(JSC::JSPromise::Field::ReactionsOrResult).set(vm, promise, value); + promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(promise->internalField(JSC::JSPromise::Field::Flags).get().asUInt32AsAnyInt() | JSC::JSPromise::isFirstResolvingFunctionCalledFlag | static_cast<unsigned>(JSC::JSPromise::Status::Rejected))); + return promise; +} + +static JSC::JSInternalPromise* resolvedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value) +{ + JSC::VM& vm = globalObject->vm(); + + JSInternalPromise* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); + promise->internalField(JSC::JSPromise::Field::ReactionsOrResult).set(vm, promise, value); + promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(promise->internalField(JSC::JSPromise::Field::Flags).get().asUInt32AsAnyInt() | JSC::JSPromise::isFirstResolvingFunctionCalledFlag | static_cast<unsigned>(JSC::JSPromise::Status::Fulfilled))); + return promise; +} + +using namespace JSC; + +static OnLoadResult handleOnLoadObjectResult(Zig::GlobalObject* globalObject, JSC::JSObject* object) +{ + OnLoadResult result {}; + result.type = OnLoadResultTypeObject; + JSC::VM& vm = globalObject->vm(); + if (JSC::JSValue exportsValue = object->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "exports"_s))) { + if (exportsValue.isObject()) { + result.value.object = exportsValue; + return result; + } + } + + auto scope = DECLARE_THROW_SCOPE(vm); + scope.throwException(globalObject, createTypeError(globalObject, "\"object\" loader must return an \"exports\" object"_s)); + result.type = OnLoadResultTypeError; + result.value.error = scope.exception(); + scope.clearException(); + scope.release(); + return result; +} + +JSC::JSInternalPromise* PendingVirtualModuleResult::internalPromise() +{ + return jsCast<JSC::JSInternalPromise*>(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* mod = new (NotNull, allocateCell<PendingVirtualModuleResult>(vm)) PendingVirtualModuleResult(vm, structure); + return mod; +} +Structure* PendingVirtualModuleResult::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info()); +} + +PendingVirtualModuleResult::PendingVirtualModuleResult(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void PendingVirtualModuleResult::finishCreation(VM& vm, const WTF::String& specifier, const WTF::String& referrer) +{ + 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())); +} + +template<typename Visitor> +void PendingVirtualModuleResult::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast<PendingVirtualModuleResult*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); +} + +DEFINE_VISIT_CHILDREN(PendingVirtualModuleResult); + +PendingVirtualModuleResult* PendingVirtualModuleResult::create(JSC::JSGlobalObject* globalObject, const WTF::String& specifier, const WTF::String& referrer) +{ + auto* virtualModule = create(globalObject->vm(), reinterpret_cast<Zig::GlobalObject*>(globalObject)->pendingVirtualModuleResultStructure()); + virtualModule->finishCreation(globalObject->vm(), specifier, referrer); + return virtualModule; +} + +OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::JSValue objectValue) +{ + OnLoadResult result = {}; + result.type = OnLoadResultTypeError; + JSC::VM& vm = globalObject->vm(); + result.value.error = JSC::jsUndefined(); + auto scope = DECLARE_THROW_SCOPE(vm); + BunLoaderType loader = BunLoaderTypeNone; + + JSC::JSObject* object = objectValue.getObject(); + if (UNLIKELY(!object)) { + scope.throwException(globalObject, JSC::createError(globalObject, "Expected onLoad callback to return an object"_s)); + result.value.error = scope.exception(); + scope.clearException(); + scope.release(); + return result; + } + + if (JSC::JSValue loaderValue = object->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "loader"_s))) { + if (loaderValue.isString()) { + if (JSC::JSString* loaderJSString = loaderValue.toStringOrNull(globalObject)) { + WTF::String loaderString = loaderJSString->value(globalObject); + if (loaderString == "js"_s) { + loader = BunLoaderTypeJS; + } else if (loaderString == "object"_s) { + return handleOnLoadObjectResult(globalObject, object); + } else if (loaderString == "jsx"_s) { + loader = BunLoaderTypeJSX; + } else if (loaderString == "ts"_s) { + loader = BunLoaderTypeTS; + } else if (loaderString == "tsx"_s) { + loader = BunLoaderTypeTSX; + } else if (loaderString == "json"_s) { + loader = BunLoaderTypeJSON; + } else if (loaderString == "toml"_s) { + loader = BunLoaderTypeTOML; + } + } + } + } + + if (UNLIKELY(loader == BunLoaderTypeNone)) { + throwException(globalObject, scope, createError(globalObject, "Expected loader to be one of \"js\", \"jsx\", \"object\", \"ts\", \"tsx\", \"toml\", or \"json\""_s)); + result.value.error = scope.exception(); + scope.clearException(); + scope.release(); + return result; + } + + result.value.sourceText.loader = loader; + result.value.sourceText.value = JSValue {}; + result.value.sourceText.string = {}; + + if (JSC::JSValue contentsValue = object->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "contents"_s))) { + if (contentsValue.isString()) { + if (JSC::JSString* contentsJSString = contentsValue.toStringOrNull(globalObject)) { + result.value.sourceText.string = Zig::toZigString(contentsJSString, globalObject); + result.value.sourceText.value = contentsValue; + } + } else if (JSC::JSArrayBufferView* view = JSC::jsDynamicCast<JSC::JSArrayBufferView*>(contentsValue)) { + result.value.sourceText.string = ZigString { reinterpret_cast<const unsigned char*>(view->vector()), view->byteLength() }; + result.value.sourceText.value = contentsValue; + } + } + + if (UNLIKELY(result.value.sourceText.value.isEmpty())) { + throwException(globalObject, scope, createError(globalObject, "Expected \"contents\" to be a string or an ArrayBufferView"_s)); + result.value.error = scope.exception(); + scope.clearException(); + scope.release(); + return result; + } + + result.type = OnLoadResultTypeCode; + return result; +} + +static OnLoadResult handleOnLoadResult(Zig::GlobalObject* globalObject, JSC::JSValue objectValue) +{ + if (JSC::JSPromise* promise = JSC::jsDynamicCast<JSC::JSPromise*>(objectValue)) { + OnLoadResult result = {}; + result.type = OnLoadResultTypePromise; + result.value.promise = objectValue; + return result; + } + + return handleOnLoadResultNotPromise(globalObject, objectValue); +} + +template<bool allowPromise> +static JSValue handleVirtualModuleResult( + Zig::GlobalObject* globalObject, + JSValue virtualModuleResult, + ErrorableResolvedSource* res, + ZigString* specifier, + ZigString* referrer) +{ + auto onLoadResult = handleOnLoadResult(globalObject, virtualModuleResult); + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto reject = [&](JSC::JSValue exception) -> JSValue { + if constexpr (allowPromise) { + return rejectedInternalPromise(globalObject, exception); + } else { + throwException(globalObject, scope, exception); + return exception; + } + }; + + auto resolve = [&](JSValue code) -> JSValue { + res->success = true; + if constexpr (allowPromise) { + return resolvedInternalPromise(globalObject, code); + } else { + return code; + } + }; + + auto rejectOrResolve = [&](JSValue code) -> JSValue { + if (auto* exception = scope.exception()) { + if constexpr (allowPromise) { + scope.clearException(); + return rejectedInternalPromise(globalObject, exception); + } else { + return exception; + } + } + + res->success = true; + + if constexpr (allowPromise) { + return resolvedInternalPromise(globalObject, code); + } else { + return code; + } + }; + + switch (onLoadResult.type) { + case OnLoadResultTypeCode: { + Bun__transpileVirtualModule(globalObject, specifier, referrer, &onLoadResult.value.sourceText.string, onLoadResult.value.sourceText.loader, res); + if (!res->success) { + return reject(JSValue::decode(reinterpret_cast<EncodedJSValue>(res->result.err.ptr))); + } + + auto provider = Zig::SourceProvider::create(res->result.value); + return resolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(provider))); + } + case OnLoadResultTypeError: { + return reject(onLoadResult.value.error); + } + + case OnLoadResultTypeObject: { + JSC::JSObject* object = onLoadResult.value.object.getObject(); + JSC::ensureStillAliveHere(object); + auto function = generateObjectModuleSourceCode( + globalObject, + object); + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create(WTFMove(function), + JSC::SourceOrigin(), Zig::toString(*specifier))); + JSC::ensureStillAliveHere(object); + return rejectOrResolve(JSSourceCode::create(globalObject->vm(), WTFMove(source))); + } + + case OnLoadResultTypePromise: { + JSC::JSPromise* promise = jsCast<JSC::JSPromise*>(onLoadResult.value.promise); + JSFunction* performPromiseThenFunction = globalObject->performPromiseThenFunction(); + auto callData = JSC::getCallData(performPromiseThenFunction); + ASSERT(callData.type != CallData::Type::None); + auto specifierString = Zig::toString(*specifier); + auto referrerString = Zig::toString(*referrer); + PendingVirtualModuleResult* pendingModule = PendingVirtualModuleResult::create(globalObject, specifierString, referrerString); + JSC::JSInternalPromise* internalPromise = pendingModule->internalPromise(); + MarkedArgumentBuffer arguments; + arguments.append(promise); + arguments.append(globalObject->thenable(jsFunctionOnLoadObjectResultResolve)); + arguments.append(globalObject->thenable(jsFunctionOnLoadObjectResultReject)); + arguments.append(jsUndefined()); + arguments.append(pendingModule); + ASSERT(!arguments.hasOverflowed()); + JSC::call(globalObject, performPromiseThenFunction, callData, jsUndefined(), arguments); + return internalPromise; + } + default: { + __builtin_unreachable(); + } + } +} + +template<bool allowPromise> +static JSValue fetchSourceCode( + Zig::GlobalObject* globalObject, + ErrorableResolvedSource* res, + ZigString* specifier, + ZigString* referrer) +{ + void* bunVM = globalObject->bunVM(); + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto reject = [&](JSC::JSValue exception) -> JSValue { + if constexpr (allowPromise) { + return rejectedInternalPromise(globalObject, exception); + } else { + throwException(globalObject, scope, exception); + return JSC::jsUndefined(); + } + }; + + auto resolve = [&](JSValue code) -> JSValue { + if constexpr (allowPromise) { + return resolvedInternalPromise(globalObject, code); + } else { + return code; + } + }; + + auto rejectOrResolve = [&](JSValue code) -> JSValue { + if (auto* exception = scope.exception()) { + scope.clearException(); + return rejectedInternalPromise(globalObject, exception); + } + + if constexpr (allowPromise) { + return resolvedInternalPromise(globalObject, code); + } else { + return code; + } + }; + + if (Bun__fetchBuiltinModule(bunVM, globalObject, specifier, referrer, res)) { + if (!res->success) { + throwException(scope, res->result.err, globalObject); + auto* exception = scope.exception(); + scope.clearException(); + return reject(exception); + } + + auto moduleKey = Zig::toString(*specifier); + + switch (res->result.value.tag) { + case SyntheticModuleType::Module: { + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create(generateNodeModuleModule, + JSC::SourceOrigin(), WTFMove(moduleKey))); + + return rejectOrResolve(JSSourceCode::create(vm, WTFMove(source))); + } + + case SyntheticModuleType::Buffer: { + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create(generateBufferSourceCode, + JSC::SourceOrigin(), WTFMove(moduleKey))); + + auto sourceCode = JSSourceCode::create(vm, WTFMove(source)); + + return rejectOrResolve(JSSourceCode::create(vm, WTFMove(source))); + } + case SyntheticModuleType::Process: { + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create(generateProcessSourceCode, + JSC::SourceOrigin(), WTFMove(moduleKey))); + + return rejectOrResolve(JSSourceCode::create(vm, WTFMove(source))); + } + case SyntheticModuleType::Events: { + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create(generateEventsSourceCode, + JSC::SourceOrigin(), WTFMove(moduleKey))); + + return rejectOrResolve(JSSourceCode::create(vm, WTFMove(source))); + } + case SyntheticModuleType::StringDecoder: { + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create(generateStringDecoderSourceCode, + JSC::SourceOrigin(), WTFMove(moduleKey))); + + return rejectOrResolve(JSSourceCode::create(vm, WTFMove(source))); + } + default: { + auto provider = Zig::SourceProvider::create(res->result.value); + return rejectOrResolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(provider))); + } + } + } + + if (JSC::JSValue virtualModuleResult = JSValue::decode(Bun__runVirtualModule(globalObject, specifier))) { + return handleVirtualModuleResult<allowPromise>(globalObject, virtualModuleResult, res, specifier, referrer); + } + + Bun__transpileFile(bunVM, globalObject, specifier, referrer, res); + if (!res->success) { + throwException(scope, res->result.err, globalObject); + auto* exception = scope.exception(); + scope.clearException(); + return reject(exception); + } + + auto provider = Zig::SourceProvider::create(res->result.value); + return rejectOrResolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(provider))); +} + +extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultResolve(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()); + JSC::JSInternalPromise* promise = pendingModule->internalPromise(); + + ZigString specifier = Zig::toZigString(specifierString, globalObject); + ZigString referrer = Zig::toZigString(referrerString, globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSC::JSValue result = handleVirtualModuleResult<false>(reinterpret_cast<Zig::GlobalObject*>(globalObject), objectResult, &res, &specifier, &referrer); + if (res.success) { + if (scope.exception()) { + auto retValue = JSValue::encode(promise->rejectWithCaughtException(globalObject, scope)); + pendingModule->internalField(2).set(vm, pendingModule, JSC::jsUndefined()); + return retValue; + } + scope.release(); + promise->resolve(globalObject, result); + pendingModule->internalField(2).set(vm, pendingModule, JSC::jsUndefined()); + } else { + throwException(globalObject, scope, result); + auto retValue = JSValue::encode(promise->rejectWithCaughtException(globalObject, scope)); + pendingModule->internalField(2).set(vm, pendingModule, JSC::jsUndefined()); + return retValue; + } + return JSValue::encode(jsUndefined()); +} + +extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultReject(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()); + JSC::JSInternalPromise* promise = pendingModule->internalPromise(); + + ZigString specifier = Zig::toZigString(specifierString, globalObject); + ZigString referrer = Zig::toZigString(referrerString, globalObject); + pendingModule->internalField(2).set(vm, pendingModule, JSC::jsUndefined()); + promise->reject(globalObject, reason); + + return JSValue::encode(reason); +} + +JSValue fetchSourceCodeSync( + Zig::GlobalObject* globalObject, + ErrorableResolvedSource* res, + ZigString* specifier, + ZigString* referrer) +{ + return fetchSourceCode<false>(globalObject, res, specifier, referrer); +} + +JSValue fetchSourceCodeAsync( + Zig::GlobalObject* globalObject, + ErrorableResolvedSource* res, + ZigString* specifier, + ZigString* referrer) +{ + return fetchSourceCode<true>(globalObject, res, specifier, referrer); +} +} +namespace JSC { + +template<unsigned passedNumberOfInternalFields> +template<typename Visitor> +void JSInternalFieldObjectImpl<passedNumberOfInternalFields>::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast<JSInternalFieldObjectImpl*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.appendValues(thisObject->m_internalFields, numberOfInternalFields); +} + +DEFINE_VISIT_CHILDREN_WITH_MODIFIER(template<unsigned passedNumberOfInternalFields>, JSInternalFieldObjectImpl<passedNumberOfInternalFields>); + +} // namespace JSC diff --git a/src/bun.js/bindings/ModuleLoader.h b/src/bun.js/bindings/ModuleLoader.h new file mode 100644 index 000000000..98f8b7dbb --- /dev/null +++ b/src/bun.js/bindings/ModuleLoader.h @@ -0,0 +1,94 @@ +#include "root.h" +#include "headers-handwritten.h" + +#include "JavaScriptCore/JSCInlines.h" +#include "BunClientData.h" + +namespace Zig { +class GlobalObject; +} + +namespace JSC { +class JSInternalPromise; +} + +namespace Bun { +using namespace JSC; + +typedef uint8_t OnLoadResultType; +const OnLoadResultType OnLoadResultTypeError = 0; +const OnLoadResultType OnLoadResultTypeCode = 1; +const OnLoadResultType OnLoadResultTypeObject = 2; +const OnLoadResultType OnLoadResultTypePromise = 3; + +struct CodeString { + ZigString string; + JSC::JSValue value; + BunLoaderType loader; +}; + +union OnLoadResultValue { + CodeString sourceText; + JSC::JSValue object; + JSC::JSValue promise; + JSC::JSValue error; +}; + +struct OnLoadResult { + OnLoadResultValue value; + OnLoadResultType type; +}; + +class PendingVirtualModuleResult : public JSC::JSInternalFieldObjectImpl<3> { +public: + using Base = JSC::JSInternalFieldObjectImpl<3>; + + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<PendingVirtualModuleResult, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForPendingVirtualModuleResult.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForPendingVirtualModuleResult = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForPendingVirtualModuleResult.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForPendingVirtualModuleResult = WTFMove(space); }); + } + + JS_EXPORT_PRIVATE static PendingVirtualModuleResult* create(VM&, Structure*); + static PendingVirtualModuleResult* create(JSC::JSGlobalObject* globalObject, const WTF::String& specifier, const WTF::String& referrer); + static PendingVirtualModuleResult* createWithInitialValues(VM&, Structure*); + static Structure* createStructure(VM&, JSGlobalObject*, JSValue); + + JSC::JSInternalPromise* internalPromise(); + + static std::array<JSValue, numberOfInternalFields> initialValues() + { + return { { + jsUndefined(), + jsUndefined(), + jsUndefined(), + } }; + } + + DECLARE_EXPORT_INFO; + DECLARE_VISIT_CHILDREN; + + PendingVirtualModuleResult(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, const WTF::String& specifier, const WTF::String& referrer); +}; + +OnLoadResult handleOnLoadResultNotPromise(Zig::GlobalObject* globalObject, JSC::JSValue objectValue); +JSValue fetchSourceCodeSync( + Zig::GlobalObject* globalObject, + ErrorableResolvedSource* res, + ZigString* specifier, + ZigString* referrer); + +JSValue fetchSourceCodeAsync( + Zig::GlobalObject* globalObject, + ErrorableResolvedSource* res, + ZigString* specifier, + 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 888bf8f6b..2753707e8 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -103,6 +103,7 @@ #include "JSSQLStatement.h" #include "ReadableStreamBuiltins.h" #include "BunJSCModule.h" +#include "ModuleLoader.h" #include "ZigGeneratedClasses.h" @@ -160,13 +161,6 @@ using JSBuffer = WebCore::JSBuffer; #include "DOMJITHelpers.h" #include <JavaScriptCore/DFGAbstractHeap.h> -#include "../modules/BufferModule.h" -#include "../modules/EventsModule.h" -#include "../modules/ProcessModule.h" -#include "../modules/StringDecoderModule.h" -#include "../modules/ObjectModule.h" -#include "../modules/NodeModuleModule.h" - // #include <iostream> static bool has_loaded_jsc = false; @@ -1022,6 +1016,7 @@ JSC: static NeverDestroyed<const String> bunJSCString(MAKE_STATIC_STRING_IMPL("bun:jsc")); static NeverDestroyed<const String> bunStreamString(MAKE_STATIC_STRING_IMPL("bun:stream")); static NeverDestroyed<const String> noopString(MAKE_STATIC_STRING_IMPL("noop")); + static NeverDestroyed<const String> createImportMeta(MAKE_STATIC_STRING_IMPL("createImportMeta")); JSC::JSValue moduleName = callFrame->argument(0); if (moduleName.isNumber()) { @@ -1081,6 +1076,11 @@ JSC: return JSValue::encode(obj); } + if (string == createImportMeta) { + Zig::ImportMetaObject* obj = Zig::ImportMetaObject::create(globalObject, callFrame->argument(1)); + return JSValue::encode(obj); + } + if (UNLIKELY(string == noopString)) { auto* obj = constructEmptyObject(globalObject); obj->putDirectCustomAccessor(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "getterSetter"_s)), JSC::CustomGetterSetter::create(vm, noop_getter, noop_setter), 0); @@ -1884,6 +1884,11 @@ void GlobalObject::finishCreation(VM& vm) init.set(JSModuleNamespaceObject::createStructure(init.vm, init.owner, init.owner->objectPrototype())); }); + this->m_pendingVirtualModuleResultStructure.initLater( + [](const Initializer<Structure>& init) { + init.set(Bun::PendingVirtualModuleResult::createStructure(init.vm, init.owner, init.owner->objectPrototype())); + }); + this->initGeneratedLazyClasses(); m_NapiClassStructure.initLater( @@ -2655,87 +2660,27 @@ static JSC_DEFINE_HOST_FUNCTION(functionFulfillModuleSync, res.result.err.code = 0; res.result.err.ptr = nullptr; - Zig__GlobalObject__fetch(&res, globalObject, &specifier, &specifier); + JSValue result = Bun::fetchSourceCodeSync( + reinterpret_cast<Zig::GlobalObject*>(globalObject), + &res, + &specifier, + &specifier); - if (!res.success) { - throwException(scope, res.result.err, globalObject); - return JSValue::encode(JSC::jsUndefined()); + if (result.isUndefined() || !result) { + return JSValue::encode(result); } - switch (res.result.value.tag) { - case SyntheticModuleType::Buffer: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create( - generateBufferSourceCode, - JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath("node:buffer"_s)), WTFMove(moduleKey))); - - 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::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( - generateProcessSourceCode, - JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath("node:process"_s)), WTFMove(moduleKey))); - - 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::Events: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create( - generateEventsSourceCode, - JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath("node:events"_s)), WTFMove(moduleKey))); - - 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::Module: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create( - generateNodeModuleModule, - JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath("node:module"_s)), WTFMove(moduleKey))); - - 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::StringDecoder: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create( - generateStringDecoderSourceCode, - JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath("node:string_decoder"_s)), WTFMove(moduleKey))); - - globalObject->moduleLoader()->provideFetch(globalObject, key, WTFMove(source)); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined())); - } - default: { - auto provider = Zig::SourceProvider::create(res.result.value); - globalObject->moduleLoader()->provideFetch(globalObject, key, JSC::SourceCode(provider)); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined())); - } - } + globalObject->moduleLoader()->provideFetch(globalObject, key, jsCast<JSC::JSSourceCode*>(result)->sourceCode()); + RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined())); +} + +static JSC::JSInternalPromise* rejectedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value) +{ + JSC::VM& vm = globalObject->vm(); + JSInternalPromise* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); + promise->internalField(JSC::JSPromise::Field::ReactionsOrResult).set(vm, promise, value); + promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(promise->internalField(JSC::JSPromise::Field::Flags).get().asUInt32AsAnyInt() | JSC::JSPromise::isFirstResolvingFunctionCalledFlag | static_cast<unsigned>(JSC::JSPromise::Status::Rejected))); + return promise; } JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalObject, @@ -2743,20 +2688,15 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb JSValue value1, JSValue value2) { JSC::VM& vm = globalObject->vm(); - JSC::JSInternalPromise* promise = JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); auto scope = DECLARE_THROW_SCOPE(vm); - auto rejectWithError = [&](JSC::JSValue error) { - promise->reject(globalObject, error); - return promise; - }; - auto moduleKey = key.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope)); + if (UNLIKELY(scope.exception())) + return rejectedInternalPromise(globalObject, scope.exception()->value()); if (moduleKey.endsWith(".node"_s)) { - return rejectWithError(createTypeError(globalObject, "To load Node-API modules, use require() or process.dlopen instead of import."_s)); + return rejectedInternalPromise(globalObject, createTypeError(globalObject, "To load Node-API modules, use require() or process.dlopen instead of import."_s)); } auto moduleKeyZig = toZigString(moduleKey); @@ -2766,123 +2706,19 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb res.result.err.code = 0; res.result.err.ptr = nullptr; - Zig__GlobalObject__fetch(&res, globalObject, &moduleKeyZig, &source); - - if (!res.success) { - throwException(scope, res.result.err, globalObject); - RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope)); - } - - switch (res.result.value.tag) { - case 1: { - auto buffer = Vector<uint8_t>(res.result.value.source_code.ptr, res.result.value.source_code.len); - auto source = JSC::SourceCode( - JSC::WebAssemblySourceProvider::create(WTFMove(buffer), - JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(Zig::toString(res.result.value.source_url))), - WTFMove(moduleKey))); - - auto sourceCode = JSSourceCode::create(vm, WTFMove(source)); - RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope)); - - promise->resolve(globalObject, sourceCode); - scope.release(); - - 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)); + JSValue result = Bun::fetchSourceCodeAsync( + reinterpret_cast<Zig::GlobalObject*>(globalObject), + &res, + &moduleKeyZig, + &source); - promise->resolve(globalObject, sourceCode); - scope.release(); - return promise; - } - case SyntheticModuleType::Module: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create(generateNodeModuleModule, - 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, - 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::Process: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create(generateProcessSourceCode, - 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::Events: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create(generateEventsSourceCode, - 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::StringDecoder: { - auto source = JSC::SourceCode( - JSC::SyntheticSourceProvider::create(generateStringDecoderSourceCode, - 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; - } - default: { - auto provider = Zig::SourceProvider::create(res.result.value); - auto jsSourceCode = JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)); - promise->resolve(globalObject, jsSourceCode); - } + if (auto* internalPromise = JSC::jsDynamicCast<JSC::JSInternalPromise*>(result)) { + return internalPromise; + } else if (auto* promise = JSC::jsDynamicCast<JSC::JSPromise*>(result)) { + return jsCast<JSC::JSInternalPromise*>(promise); + } else { + return rejectedInternalPromise(globalObject, result); } - - // if (provider.ptr()->isBytecodeCacheEnabled()) { - // provider.ptr()->readOrGenerateByteCodeCache(vm, jsSourceCode->sourceCode()); - // } - - scope.release(); - - globalObject->vm().drainMicrotasks(); - return promise; } JSC::JSObject* GlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObject* globalObject, diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 6c5c23b3a..160aef714 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -35,7 +35,9 @@ class EventLoopTask; #include "BunPlugin.h" extern "C" void Bun__reportError(JSC__JSGlobalObject*, JSC__JSValue); - +// 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); // #include "EventTarget.h" // namespace WebCore { @@ -239,7 +241,10 @@ public: Bun__HTTPRequestContextDebugTLS__onResolve, Bun__HTTPRequestContextDebugTLS__onResolveStream, + jsFunctionOnLoadObjectResultResolve, + jsFunctionOnLoadObjectResultReject, }; + static constexpr size_t promiseFunctionsSize = 18; static PromiseFunctions promiseHandlerID(EncodedJSValue (*handler)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1)) { @@ -275,6 +280,14 @@ public: return PromiseFunctions::Bun__HTTPRequestContextDebugTLS__onResolve; } else if (handler == Bun__HTTPRequestContextDebugTLS__onResolveStream) { return PromiseFunctions::Bun__HTTPRequestContextDebugTLS__onResolveStream; + } else if (handler == Bun__HTTPRequestContextDebugTLS__onResolveStream) { + return PromiseFunctions::Bun__HTTPRequestContextDebugTLS__onResolveStream; + } else if (handler == Bun__HTTPRequestContextDebugTLS__onResolveStream) { + return PromiseFunctions::Bun__HTTPRequestContextDebugTLS__onResolveStream; + } else if (handler == jsFunctionOnLoadObjectResultResolve) { + return PromiseFunctions::jsFunctionOnLoadObjectResultResolve; + } else if (handler == jsFunctionOnLoadObjectResultReject) { + return PromiseFunctions::jsFunctionOnLoadObjectResultReject; } else { RELEASE_ASSERT_NOT_REACHED(); } @@ -300,7 +313,7 @@ public: mutable WriteBarrier<JSFunction> m_readableStreamToJSON; mutable WriteBarrier<JSFunction> m_readableStreamToArrayBuffer; mutable WriteBarrier<JSFunction> m_assignToStream; - mutable WriteBarrier<JSFunction> m_thenables[16]; + mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1]; void trackFFIFunction(JSC::JSFunction* function) { @@ -311,6 +324,8 @@ public: BunPlugin::OnResolve onResolvePlugins[BunPluginTargetMax + 1] {}; BunPluginTarget defaultBunPluginTarget = BunPluginTargetBun; + JSC::Structure* pendingVirtualModuleResultStructure() { return m_pendingVirtualModuleResultStructure.get(this); } + // When a napi module initializes on dlopen, we need to know what the value is JSValue pendingNapiModule = JSValue {}; @@ -344,6 +359,8 @@ private: LazyProperty<JSGlobalObject, JSMap> m_requireMap; LazyProperty<JSGlobalObject, JSObject> m_performanceObject; + LazyProperty<JSGlobalObject, JSC::Structure> m_pendingVirtualModuleResultStructure; + LazyProperty<JSGlobalObject, JSObject> m_encodeIntoObjectPrototype; // LazyProperty<JSGlobalObject, WebCore::JSEventTarget> m_eventTarget; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 81e9c602b..bb0718ea9 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -873,27 +873,33 @@ bool JSC__JSModuleLoader__checkSyntax(JSC__JSGlobalObject* arg0, const JSC__Sour return result; } -JSC__JSValue JSC__JSModuleLoader__evaluate(JSC__JSGlobalObject* arg0, const unsigned char* arg1, +JSC__JSValue JSC__JSModuleLoader__evaluate(JSC__JSGlobalObject* globalObject, const unsigned char* arg1, size_t arg2, const unsigned char* arg3, size_t arg4, JSC__JSValue JSValue5, JSC__JSValue* arg6) { - WTF::String src = WTF::String(WTF::StringImpl::createWithoutCopying(arg1, arg2)); - WTF::URL origin = WTF::URL::fileURLWithFileSystemPath(WTF::StringView(arg3, arg4)); + WTF::String src = WTF::String::fromUTF8(arg1, arg2).isolatedCopy(); + WTF::URL origin = WTF::URL::fileURLWithFileSystemPath(WTF::String(WTF::StringImpl::createWithoutCopying(arg3, arg4))).isolatedCopy(); - JSC::VM& vm = arg0->vm(); - JSC::JSLockHolder locker(vm); + JSC::VM& vm = globalObject->vm(); JSC::SourceCode sourceCode = JSC::makeSource( - src, JSC::SourceOrigin { origin }, origin.lastPathComponent().toStringWithoutCopying(), + src, JSC::SourceOrigin { origin }, origin.fileSystemPath(), WTF::TextPosition(), JSC::SourceProviderSourceType::Module); - WTF::NakedPtr<JSC::Exception> exception; - auto val = JSC::evaluate(arg0, sourceCode, JSC::JSValue(), exception); - if (exception.get()) { - *arg6 = JSC::JSValue::encode(JSC::JSValue(exception.get())); + globalObject->moduleLoader()->provideFetch(globalObject, jsString(vm, origin.fileSystemPath()), WTFMove(sourceCode)); + auto* promise = JSC::importModule(globalObject, JSC::Identifier::fromString(vm, origin.fileSystemPath()), JSValue(), JSValue()); + + if (promise->status(vm) == JSC::JSPromise::Status::Pending) { + vm.drainMicrotasks(); } - vm.drainMicrotasks(); - return JSC::JSValue::encode(val); + if (promise->status(vm) == JSC::JSPromise::Status::Fulfilled) { + return JSC::JSValue::encode(promise->result(vm)); + } else if (promise->status(vm) == JSC::JSPromise::Status::Rejected) { + *arg6 = JSC::JSValue::encode(promise->result(vm)); + return JSC::JSValue::encode(JSC::jsUndefined()); + } else { + return JSC::JSValue::encode(promise); + } } JSC__JSInternalPromise* JSC__JSModuleLoader__importModule(JSC__JSGlobalObject* arg0, const JSC__Identifier* arg1) diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 45d4752e0..36678e22e 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -116,6 +116,19 @@ const JSErrorCode JSErrorCodeOutOfMemoryError = 8; 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; + #pragma mark - Stream typedef uint8_t Encoding; @@ -199,6 +212,32 @@ extern "C" void ZigString__free(const unsigned char* ptr, size_t len, void* allo extern "C" void Microtask__run(void* ptr, void* global); extern "C" void Microtask__run_default(void* ptr, void* global); +extern "C" bool Bun__transpileVirtualModule( + JSC::JSGlobalObject* global, + ZigString* specifier, + ZigString* referrer, + ZigString* sourceCode, + BunLoaderType loader, + ErrorableResolvedSource* result); + +extern "C" JSC::EncodedJSValue Bun__runVirtualModule( + JSC::JSGlobalObject* global, + ZigString* specifier); + +extern "C" bool Bun__transpileFile( + void* bunVM, + JSC::JSGlobalObject* global, + ZigString* specifier, + ZigString* referrer, + ErrorableResolvedSource* result); + +extern "C" bool Bun__fetchBuiltinModule( + void* bunVM, + JSC::JSGlobalObject* global, + ZigString* specifier, + ZigString* referrer, + ErrorableResolvedSource* result); + // Used in process.version extern "C" const char* Bun__version; diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 75b7995f0..456ef6aa4 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -28,6 +28,7 @@ public: std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSSink; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForStringDecoder; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForStringDecoderConstructor; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForPendingVirtualModuleResult; #include "ZigGeneratedClasses+DOMClientIsoSubspaces.h" /* --- bun --- */ diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 42fb1d88b..c5e01c902 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -28,6 +28,7 @@ public: std::unique_ptr<IsoSubspace> m_subspaceForJSSink; std::unique_ptr<IsoSubspace> m_subspaceForStringDecoder; std::unique_ptr<IsoSubspace> m_subspaceForStringDecoderConstructor; + std::unique_ptr<IsoSubspace> m_subspaceForPendingVirtualModuleResult; #include "ZigGeneratedClasses+DOMIsoSubspaces.h" /*-- BUN --*/ diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 2dd7a9d3f..4053b89d5 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -325,6 +325,8 @@ pub const VirtualMachine = struct { timer: Bun.Timer = Bun.Timer{}, uws_event_loop: ?*uws.Loop = null, + is_printing_plugin: bool = false, + plugin_runner: ?PluginRunner = null, /// Do not access this field directly @@ -596,7 +598,7 @@ pub const VirtualMachine = struct { // } - threadlocal var source_code_printer: ?*js_printer.BufferPrinter = null; + pub threadlocal var source_code_printer: ?*js_printer.BufferPrinter = null; pub fn clearRefString(_: *anyopaque, ref_string: *JSC.RefString) void { _ = VirtualMachine.vm.ref_strings.remove(ref_string.hash); @@ -676,27 +678,8 @@ pub const VirtualMachine = struct { const shared_library_suffix = if (Environment.isMac) "dylib" else if (Environment.isLinux) "so" else ""; - const FetchFlags = enum { - transpile, - print_source, - print_source_and_clone, - - pub fn disableTranspiling(this: FetchFlags) bool { - return this != .transpile; - } - }; - - fn _fetch( - jsc_vm: *VirtualMachine, - globalObject: *JSGlobalObject, - _specifier: string, - _: string, - log: *logger.Log, - comptime flags: FetchFlags, - ) !ResolvedSource { - std.debug.assert(VirtualMachine.vm_loaded); - const disable_transpilying = comptime flags.disableTranspiling(); - if (jsc_vm.node_modules != null and strings.eqlComptime(_specifier, bun_file_import_path)) { + pub fn fetchBuiltinModule(jsc_vm: *VirtualMachine, specifier: string, log: *logger.Log, comptime disable_transpilying: bool) !?ResolvedSource { + if (jsc_vm.node_modules != null and strings.eqlComptime(specifier, bun_file_import_path)) { // We kind of need an abstraction around this. // Basically we should subclass JSC::SourceCode with: // - hash @@ -712,7 +695,7 @@ pub const VirtualMachine = struct { .source_url = ZigString.init(bun_file_import_path[1..]), .hash = 0, // TODO }; - } else if (jsc_vm.node_modules == null and strings.eqlComptime(_specifier, Runtime.Runtime.Imports.Name)) { + } else if (jsc_vm.node_modules == null and strings.eqlComptime(specifier, Runtime.Runtime.Imports.Name)) { return ResolvedSource{ .allocator = null, .source_code = ZigString.init(Runtime.Runtime.sourceContentBun()), @@ -720,7 +703,7 @@ pub const VirtualMachine = struct { .source_url = ZigString.init(Runtime.Runtime.Imports.Name), .hash = Runtime.Runtime.versionHash(), }; - } else if (HardcodedModule.Map.get(_specifier)) |hardcoded| { + } else if (HardcodedModule.Map.get(specifier)) |hardcoded| { switch (hardcoded) { // This is all complicated because the imports have to be linked and we want to run the printer on it // so it consistently handles bundled imports @@ -1011,356 +994,61 @@ pub const VirtualMachine = struct { }; }, } - } else if (_specifier.len > js_ast.Macro.namespaceWithColon.len and - strings.eqlComptimeIgnoreLen(_specifier[0..js_ast.Macro.namespaceWithColon.len], js_ast.Macro.namespaceWithColon)) + } else if (specifier.len > js_ast.Macro.namespaceWithColon.len and + strings.eqlComptimeIgnoreLen(specifier[0..js_ast.Macro.namespaceWithColon.len], js_ast.Macro.namespaceWithColon)) { if (comptime !disable_transpilying) { - if (jsc_vm.macro_entry_points.get(MacroEntryPoint.generateIDFromSpecifier(_specifier))) |entry| { + if (jsc_vm.macro_entry_points.get(MacroEntryPoint.generateIDFromSpecifier(specifier))) |entry| { return ResolvedSource{ .allocator = null, .source_code = ZigString.init(entry.source.contents), - .specifier = ZigString.init(_specifier), - .source_url = ZigString.init(_specifier), + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(specifier), .hash = 0, }; } } } - var specifier = normalizeSpecifier(_specifier); + return null; + } + + pub fn fetchWithoutOnLoadPlugins( + jsc_vm: *VirtualMachine, + _specifier: string, + log: *logger.Log, + ret: *ErrorableResolvedSource, + comptime flags: FetchFlags, + ) !ResolvedSource { + std.debug.assert(VirtualMachine.vm_loaded); + + if (try fetchBuiltinModule(jsc_vm, _specifier, log, comptime flags.disableTranspiling())) |builtin| { + return builtin; + } + + var specifier = ModuleLoader.normalizeSpecifier(jsc_vm, _specifier); var path = Fs.Path.init(specifier); - const default_loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: { + const 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( + return try ModuleLoader.transpileSourceCode( jsc_vm, specifier, path, loader, log, - if (has_virtual_source) &virtual_source else null, + null, + ret, + VirtualMachine.source_code_printer.?, 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 => { - jsc_vm.transpiled_count += 1; - jsc_vm.bundler.resetStore(); - const hash = http.Watcher.getHash(path.text); - - var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; - - var fd: ?StoredFileDescriptorType = null; - var package_json: ?*PackageJSON = null; - - if (jsc_vm.watcher) |watcher| { - if (watcher.indexOf(hash)) |index| { - const _fd = watcher.watchlist.items(.fd)[index]; - fd = if (_fd > 0) _fd else null; - package_json = watcher.watchlist.items(.package_json)[index]; - } - } - - var old = jsc_vm.bundler.log; - jsc_vm.bundler.log = log; - jsc_vm.bundler.linker.log = log; - jsc_vm.bundler.resolver.log = log; - - defer { - jsc_vm.bundler.log = old; - jsc_vm.bundler.linker.log = old; - jsc_vm.bundler.resolver.log = old; - } - - // this should be a cheap lookup because 24 bytes == 8 * 3 so it's read 3 machine words - const is_node_override = specifier.len > "/bun-vfs/node_modules/".len and strings.eqlComptimeIgnoreLen(specifier[0.."/bun-vfs/node_modules/".len], "/bun-vfs/node_modules/"); - - const macro_remappings = if (jsc_vm.macro_mode or !jsc_vm.has_any_macro_remappings or is_node_override) - MacroRemap{} - else - jsc_vm.bundler.options.macro_remap; - - var fallback_source: logger.Source = undefined; - - var parse_options = Bundler.ParseOptions{ - .allocator = allocator, - .path = path, - .loader = loader, - .dirname_fd = 0, - .file_descriptor = fd, - .file_hash = hash, - .macro_remappings = macro_remappings, - .jsx = jsc_vm.bundler.options.jsx, - .virtual_source = virtual_source, - }; - - if (is_node_override) { - if (NodeFallbackModules.contentsFromPath(specifier)) |code| { - const fallback_path = Fs.Path.initWithNamespace(specifier, "node"); - fallback_source = logger.Source{ .path = fallback_path, .contents = code, .key_path = fallback_path }; - parse_options.virtual_source = &fallback_source; - } - } - - var parse_result = jsc_vm.bundler.parseMaybeReturnFileOnly( - parse_options, - null, - disable_transpilying, - ) orelse { - return error.ParseError; - }; - - if (comptime disable_transpilying) { - return ResolvedSource{ - .allocator = null, - .source_code = switch (comptime flags) { - .print_source_and_clone => ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), - .print_source => ZigString.init(parse_result.source.contents), - else => unreachable, - }, - .specifier = ZigString.init(specifier), - .source_url = ZigString.init(path.text), - .hash = 0, - }; - } - - const start_count = jsc_vm.bundler.linker.import_counter; - // We _must_ link because: - // - node_modules bundle won't be properly - try jsc_vm.bundler.linker.link( - path, - &parse_result, - jsc_vm.origin, - .absolute_path, - false, - true, - ); - - if (!jsc_vm.macro_mode) - jsc_vm.resolved_count += jsc_vm.bundler.linker.import_counter - start_count; - jsc_vm.bundler.linker.import_counter = 0; - - var printer = source_code_printer.?.*; - printer.ctx.reset(); - - const written = brk: { - defer source_code_printer.?.* = printer; - break :brk try jsc_vm.bundler.printWithSourceMap( - parse_result, - @TypeOf(&printer), - &printer, - .esm_ascii, - SavedSourceMap.SourceMapHandler.init(&jsc_vm.source_mappings), - ); - }; - - if (written == 0) { - return error.PrintingErrorWriteFailed; - } - - if (jsc_vm.has_loaded) { - return jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null); - } - - return ResolvedSource{ - .allocator = null, - .source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())), - .specifier = ZigString.init(specifier), - .source_url = ZigString.init(path.text), - // // TODO: change hash to a bitfield - // .hash = 1, - - // having JSC own the memory causes crashes - .hash = 0, - }; - }, - // provideFetch() should be called - .napi => unreachable, - // .wasm => { - // jsc_vm.transpiled_count += 1; - // var fd: ?StoredFileDescriptorType = null; - - // var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; - - // const hash = http.Watcher.getHash(path.text); - // if (jsc_vm.watcher) |watcher| { - // if (watcher.indexOf(hash)) |index| { - // const _fd = watcher.watchlist.items(.fd)[index]; - // fd = if (_fd > 0) _fd else null; - // } - // } - - // var parse_options = Bundler.ParseOptions{ - // .allocator = allocator, - // .path = path, - // .loader = loader, - // .dirname_fd = 0, - // .file_descriptor = fd, - // .file_hash = hash, - // .macro_remappings = MacroRemap{}, - // .jsx = jsc_vm.bundler.options.jsx, - // }; - - // var parse_result = jsc_vm.bundler.parse( - // parse_options, - // null, - // ) orelse { - // return error.ParseError; - // }; - - // return ResolvedSource{ - // .allocator = if (jsc_vm.has_loaded) &jsc_vm.allocator else null, - // .source_code = ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), - // .specifier = ZigString.init(specifier), - // .source_url = ZigString.init(path.text), - // .hash = 0, - // .tag = ResolvedSource.Tag.wasm, - // }; - // }, - else => { - return ResolvedSource{ - .allocator = &vm.allocator, - .source_code = ZigString.init(try strings.quotedAlloc(jsc_vm.allocator, path.pretty)), - .specifier = ZigString.init(path.text), - .source_url = ZigString.init(path.text), - .hash = 0, - }; - }, - } - } pub const ResolveFunctionResult = struct { result: ?Resolver.Result, path: string, @@ -1546,42 +1234,6 @@ pub const VirtualMachine = struct { res.* = ErrorableZigString.ok(ZigString.init(result.path)); } - pub fn normalizeSpecifier(slice_: string) string { - var vm_ = VirtualMachine.vm; - - var slice = slice_; - if (slice.len == 0) return slice; - var was_http = false; - if (strings.hasPrefixComptime(slice, "https://")) { - slice = slice["https://".len..]; - was_http = true; - } else if (strings.hasPrefixComptime(slice, "http://")) { - slice = slice["http://".len..]; - was_http = true; - } - - if (strings.hasPrefix(slice, vm_.origin.host)) { - slice = slice[vm_.origin.host.len..]; - } else if (was_http) { - if (strings.indexOfChar(slice, '/')) |i| { - slice = slice[i..]; - } - } - - if (vm_.origin.path.len > 1) { - if (strings.hasPrefix(slice, vm_.origin.path)) { - slice = slice[vm_.origin.path.len..]; - } - } - - if (vm_.bundler.options.routes.asset_prefix_path.len > 0) { - if (strings.hasPrefix(slice, vm_.bundler.options.routes.asset_prefix_path)) { - slice = slice[vm_.bundler.options.routes.asset_prefix_path.len..]; - } - } - - return slice; - } // // This double prints // pub fn promiseRejectionTracker(global: *JSGlobalObject, promise: *JSPromise, _: JSPromiseRejectionOperation) callconv(.C) JSValue { @@ -1605,12 +1257,12 @@ pub const VirtualMachine = struct { global.bunVM(); const result = if (!jsc_vm.bundler.options.disable_transpilation) - @call(.{ .modifier = .always_inline }, _fetch, .{ jsc_vm, global, spec, source.slice(), &log, .transpile }) catch |err| { + @call(.{ .modifier = .always_inline }, fetchWithoutOnLoadPlugins, .{ jsc_vm, spec, &log, ret, .transpile }) catch |err| { processFetchLog(global, specifier, source, &log, ret, err); return; } else - _fetch(jsc_vm, global, spec, source.slice(), &log, .print_source_and_clone) catch |err| { + fetchWithoutOnLoadPlugins(jsc_vm, spec, &log, ret, .print_source_and_clone) catch |err| { processFetchLog(global, specifier, source, &log, ret, err); return; }; @@ -1657,7 +1309,7 @@ pub const VirtualMachine = struct { ret.success = true; } - fn processFetchLog(globalThis: *JSGlobalObject, specifier: ZigString, referrer: ZigString, log: *logger.Log, ret: *ErrorableResolvedSource, err: anyerror) void { + pub fn processFetchLog(globalThis: *JSGlobalObject, specifier: ZigString, referrer: ZigString, log: *logger.Log, ret: *ErrorableResolvedSource, err: anyerror) void { switch (log.msgs.items.len) { 0 => { const msg = logger.Msg{ @@ -2074,7 +1726,8 @@ pub const VirtualMachine = struct { @maximum(top.position.column_start, 0), )) |mapping| { var log = logger.Log.init(default_allocator); - var original_source = _fetch(this, this.global, top.source_url.slice(), "", &log, .print_source) catch return; + var errorable: ErrorableResolvedSource = undefined; + var original_source = fetchWithoutOnLoadPlugins(this, top.source_url.slice(), &log, &errorable, .print_source) catch return; const code = original_source.source_code.slice(); top.position.line = mapping.original.lines; top.position.line_start = mapping.original.lines; @@ -3091,3 +2744,528 @@ inline fn jsSyntheticModule(comptime name: ResolvedSource.Tag) ResolvedSource { .tag = name, }; } + +fn dumpSource(specifier: string, printer: anytype) !void { + const BunDebugHolder = struct { + pub var dir: ?std.fs.Dir = null; + }; + if (BunDebugHolder.dir == null) { + BunDebugHolder.dir = try std.fs.cwd().makeOpenPath("/tmp/bun-debug-src/", .{ .iterate = true }); + } + + if (std.fs.path.dirname(specifier)) |dir_path| { + var parent = try BunDebugHolder.dir.?.makeOpenPath(dir_path[1..], .{ .iterate = true }); + defer parent.close(); + try parent.writeFile(std.fs.path.basename(specifier), printer.ctx.getWritten()); + } else { + try BunDebugHolder.dir.?.writeFile(std.fs.path.basename(specifier), printer.ctx.getWritten()); + } +} + +pub const ModuleLoader = struct { + pub fn transpileSourceCode( + jsc_vm: *VirtualMachine, + specifier: string, + path: Fs.Path, + loader: options.Loader, + log: *logger.Log, + virtual_source: ?*const logger.Source, + ret: *ErrorableResolvedSource, + source_code_printer: *js_printer.BufferPrinter, + comptime flags: FetchFlags, + ) !ResolvedSource { + const disable_transpilying = comptime flags.disableTranspiling(); + + switch (loader) { + .js, .jsx, .ts, .tsx, .json, .toml => { + jsc_vm.transpiled_count += 1; + jsc_vm.bundler.resetStore(); + const hash = http.Watcher.getHash(path.text); + + var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; + + var fd: ?StoredFileDescriptorType = null; + var package_json: ?*PackageJSON = null; + + if (jsc_vm.watcher) |watcher| { + if (watcher.indexOf(hash)) |index| { + const _fd = watcher.watchlist.items(.fd)[index]; + fd = if (_fd > 0) _fd else null; + package_json = watcher.watchlist.items(.package_json)[index]; + } + } + + var old = jsc_vm.bundler.log; + jsc_vm.bundler.log = log; + jsc_vm.bundler.linker.log = log; + jsc_vm.bundler.resolver.log = log; + + defer { + jsc_vm.bundler.log = old; + jsc_vm.bundler.linker.log = old; + jsc_vm.bundler.resolver.log = old; + } + + // this should be a cheap lookup because 24 bytes == 8 * 3 so it's read 3 machine words + const is_node_override = specifier.len > "/bun-vfs/node_modules/".len and strings.eqlComptimeIgnoreLen(specifier[0.."/bun-vfs/node_modules/".len], "/bun-vfs/node_modules/"); + + const macro_remappings = if (jsc_vm.macro_mode or !jsc_vm.has_any_macro_remappings or is_node_override) + MacroRemap{} + else + jsc_vm.bundler.options.macro_remap; + + var fallback_source: logger.Source = undefined; + + var parse_options = Bundler.ParseOptions{ + .allocator = allocator, + .path = path, + .loader = loader, + .dirname_fd = 0, + .file_descriptor = fd, + .file_hash = hash, + .macro_remappings = macro_remappings, + .jsx = jsc_vm.bundler.options.jsx, + .virtual_source = virtual_source, + .hoist_bun_plugin = true, + }; + + if (is_node_override) { + if (NodeFallbackModules.contentsFromPath(specifier)) |code| { + const fallback_path = Fs.Path.initWithNamespace(specifier, "node"); + fallback_source = logger.Source{ .path = fallback_path, .contents = code, .key_path = fallback_path }; + parse_options.virtual_source = &fallback_source; + } + } + + var parse_result = jsc_vm.bundler.parseMaybeReturnFileOnly( + parse_options, + null, + disable_transpilying, + ) orelse { + return error.ParseError; + }; + + if (comptime disable_transpilying) { + return ResolvedSource{ + .allocator = null, + .source_code = switch (comptime flags) { + .print_source_and_clone => ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), + .print_source => ZigString.init(parse_result.source.contents), + else => unreachable, + }, + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(path.text), + .hash = 0, + }; + } + + const has_bun_plugin = parse_result.ast.bun_plugin.hoisted_stmts.items.len > 0; + + if (has_bun_plugin) { + try ModuleLoader.runBunPlugin(jsc_vm, source_code_printer, &parse_result, ret); + } + + var printer = source_code_printer.*; + printer.ctx.reset(); + + const start_count = jsc_vm.bundler.linker.import_counter; + // We _must_ link because: + // - node_modules bundle won't be properly + try jsc_vm.bundler.linker.link( + path, + &parse_result, + jsc_vm.origin, + .absolute_path, + false, + true, + ); + + if (!jsc_vm.macro_mode) + jsc_vm.resolved_count += jsc_vm.bundler.linker.import_counter - start_count; + jsc_vm.bundler.linker.import_counter = 0; + + const written = brk: { + defer source_code_printer.* = printer; + break :brk try jsc_vm.bundler.printWithSourceMap( + parse_result, + @TypeOf(&printer), + &printer, + .esm_ascii, + SavedSourceMap.SourceMapHandler.init(&jsc_vm.source_mappings), + ); + }; + + if (written == 0) { + // if it's an empty file but there were plugins + // we don't want it to break if you try to import from it + if (has_bun_plugin) { + return ResolvedSource{ + .allocator = null, + .source_code = ZigString.init("// auto-generated plugin stub\nexport default undefined\n"), + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(path.text), + // // TODO: change hash to a bitfield + // .hash = 1, + + // having JSC own the memory causes crashes + .hash = 0, + }; + } + return error.PrintingErrorWriteFailed; + } + + if (jsc_vm.has_loaded) { + return jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null); + } + + if (comptime Environment.dump_source) { + try dumpSource(specifier, &printer); + } + + return ResolvedSource{ + .allocator = null, + .source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())), + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(path.text), + // // TODO: change hash to a bitfield + // .hash = 1, + + // having JSC own the memory causes crashes + .hash = 0, + }; + }, + // provideFetch() should be called + .napi => unreachable, + // .wasm => { + // jsc_vm.transpiled_count += 1; + // var fd: ?StoredFileDescriptorType = null; + + // var allocator = if (jsc_vm.has_loaded) jsc_vm.arena.allocator() else jsc_vm.allocator; + + // const hash = http.Watcher.getHash(path.text); + // if (jsc_vm.watcher) |watcher| { + // if (watcher.indexOf(hash)) |index| { + // const _fd = watcher.watchlist.items(.fd)[index]; + // fd = if (_fd > 0) _fd else null; + // } + // } + + // var parse_options = Bundler.ParseOptions{ + // .allocator = allocator, + // .path = path, + // .loader = loader, + // .dirname_fd = 0, + // .file_descriptor = fd, + // .file_hash = hash, + // .macro_remappings = MacroRemap{}, + // .jsx = jsc_vm.bundler.options.jsx, + // }; + + // var parse_result = jsc_vm.bundler.parse( + // parse_options, + // null, + // ) orelse { + // return error.ParseError; + // }; + + // return ResolvedSource{ + // .allocator = if (jsc_vm.has_loaded) &jsc_vm.allocator else null, + // .source_code = ZigString.init(jsc_vm.allocator.dupe(u8, parse_result.source.contents) catch unreachable), + // .specifier = ZigString.init(specifier), + // .source_url = ZigString.init(path.text), + // .hash = 0, + // .tag = ResolvedSource.Tag.wasm, + // }; + // }, + else => { + return ResolvedSource{ + .allocator = &jsc_vm.allocator, + .source_code = ZigString.init(try strings.quotedAlloc(jsc_vm.allocator, path.pretty)), + .specifier = ZigString.init(path.text), + .source_url = ZigString.init(path.text), + .hash = 0, + }; + }, + } + } + + pub fn runBunPlugin( + jsc_vm: *VirtualMachine, + source_code_printer: *js_printer.BufferPrinter, + parse_result: *ParseResult, + ret: *ErrorableResolvedSource, + ) !void { + var printer = source_code_printer.*; + printer.ctx.reset(); + + defer printer.ctx.reset(); + // If we start transpiling in the middle of an existing transpilation session + // we will hit undefined memory bugs + // unless we disable resetting the store until we are done transpiling + const prev_disable_reset = js_ast.Stmt.Data.Store.disable_reset; + js_ast.Stmt.Data.Store.disable_reset = true; + js_ast.Expr.Data.Store.disable_reset = true; + + // flip the source code we use + // unless we're already transpiling a plugin + // that case could happen when + const was_printing_plugin = jsc_vm.is_printing_plugin; + const prev = jsc_vm.bundler.resolver.caches.fs.use_alternate_source_cache; + jsc_vm.is_printing_plugin = true; + defer { + js_ast.Stmt.Data.Store.disable_reset = prev_disable_reset; + js_ast.Expr.Data.Store.disable_reset = prev_disable_reset; + if (!was_printing_plugin) jsc_vm.bundler.resolver.caches.fs.use_alternate_source_cache = prev; + jsc_vm.is_printing_plugin = was_printing_plugin; + } + // we flip use_alternate_source_cache + if (!was_printing_plugin) jsc_vm.bundler.resolver.caches.fs.use_alternate_source_cache = !prev; + + // this is a bad idea, but it should work for now. + const original_name = parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name; + parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name = "globalThis.Bun.plugin"; + defer { + parse_result.ast.symbols[parse_result.ast.bun_plugin.ref.innerIndex()].original_name = original_name; + } + const hoisted_stmts = parse_result.ast.bun_plugin.hoisted_stmts.items; + + var parts = [1]js_ast.Part{ + js_ast.Part{ + .stmts = hoisted_stmts, + }, + }; + var ast_copy = parse_result.ast; + ast_copy.parts = &parts; + ast_copy.prepend_part = null; + var temporary_source = parse_result.source; + var source_name = try std.fmt.allocPrint(jsc_vm.allocator, "{s}.plugin.{s}", .{ temporary_source.path.text, temporary_source.path.name.ext[1..] }); + temporary_source.path = Fs.Path.init(source_name); + + _ = brk: { + defer source_code_printer.* = printer; + break :brk try jsc_vm.bundler.printWithSourceMapMaybe( + ast_copy, + &temporary_source, + @TypeOf(&printer), + &printer, + .esm_ascii, + true, + SavedSourceMap.SourceMapHandler.init(&jsc_vm.source_mappings), + ); + }; + const wrote = printer.ctx.getWritten(); + + if (wrote.len > 0) { + if (comptime Environment.dump_source) + try dumpSource(temporary_source.path.text, &printer); + + var exception = [1]JSC.JSValue{JSC.JSValue.zero}; + _ = JSC.JSModuleLoader.evaluate( + jsc_vm.global, + wrote.ptr, + wrote.len, + temporary_source.path.text.ptr, + temporary_source.path.text.len, + JSC.JSValue.jsUndefined(), + &exception, + ); + if (!exception[0].isEmpty()) { + ret.* = JSC.ErrorableResolvedSource.err( + error.JSErrorObject, + exception[0].asVoid(), + ); + return error.PluginError; + } + } + } + pub fn normalizeSpecifier(jsc_vm: *VirtualMachine, slice_: string) string { + var slice = slice_; + if (slice.len == 0) return slice; + var was_http = false; + if (strings.hasPrefixComptime(slice, "https://")) { + slice = slice["https://".len..]; + was_http = true; + } else if (strings.hasPrefixComptime(slice, "http://")) { + slice = slice["http://".len..]; + was_http = true; + } + + if (strings.hasPrefix(slice, jsc_vm.origin.host)) { + slice = slice[jsc_vm.origin.host.len..]; + } else if (was_http) { + if (strings.indexOfChar(slice, '/')) |i| { + slice = slice[i..]; + } + } + + if (jsc_vm.origin.path.len > 1) { + if (strings.hasPrefix(slice, jsc_vm.origin.path)) { + slice = slice[jsc_vm.origin.path.len..]; + } + } + + if (jsc_vm.bundler.options.routes.asset_prefix_path.len > 0) { + if (strings.hasPrefix(slice, jsc_vm.bundler.options.routes.asset_prefix_path)) { + slice = slice[jsc_vm.bundler.options.routes.asset_prefix_path.len..]; + } + } + + return slice; + } + + pub export fn Bun__fetchBuiltinModule( + jsc_vm: *VirtualMachine, + globalObject: *JSC.JSGlobalObject, + specifier: *ZigString, + referrer: *ZigString, + ret: *ErrorableResolvedSource, + ) bool { + JSC.markBinding(); + var log = logger.Log.init(jsc_vm.bundler.allocator); + defer log.deinit(); + if (jsc_vm.fetchBuiltinModule(specifier.slice(), &log, true) catch |err| { + VirtualMachine.processFetchLog(globalObject, specifier.*, referrer.*, &log, ret, err); + return true; + }) |builtin| { + ret.* = ErrorableResolvedSource.ok(builtin); + return true; + } else { + return false; + } + } + + pub export fn Bun__transpileFile( + jsc_vm: *VirtualMachine, + globalObject: *JSC.JSGlobalObject, + specifier_ptr: *ZigString, + referrer: *ZigString, + ret: *ErrorableResolvedSource, + ) bool { + JSC.markBinding(); + var log = logger.Log.init(jsc_vm.bundler.allocator); + defer log.deinit(); + var _specifier = specifier_ptr.toSlice(jsc_vm.allocator); + defer _specifier.deinit(); + var specifier = normalizeSpecifier(jsc_vm, _specifier.slice()); + const path = Fs.Path.init(specifier); + const 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; + }; + ret.* = ErrorableResolvedSource.ok( + ModuleLoader.transpileSourceCode( + jsc_vm, + specifier, + path, + loader, + &log, + null, + ret, + VirtualMachine.source_code_printer.?, + FetchFlags.transpile, + ) catch |err| { + if (err == error.PluginERror) { + return true; + } + VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer.*, &log, ret, err); + return true; + }, + ); + return true; + } + + export fn Bun__runVirtualModule(globalObject: *JSC.JSGlobalObject, specifier_ptr: *ZigString) JSValue { + JSC.markBinding(); + if (globalObject.bunVM().plugin_runner == null) return JSValue.zero; + + const specifier = specifier_ptr.slice(); + + if (!PluginRunner.couldBePlugin(specifier)) { + return JSValue.zero; + } + + const namespace = PluginRunner.extractNamespace(specifier); + const after_namespace = if (namespace.len == 0) + specifier + else + specifier[@minimum(namespace.len + 1, specifier.len)..]; + + return globalObject.runOnLoadPlugins(ZigString.init(namespace), ZigString.init(after_namespace), .bun) orelse return JSValue.zero; + } + + export fn Bun__transpileVirtualModule( + globalObject: *JSC.JSGlobalObject, + specifier_ptr: *ZigString, + referrer_ptr: *ZigString, + source_code: *ZigString, + loader_: Api.Loader, + ret: *ErrorableResolvedSource, + ) bool { + JSC.markBinding(); + const jsc_vm = globalObject.bunVM(); + std.debug.assert(jsc_vm.plugin_runner != null); + + var specifier_slice = specifier_ptr.toSlice(jsc_vm.allocator); + const specifier = specifier_slice.slice(); + defer specifier_slice.deinit(); + var source_code_slice = source_code.toSlice(jsc_vm.allocator); + defer source_code_slice.deinit(); + + var virtual_source = logger.Source.initPathString(specifier, source_code_slice.slice()); + var log = logger.Log.init(jsc_vm.allocator); + const path = Fs.Path.init(specifier); + + const loader = if (loader_ != ._none) + options.Loader.fromString(@tagName(loader_)).? + else + 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; + }; + + defer log.deinit(); + ret.* = ErrorableResolvedSource.ok( + ModuleLoader.transpileSourceCode( + jsc_vm, + specifier, + path, + options.Loader.fromString(@tagName(loader)).?, + &log, + &virtual_source, + ret, + VirtualMachine.source_code_printer.?, + FetchFlags.transpile, + ) catch |err| { + if (err == error.PluginError) { + return true; + } + VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer_ptr.*, &log, ret, err); + return true; + }, + ); + return true; + } + + comptime { + _ = Bun__transpileVirtualModule; + _ = Bun__runVirtualModule; + _ = Bun__transpileFile; + _ = Bun__fetchBuiltinModule; + } +}; + +const FetchFlags = enum { + transpile, + print_source, + print_source_and_clone, + + pub fn disableTranspiling(this: FetchFlags) bool { + return this != .transpile; + } +}; diff --git a/src/bun.js/modules/BufferModule.h b/src/bun.js/modules/BufferModule.h index 9032f0d2e..8a9bf8c27 100644 --- a/src/bun.js/modules/BufferModule.h +++ b/src/bun.js/modules/BufferModule.h @@ -1,33 +1,46 @@ -#include "../bindings/ZigGlobalObject.h" #include "../bindings/JSBuffer.h" +#include "../bindings/ZigGlobalObject.h" #include "JavaScriptCore/JSGlobalObject.h" namespace Zig { - -inline void generateBufferSourceCode(JSC::JSGlobalObject* lexicalGlobalObject, JSC::Identifier moduleKey, Vector<JSC::Identifier, 4>& exportNames, JSC::MarkedArgumentBuffer& exportValues) { - JSC::VM& vm = lexicalGlobalObject->vm(); - GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject); - - exportNames.append(JSC::Identifier::fromString(vm, "Buffer"_s)); - exportValues.append(WebCore::JSBuffer::getConstructor(vm, globalObject)); - - auto* slowBuffer = JSC::JSFunction::create(vm, globalObject, 0, "SlowBuffer"_s, WebCore::constructSlowBuffer, ImplementationVisibility::Public, NoIntrinsic, WebCore::constructSlowBuffer); - slowBuffer->putDirect(vm, vm.propertyNames->prototype, WebCore::JSBuffer::prototype(vm, *jsCast<JSDOMGlobalObject*>(lexicalGlobalObject)), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); - exportNames.append(JSC::Identifier::fromString(vm, "SlowBuffer"_s)); - exportValues.append(slowBuffer); - - // substitute after JSBlob is implemented. - exportNames.append(JSC::Identifier::fromString(vm, "Blob"_s)); - exportValues.append(JSC::jsUndefined()); - - exportNames.append(JSC::Identifier::fromString(vm, "INSPECT_MAX_BYTES"_s)); - exportValues.append(JSC::jsNumber(50)); - - exportNames.append(JSC::Identifier::fromString(vm, "kMaxLength"_s)); - exportValues.append(JSC::jsNumber(4294967296LL)); - - exportNames.append(JSC::Identifier::fromString(vm, "kMaxLength"_s)); - exportValues.append(JSC::jsNumber(536870888)); +using namespace WebCore; + +inline void generateBufferSourceCode(JSC::JSGlobalObject *lexicalGlobalObject, + JSC::Identifier moduleKey, + Vector<JSC::Identifier, 4> &exportNames, + JSC::MarkedArgumentBuffer &exportValues) { + JSC::VM &vm = lexicalGlobalObject->vm(); + GlobalObject *globalObject = + reinterpret_cast<GlobalObject *>(lexicalGlobalObject); + + exportNames.append(JSC::Identifier::fromString(vm, "Buffer"_s)); + exportValues.append(WebCore::JSBuffer::getConstructor(vm, globalObject)); + + auto *slowBuffer = JSC::JSFunction::create( + vm, globalObject, 0, "SlowBuffer"_s, WebCore::constructSlowBuffer, + ImplementationVisibility::Public, NoIntrinsic, + WebCore::constructSlowBuffer); + slowBuffer->putDirect( + vm, vm.propertyNames->prototype, + WebCore::JSBuffer::prototype( + vm, *jsCast<JSDOMGlobalObject *>(lexicalGlobalObject)), + JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | + JSC::PropertyAttribute::DontDelete); + exportNames.append(JSC::Identifier::fromString(vm, "SlowBuffer"_s)); + exportValues.append(slowBuffer); + + // substitute after JSBlob is implemented. + exportNames.append(JSC::Identifier::fromString(vm, "Blob"_s)); + exportValues.append(JSC::jsUndefined()); + + exportNames.append(JSC::Identifier::fromString(vm, "INSPECT_MAX_BYTES"_s)); + exportValues.append(JSC::jsNumber(50)); + + exportNames.append(JSC::Identifier::fromString(vm, "kMaxLength"_s)); + exportValues.append(JSC::jsNumber(4294967296LL)); + + exportNames.append(JSC::Identifier::fromString(vm, "kMaxLength"_s)); + exportValues.append(JSC::jsNumber(536870888)); } -} +} // namespace Zig diff --git a/src/bun.js/modules/EventsModule.h b/src/bun.js/modules/EventsModule.h index 5adb19d01..d1e14a2db 100644 --- a/src/bun.js/modules/EventsModule.h +++ b/src/bun.js/modules/EventsModule.h @@ -1,27 +1,36 @@ -#include "../bindings/ZigGlobalObject.h" #include "JavaScriptCore/JSGlobalObject.h" +#include "ZigGlobalObject.h" namespace Zig { +using namespace WebCore; +inline void generateEventsSourceCode(JSC::JSGlobalObject *lexicalGlobalObject, + JSC::Identifier moduleKey, + Vector<JSC::Identifier, 4> &exportNames, + JSC::MarkedArgumentBuffer &exportValues) { + JSC::VM &vm = lexicalGlobalObject->vm(); + GlobalObject *globalObject = + reinterpret_cast<GlobalObject *>(lexicalGlobalObject); -inline void generateEventsSourceCode(JSC::JSGlobalObject* lexicalGlobalObject, JSC::Identifier moduleKey, Vector<JSC::Identifier, 4>& exportNames, JSC::MarkedArgumentBuffer& exportValues) { - JSC::VM& vm = lexicalGlobalObject->vm(); - GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject); + exportNames.append(JSC::Identifier::fromString(vm, "EventEmitter"_s)); + exportValues.append( + WebCore::JSEventEmitter::getConstructor(vm, globalObject)); - exportNames.append(JSC::Identifier::fromString(vm, "EventEmitter"_s)); - exportValues.append(WebCore::JSEventEmitter::getConstructor(vm, globalObject)); - - exportNames.append(JSC::Identifier::fromString(vm, "getEventListeners"_s)); - exportValues.append(JSC::JSFunction::create(vm, lexicalGlobalObject, 0, - MAKE_STATIC_STRING_IMPL("getEventListeners"), Events_functionGetEventListeners, ImplementationVisibility::Public)); - exportNames.append(JSC::Identifier::fromString(vm, "listenerCount"_s)); - exportValues.append(JSC::JSFunction::create(vm, lexicalGlobalObject, 0, - MAKE_STATIC_STRING_IMPL("listenerCount"), Events_functionListenerCount, ImplementationVisibility::Public)); - exportNames.append(JSC::Identifier::fromString(vm, "once"_s)); - exportValues.append(JSC::JSFunction::create(vm, lexicalGlobalObject, 0, - MAKE_STATIC_STRING_IMPL("once"), Events_functionOnce, ImplementationVisibility::Public)); - exportNames.append(JSC::Identifier::fromString(vm, "on"_s)); - exportValues.append(JSC::JSFunction::create(vm, lexicalGlobalObject, 0, - MAKE_STATIC_STRING_IMPL("on"), Events_functionOn, ImplementationVisibility::Public)); + exportNames.append(JSC::Identifier::fromString(vm, "getEventListeners"_s)); + exportValues.append(JSC::JSFunction::create( + vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("getEventListeners"), + Events_functionGetEventListeners, ImplementationVisibility::Public)); + exportNames.append(JSC::Identifier::fromString(vm, "listenerCount"_s)); + exportValues.append(JSC::JSFunction::create( + vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("listenerCount"), + Events_functionListenerCount, ImplementationVisibility::Public)); + exportNames.append(JSC::Identifier::fromString(vm, "once"_s)); + exportValues.append(JSC::JSFunction::create( + vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("once"), + Events_functionOnce, ImplementationVisibility::Public)); + exportNames.append(JSC::Identifier::fromString(vm, "on"_s)); + exportValues.append(JSC::JSFunction::create( + vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("on"), + Events_functionOn, ImplementationVisibility::Public)); } -} +} // namespace Zig diff --git a/src/bundler.zig b/src/bundler.zig index c5b593507..86223ea60 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -859,7 +859,8 @@ pub const Bundler = struct { return BuildResolveResultPair{ .written = switch (result.ast.exports_kind) { .esm => try bundler.printWithSourceMapMaybe( - result, + result.ast, + &result.source, Writer, writer, .esm_ascii, @@ -867,7 +868,8 @@ pub const Bundler = struct { source_map_handler, ), .cjs => try bundler.printWithSourceMapMaybe( - result, + result.ast, + &result.source, Writer, writer, .cjs_ascii, @@ -885,7 +887,8 @@ pub const Bundler = struct { return BuildResolveResultPair{ .written = switch (result.ast.exports_kind) { .none, .esm => try bundler.printWithSourceMapMaybe( - result, + result.ast, + &result.source, Writer, writer, .esm, @@ -893,7 +896,8 @@ pub const Bundler = struct { source_map_handler, ), .cjs => try bundler.printWithSourceMapMaybe( - result, + result.ast, + &result.source, Writer, writer, .cjs, @@ -1101,14 +1105,14 @@ pub const Bundler = struct { pub fn printWithSourceMapMaybe( bundler: *ThisBundler, - result: ParseResult, + ast: js_ast.Ast, + source: *const logger.Source, comptime Writer: type, writer: Writer, comptime format: js_printer.Format, comptime enable_source_map: bool, source_map_context: ?js_printer.SourceMapHandler, ) !usize { - const ast = result.ast; var symbols: [][]js_ast.Symbol = &([_][]js_ast.Symbol{ast.symbols}); return switch (format) { @@ -1117,7 +1121,7 @@ pub const Bundler = struct { writer, ast, js_ast.Symbol.Map.initList(symbols), - &result.source, + source, false, js_printer.Options{ .to_module_ref = Ref.RuntimeRef, @@ -1138,7 +1142,7 @@ pub const Bundler = struct { writer, ast, js_ast.Symbol.Map.initList(symbols), - &result.source, + source, false, js_printer.Options{ .to_module_ref = Ref.RuntimeRef, @@ -1159,7 +1163,7 @@ pub const Bundler = struct { writer, ast, js_ast.Symbol.Map.initList(symbols), - &result.source, + source, true, js_printer.Options{ .to_module_ref = Ref.RuntimeRef, @@ -1180,7 +1184,7 @@ pub const Bundler = struct { writer, ast, js_ast.Symbol.Map.initList(symbols), - &result.source, + source, false, js_printer.Options{ .to_module_ref = Ref.RuntimeRef, @@ -1201,7 +1205,7 @@ pub const Bundler = struct { writer, ast, js_ast.Symbol.Map.initList(symbols), - &result.source, + source, true, js_printer.Options{ .to_module_ref = Ref.RuntimeRef, @@ -1222,7 +1226,7 @@ pub const Bundler = struct { writer, ast, js_ast.Symbol.Map.initList(symbols), - &result.source, + source, false, js_printer.Options{ .to_module_ref = Ref.RuntimeRef, @@ -1248,7 +1252,8 @@ pub const Bundler = struct { comptime format: js_printer.Format, ) !usize { return bundler.printWithSourceMapMaybe( - result, + result.ast, + &result.source, Writer, writer, format, @@ -1266,7 +1271,8 @@ pub const Bundler = struct { handler: js_printer.SourceMapHandler, ) !usize { return bundler.printWithSourceMapMaybe( - result, + result.ast, + &result.source, Writer, writer, format, @@ -1287,6 +1293,7 @@ pub const Bundler = struct { macro_js_ctx: MacroJSValueType = default_macro_js_value, virtual_source: ?*const logger.Source = null, replace_exports: runtime.Runtime.Features.ReplaceableExport.Map = .{}, + hoist_bun_plugin: bool = false, }; pub fn parse( @@ -1403,7 +1410,7 @@ pub const Bundler = struct { (jsx.runtime == .automatic or jsx.runtime == .classic); opts.features.jsx_optimization_hoist = bundler.options.jsx_optimization_hoist orelse opts.features.jsx_optimization_inline; - + opts.features.hoist_bun_plugin = this_parse.hoist_bun_plugin; if (bundler.macro_context == null) { bundler.macro_context = js_ast.Macro.MacroContext.init(bundler); } diff --git a/src/env.zig b/src/env.zig index 904c80006..d168865ab 100644 --- a/src/env.zig +++ b/src/env.zig @@ -36,3 +36,4 @@ pub const baseline = BuildOptions.baseline; pub const enableSIMD: bool = !baseline; pub const git_sha = BuildOptions.sha; pub const is_canary = BuildOptions.is_canary; +pub const dump_source = isDebug and !isTest; diff --git a/src/fs.zig b/src/fs.zig index e54c7e0e6..59f162609 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -286,7 +286,7 @@ pub const FileSystem = struct { var scratch_lookup_buffer: [256]u8 = undefined; std.debug.assert(scratch_lookup_buffer.len >= _query.len); - const query = strings.copyLowercase(_query, &scratch_lookup_buffer); + const query = strings.copyLowercaseIfNeeded(_query, &scratch_lookup_buffer); const result = entry.data.get(query) orelse return null; const basename = result.base(); if (!strings.eql(basename, _query)) { @@ -810,7 +810,7 @@ pub const FileSystem = struct { // This custom map implementation: // - Preallocates a fixed amount of directory name space // - Doesn't store directory names which don't exist. - pub const Map = allocators.BSSMap(EntriesOption, Preallocate.Counts.dir_entry, false, 128, true); + pub const Map = allocators.BSSMap(EntriesOption, Preallocate.Counts.dir_entry, false, 256, true); }; pub fn openDir(_: *RealFS, unsafe_dir_string: string) std.fs.File.OpenError!std.fs.Dir { diff --git a/src/js_ast.zig b/src/js_ast.zig index 7ff8476ac..30ebffaea 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -4391,7 +4391,10 @@ pub const Ast = struct { wrapper_ref: ?Ref = null, require_ref: Ref = Ref.None, + bun_plugin: BunPlugin = .{}, + bundle_namespace_ref: ?Ref = null, + prepend_part: ?Part = null, // These are used when bundling. They are filled in during the parser pass @@ -4520,6 +4523,7 @@ pub const Part = struct { cjs_imports, react_fast_refresh, dirname_filename, + bun_plugin, }; pub const SymbolUseMap = std.ArrayHashMapUnmanaged(Ref, Symbol.Use, RefHashCtx, false); @@ -4750,6 +4754,11 @@ pub fn printmem(comptime format: string, args: anytype) void { Output.print(format, args); } +pub const BunPlugin = struct { + ref: Ref = Ref.None, + hoisted_stmts: std.ArrayListUnmanaged(Stmt) = .{}, +}; + pub const Macro = struct { const JavaScript = @import("javascript_core"); const JSCBase = @import("./bun.js/base.zig"); diff --git a/src/js_parser.zig b/src/js_parser.zig index a69db9db4..164f915c4 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2390,9 +2390,6 @@ pub const Parser = struct { } else { // When tree shaking is enabled, each top-level statement is potentially a separate part. for (stmts) |stmt| { - // switch (stmt.data) { - - // } switch (stmt.data) { .s_local => |local| { if (local.decls.len > 1) { @@ -3899,7 +3896,7 @@ fn NewParser_( filename_ref: Ref = Ref.None, dirname_ref: Ref = Ref.None, import_meta_ref: Ref = Ref.None, - promise_ref: ?Ref = null, + bun_plugin: js_ast.BunPlugin = .{}, scopes_in_order_visitor_index: usize = 0, has_classic_runtime_warned: bool = false, macro_call_count: MacroCallCountType = 0, @@ -4334,13 +4331,12 @@ fn NewParser_( parts.* = parts_; } + const default_export_ref = + if (p.named_exports.get("default")) |default_| default_.ref else Ref.None; + while (parts_.len > 1) { var parts_end: usize = 0; var last_end = parts_.len; - var default_export_ref = Ref.None; - if (p.named_exports.get("default")) |named| { - default_export_ref = named.ref; - } for (parts_) |part| { const is_dead = part.can_be_removed_if_unused and can_remove_part: { @@ -6297,6 +6293,38 @@ fn NewParser_( return p.s(S.Empty{}, loc); } + if (p.options.features.hoist_bun_plugin and strings.eqlComptime(path.text, "bun")) { + var plugin_i: usize = std.math.maxInt(usize); + const items = stmt.items; + for (items) |item, i| { + // Mark Bun.plugin() + // TODO: remove if they have multiple imports of the same name? + if (strings.eqlComptime(item.alias, "plugin")) { + const name = p.loadNameFromRef(item.name.ref.?); + const ref = try p.declareSymbol(.other, item.name.loc, name); + try p.is_import_item.put(p.allocator, ref, .{}); + p.bun_plugin.ref = ref; + plugin_i = i; + break; + } + } + + if (plugin_i != std.math.maxInt(usize)) { + var list = std.ArrayListUnmanaged(@TypeOf(stmt.items[0])){ + .items = stmt.items, + .capacity = stmt.items.len, + }; + // remove it from the list + _ = list.swapRemove(plugin_i); + stmt.items = list.items; + } + + // if the import statement is now empty, remove it completely + if (stmt.items.len == 0 and stmt.default_name == null and stmt.star_name_loc == null) { + return p.s(S.Empty{}, loc); + } + } + const macro_remap = if ((comptime allow_macros) and !is_macro) p.options.macro_context.getRemap(path.text) else @@ -12409,6 +12437,13 @@ fn NewParser_( const allocator = p.allocator; var opts = PrependTempRefsOpts{}; var partStmts = ListManaged(Stmt).fromOwnedSlice(allocator, stmts); + + // + const bun_plugin_usage_count_before: usize = if (p.options.features.hoist_bun_plugin and !p.bun_plugin.ref.isNull()) + p.symbols.items[p.bun_plugin.ref.innerIndex()].use_count_estimate + else + 0; + try p.visitStmtsAndPrependTempRefs(&partStmts, &opts); // Insert any relocated variable statements now @@ -12449,6 +12484,22 @@ fn NewParser_( if (partStmts.items.len > 0) { const _stmts = partStmts.toOwnedSlice(); + // -- hoist_bun_plugin -- + if (_stmts.len == 1 and p.options.features.hoist_bun_plugin and !p.bun_plugin.ref.isNull()) { + const bun_plugin_usage_count_after: usize = p.symbols.items[p.bun_plugin.ref.innerIndex()].use_count_estimate; + if (bun_plugin_usage_count_after > bun_plugin_usage_count_before) { + // Single-statement part which uses Bun.plugin() + // It's effectively an unrelated file + if (p.declared_symbols.items.len > 0 or p.symbol_uses.count() > 0) { + p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols.items, .symbol_uses = p.symbol_uses }); + } + + p.bun_plugin.hoisted_stmts.append(p.allocator, _stmts[0]) catch unreachable; + return; + } + } + // -- hoist_bun_plugin -- + try parts.append(js_ast.Part{ .stmts = _stmts, .symbol_uses = p.symbol_uses, @@ -18730,6 +18781,7 @@ fn NewParser_( else false, // .top_Level_await_keyword = p.top_level_await_keyword, + .bun_plugin = p.bun_plugin, }; } diff --git a/src/runtime.zig b/src/runtime.zig index bf589c54f..1a490e4d2 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -322,6 +322,8 @@ pub const Runtime = struct { replace_exports: ReplaceableExport.Map = .{}, + hoist_bun_plugin: bool = false, + pub const ReplaceableExport = union(enum) { delete: void, replace: JSAst.Expr, diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 48d591eff..acf9d057c 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -324,6 +324,43 @@ pub fn copyLowercase(in: string, out: []u8) string { return out[0..in.len]; } +pub fn copyLowercaseIfNeeded(in: string, out: []u8) string { + var in_slice: string = in; + var out_slice: []u8 = out[0..in.len]; + var any = false; + + begin: while (out_slice.len > 0) { + for (in_slice) |c, i| { + switch (c) { + 'A'...'Z' => { + @memcpy(out_slice.ptr, in_slice.ptr, i); + out_slice[i] = std.ascii.toLower(c); + const end = i + 1; + if (end >= out_slice.len) break :begin; + in_slice = in_slice[end..]; + out_slice = out_slice[end..]; + any = true; + continue :begin; + }, + else => {}, + } + } + + if (!any) { + return in; + } + + @memcpy(out_slice.ptr, in_slice.ptr, in_slice.len); + break :begin; + } + + if (!any) { + return in; + } + + return out[0..in.len]; +} + test "indexOf" { const fixtures = .{ .{ |