JSValue fetchCommonJSModule()

in src/bun.js/bindings/ModuleLoader.cpp [460:653]


JSValue fetchCommonJSModule(
    Zig::GlobalObject* globalObject,
    JSCommonJSModule* target,
    JSValue specifierValue,
    BunString* specifier,
    BunString* referrer,
    BunString* typeAttribute)
{
    void* bunVM = globalObject->bunVM();
    auto& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);
    ErrorableResolvedSource resValue;
    memset(&resValue, 0, sizeof(ErrorableResolvedSource));

    ErrorableResolvedSource* res = &resValue;
    ResolvedSourceCodeHolder sourceCodeHolder(res);
    auto& builtinNames = WebCore::clientData(vm)->builtinNames();

    bool wasModuleMock = false;

    // When "bun test" is enabled, allow users to override builtin modules
    // This is important for being able to trivially mock things like the filesystem.
    if (isBunTest) {
        if (JSC::JSValue virtualModuleResult = Bun::runVirtualModule(globalObject, specifier, wasModuleMock)) {
            JSPromise* promise = jsCast<JSPromise*>(handleVirtualModuleResult<true>(globalObject, virtualModuleResult, res, specifier, referrer, wasModuleMock));
            switch (promise->status(vm)) {
            case JSPromise::Status::Rejected: {
                uint32_t promiseFlags = promise->internalField(JSPromise::Field::Flags).get().asUInt32AsAnyInt();
                promise->internalField(JSPromise::Field::Flags).set(vm, promise, jsNumber(promiseFlags | JSPromise::isHandledFlag));
                JSC::throwException(globalObject, scope, promise->result(vm));
                RELEASE_AND_RETURN(scope, JSValue {});
            }
            case JSPromise::Status::Pending: {
                JSC::throwTypeError(globalObject, scope, makeString("require() async module \""_s, specifier->toWTFString(BunString::ZeroCopy), "\" is unsupported. use \"await import()\" instead."_s));
                RELEASE_AND_RETURN(scope, JSValue {});
            }
            case JSPromise::Status::Fulfilled: {
                if (!res->success) {
                    throwException(scope, res->result.err, globalObject);
                    RELEASE_AND_RETURN(scope, {});
                }
                if (!wasModuleMock) {
                    auto* jsSourceCode = jsCast<JSSourceCode*>(promise->result(vm));
                    globalObject->moduleLoader()->provideFetch(globalObject, specifierValue, jsSourceCode->sourceCode());
                    RETURN_IF_EXCEPTION(scope, {});
                }
                RELEASE_AND_RETURN(scope, jsNumber(-1));
            }
            }
        }
    }

    if (Bun__fetchBuiltinModule(bunVM, globalObject, specifier, referrer, res)) {
        if (!res->success) {
            throwException(scope, res->result.err, globalObject);
            return JSValue();
        }

        auto tag = res->result.value.tag;
        switch (tag) {
// Generated native module cases
#define CASE(str, name)                                                                                           \
    case SyntheticModuleType::name: {                                                                             \
        target->evaluate(globalObject, specifier->toWTFString(BunString::ZeroCopy), generateNativeModule_##name); \
        RETURN_IF_EXCEPTION(scope, {});                                                                           \
        RELEASE_AND_RETURN(scope, target);                                                                        \
    }
            BUN_FOREACH_NATIVE_MODULE(CASE)
#undef CASE

        case SyntheticModuleType::ESM: {
            RELEASE_AND_RETURN(scope, jsNumber(-1));
        }

        default: {
            if (tag & SyntheticModuleType::InternalModuleRegistryFlag) {
                constexpr auto mask = (SyntheticModuleType::InternalModuleRegistryFlag - 1);
                auto result = globalObject->internalModuleRegistry()->requireId(globalObject, vm, static_cast<InternalModuleRegistry::Field>(tag & mask));
                RETURN_IF_EXCEPTION(scope, {});

                target->putDirect(
                    vm,
                    builtinNames.exportsPublicName(),
                    result,
                    JSC::PropertyAttribute::ReadOnly | 0);
                RELEASE_AND_RETURN(scope, target);
            } else {
                RELEASE_AND_RETURN(scope, jsNumber(-1));
            }
        }
        }
    }

    // When "bun test" is NOT enabled, disable users from overriding builtin modules
    if (!isBunTest) {
        if (JSC::JSValue virtualModuleResult = Bun::runVirtualModule(globalObject, specifier, wasModuleMock)) {
            JSPromise* promise = jsCast<JSPromise*>(handleVirtualModuleResult<true>(globalObject, virtualModuleResult, res, specifier, referrer, wasModuleMock));
            switch (promise->status(vm)) {
            case JSPromise::Status::Rejected: {
                uint32_t promiseFlags = promise->internalField(JSPromise::Field::Flags).get().asUInt32AsAnyInt();
                promise->internalField(JSPromise::Field::Flags).set(vm, promise, jsNumber(promiseFlags | JSPromise::isHandledFlag));
                JSC::throwException(globalObject, scope, promise->result(vm));
                RELEASE_AND_RETURN(scope, JSValue {});
            }
            case JSPromise::Status::Pending: {
                JSC::throwTypeError(globalObject, scope, makeString("require() async module \""_s, specifier->toWTFString(BunString::ZeroCopy), "\" is unsupported. use \"await import()\" instead."_s));
                RELEASE_AND_RETURN(scope, JSValue {});
            }
            case JSPromise::Status::Fulfilled: {
                if (!res->success) {
                    throwException(scope, res->result.err, globalObject);
                    RELEASE_AND_RETURN(scope, {});
                }
                if (!wasModuleMock) {
                    auto* jsSourceCode = jsCast<JSSourceCode*>(promise->result(vm));
                    globalObject->moduleLoader()->provideFetch(globalObject, specifierValue, jsSourceCode->sourceCode());
                    RETURN_IF_EXCEPTION(scope, {});
                }
                RELEASE_AND_RETURN(scope, jsNumber(-1));
            }
            }
        }
    }

    JSMap* registry = globalObject->esmRegistryMap();

    const auto hasAlreadyLoadedESMVersionSoWeShouldntTranspileItTwice = [&]() -> bool {
        JSValue entry = registry->get(globalObject, specifierValue);

        if (!entry || !entry.isObject()) {
            return false;
        }

        int status = entry.getObject()->getDirect(vm, WebCore::clientData(vm)->builtinNames().statePublicName()).asInt32();
        return status > JSModuleLoader::Status::Fetch;
    };

    if (hasAlreadyLoadedESMVersionSoWeShouldntTranspileItTwice()) {
        RELEASE_AND_RETURN(scope, jsNumber(-1));
    }

    Bun__transpileFile(bunVM, globalObject, specifier, referrer, typeAttribute, res, false);
    if (res->success && res->result.value.isCommonJSModule) {
        target->evaluate(globalObject, specifier->toWTFString(BunString::ZeroCopy), res->result.value);
        RETURN_IF_EXCEPTION(scope, {});
        RELEASE_AND_RETURN(scope, target);
    }

    if (!res->success) {
        throwException(scope, res->result.err, globalObject);
        RELEASE_AND_RETURN(scope, {});
    }

    // The JSONForObjectLoader tag is source code returned from Bun that needs
    // to go through the JSON parser in JSC.
    //
    // We don't use JSON.parse directly in JS because we want the top-level keys of the JSON
    // object to be accessible as named imports.
    //
    // We don't use Bun's JSON parser because JSON.parse is faster and
    // handles stack overflow better.
    //
    // When parsing tsconfig.*.json or jsconfig.*.json, we go through Bun's JSON
    // parser instead to support comments and trailing commas.
    if (res->result.value.tag == SyntheticModuleType::JSONForObjectLoader) {
        JSC::JSValue value = JSC::JSONParse(globalObject, res->result.value.source_code.toWTFString(BunString::ZeroCopy));
        if (!value) {
            JSC::throwException(globalObject, scope, JSC::createSyntaxError(globalObject, "Failed to parse JSON"_s));
            RELEASE_AND_RETURN(scope, {});
        }

        target->putDirect(vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), value, 0);
        target->hasEvaluated = true;
        RELEASE_AND_RETURN(scope, target);

    }
    // TOML and JSONC may go through here
    else if (res->result.value.tag == SyntheticModuleType::ExportsObject) {
        JSC::JSValue value = JSC::JSValue::decode(res->result.value.jsvalue_for_export);
        if (!value) {
            JSC::throwException(globalObject, scope, JSC::createSyntaxError(globalObject, "Failed to parse Object"_s));
            RELEASE_AND_RETURN(scope, {});
        }

        target->putDirect(vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), value, 0);
        target->hasEvaluated = true;
        RELEASE_AND_RETURN(scope, target);
    }

    auto&& provider = Zig::SourceProvider::create(globalObject, res->result.value);
    globalObject->moduleLoader()->provideFetch(globalObject, specifierValue, JSC::SourceCode(provider));
    RETURN_IF_EXCEPTION(scope, {});
    RELEASE_AND_RETURN(scope, jsNumber(-1));
}