From c821a4d14abf7ffdbc5214da4161cf420539d52c Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Tue, 16 Jan 2024 13:33:47 -0800 Subject: [PATCH] Add support for custom Module._extensions --- src/bun.js/bindings/CommonJSModuleRecord.cpp | 489 ++++++++++++------ src/bun.js/bindings/CommonJSModuleRecord.h | 17 +- src/bun.js/bindings/ImportMetaObject.cpp | 327 ++++-------- src/bun.js/bindings/ImportMetaObject.h | 6 - src/bun.js/bindings/ZigGlobalObject.cpp | 30 +- src/bun.js/bindings/ZigGlobalObject.h | 6 +- src/bun.js/modules/NodeModuleModule.h | 62 ++- src/codegen/replacements.ts | 9 +- src/js/builtins.d.ts | 7 + src/js/builtins/BunBuiltinNames.h | 2 + src/js/builtins/Module.ts | 54 +- .../module/moduleExtensionsChange-fixture.cjs | 1 + .../js/node/module/moduleExtensionsChange.cjs | 51 ++ .../node/module/modulePrototypeOverwrite.cjs | 32 +- .../js/node/module/node-module-module.test.js | 5 +- .../node/module/resolveFilenameOverwrite.cjs | 39 +- 16 files changed, 607 insertions(+), 530 deletions(-) create mode 100644 test/js/node/module/moduleExtensionsChange-fixture.cjs create mode 100644 test/js/node/module/moduleExtensionsChange.cjs diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index cdcb36451d..2bd5d69990 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -68,11 +68,13 @@ #include extern "C" bool Bun__isBunMain(JSC::JSGlobalObject* global, const BunString*); +extern "C" JSC__JSValue Bun__Path__basename(JSC__JSGlobalObject* arg0, bool arg1, JSC__JSValue* arg2, uint16_t arg3); +extern "C" JSC__JSValue Bun__Path__dirname(JSC__JSGlobalObject* arg0, bool arg1, JSC__JSValue* arg2, uint16_t arg3); namespace Bun { using namespace JSC; -JSC_DECLARE_HOST_FUNCTION(jsFunctionRequireCommonJS); +JSC_DECLARE_HOST_FUNCTION(jsFunctionRequirePrivate); static bool canPerformFastEnumeration(Structure* s) { @@ -103,19 +105,23 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj return false; } + auto clientData = WebCore::clientData(vm); + auto builtinNames = clientData->builtinNames(); + auto globalString = globalObject->commonStrings().resolveString(globalObject); + JSFunction* resolveFunction = JSC::JSBoundFunction::create(vm, globalObject, globalObject->requireResolveFunctionUnbound(), moduleObject->id(), - ArgList(), 1, globalObject->commonStrings().resolveString(globalObject)); + ArgList(), 1, globalString); JSFunction* requireFunction = JSC::JSBoundFunction::create(vm, globalObject, globalObject->requireFunctionUnbound(), moduleObject, - ArgList(), 1, globalObject->commonStrings().requireString(globalObject)); + ArgList(), 1, globalString); requireFunction->putDirect(vm, vm.propertyNames->resolve, resolveFunction, 0); - moduleObject->putDirect(vm, WebCore::clientData(vm)->builtinNames().requirePublicName(), requireFunction, 0); + moduleObject->putDirect(vm, builtinNames.requirePublicName(), requireFunction, 0); moduleObject->hasEvaluated = true; // This will return 0 if there was a syntax error or an allocation failure @@ -148,11 +154,11 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj return exception.get() == nullptr; } -JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe)) +JSC_DEFINE_HOST_FUNCTION(jsFunctionEvaluateCommonJSModule, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto* globalObject = jsCast(lexicalGlobalObject); auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSCommonJSModule* moduleObject = jsDynamicCast(callframe->argument(0)); + JSCommonJSModule* moduleObject = jsDynamicCast(callFrame->argument(0)); if (!moduleObject) { RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true))); } @@ -184,18 +190,18 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalOb RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true))); } -JSC_DEFINE_HOST_FUNCTION(requireResolvePathsFunction, (JSGlobalObject * globalObject, CallFrame* callframe)) +JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireResolvePaths, (JSGlobalObject * globalObject, CallFrame* callFrame)) { return JSValue::encode(JSC::constructEmptyArray(globalObject, nullptr, 0)); } -JSC_DEFINE_CUSTOM_GETTER(jsRequireCacheGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +JSC_DEFINE_CUSTOM_GETTER(requireCacheGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { Zig::GlobalObject* thisObject = jsCast(globalObject); return JSValue::encode(thisObject->lazyRequireCacheObject()); } -JSC_DEFINE_CUSTOM_SETTER(jsRequireCacheSetter, +JSC_DEFINE_CUSTOM_SETTER(requireCacheSetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) { @@ -208,11 +214,11 @@ JSC_DEFINE_CUSTOM_SETTER(jsRequireCacheSetter, } static const HashTableValue RequireResolveFunctionPrototypeValues[] = { - { "paths"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, requireResolvePathsFunction, 1 } }, + { "paths"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionRequireResolvePaths, 1 } }, }; static const HashTableValue RequireFunctionPrototypeValues[] = { - { "cache"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsRequireCacheGetter, jsRequireCacheSetter } }, + { "cache"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, requireCacheGetter, requireCacheSetter } }, }; Structure* RequireFunctionPrototype::createStructure( @@ -236,7 +242,6 @@ Structure* RequireResolveFunctionPrototype::createStructure( RequireResolveFunctionPrototype* RequireResolveFunctionPrototype::create(JSC::JSGlobalObject* globalObject) { auto& vm = globalObject->vm(); - auto* structure = RequireResolveFunctionPrototype::createStructure(vm, globalObject); RequireResolveFunctionPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) RequireResolveFunctionPrototype(vm, structure); prototype->finishCreation(vm); @@ -247,13 +252,10 @@ RequireFunctionPrototype* RequireFunctionPrototype::create( JSC::JSGlobalObject* globalObject) { auto& vm = globalObject->vm(); - auto* structure = RequireFunctionPrototype::createStructure(vm, globalObject); RequireFunctionPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) RequireFunctionPrototype(vm, structure); prototype->finishCreation(vm); - prototype->putDirect(vm, builtinNames(vm).resolvePublicName(), jsCast(globalObject)->requireResolveFunctionUnbound(), 0); - return prototype; } @@ -263,26 +265,34 @@ void RequireFunctionPrototype::finishCreation(JSC::VM& vm) ASSERT(inherits(info())); reifyStaticProperties(vm, info(), RequireFunctionPrototypeValues, *this); + + auto clientData = WebCore::clientData(vm); + auto* globalObject = reinterpret_cast(this->globalObject()); + auto builtinNames = clientData->builtinNames(); + JSC::JSFunction* requireDotMainFunction = JSFunction::create( vm, moduleMainCodeGenerator(vm), - globalObject()->globalScope()); + globalObject->globalScope()); this->putDirectAccessor( - globalObject(), + globalObject, JSC::Identifier::fromString(vm, "main"_s), - JSC::GetterSetter::create(vm, globalObject(), requireDotMainFunction, requireDotMainFunction), + JSC::GetterSetter::create(vm, globalObject, requireDotMainFunction, requireDotMainFunction), PropertyAttribute::Accessor | PropertyAttribute::ReadOnly | 0); - auto extensions = constructEmptyObject(globalObject()); + this->putDirect(vm, builtinNames.resolvePublicName(), globalObject->requireResolveFunctionUnbound(), 0); + + auto extensions = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure()); extensions->putDirect(vm, JSC::Identifier::fromString(vm, ".js"_s), jsBoolean(true), 0); extensions->putDirect(vm, JSC::Identifier::fromString(vm, ".json"_s), jsBoolean(true), 0); extensions->putDirect(vm, JSC::Identifier::fromString(vm, ".node"_s), jsBoolean(true), 0); - + extensions->putDirect(vm, builtinNames.originalStructureIDPrivateName(), jsNumber(0), 0); + extensions->putDirect(vm, builtinNames.originalStructureIDPrivateName(), jsNumber(extensions->structureID().bits()), 0); this->putDirect(vm, JSC::Identifier::fromString(vm, "extensions"_s), extensions, 0); } -JSC_DEFINE_CUSTOM_GETTER(getterFilename, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +JSC_DEFINE_CUSTOM_GETTER(filenameGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { @@ -290,7 +300,20 @@ JSC_DEFINE_CUSTOM_GETTER(getterFilename, (JSC::JSGlobalObject * globalObject, JS } return JSValue::encode(thisObject->m_filename.get()); } -JSC_DEFINE_CUSTOM_GETTER(getterId, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) + +JSC_DEFINE_CUSTOM_SETTER(filenameSetter, + (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, + JSC::EncodedJSValue value, JSC::PropertyName propertyName)) +{ + JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!thisObject) + return false; + + thisObject->m_filename.set(globalObject->vm(), thisObject, JSValue::decode(value).toString(globalObject)); + return true; +} + +JSC_DEFINE_CUSTOM_GETTER(idGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { @@ -299,16 +322,42 @@ JSC_DEFINE_CUSTOM_GETTER(getterId, (JSC::JSGlobalObject * globalObject, JSC::Enc return JSValue::encode(thisObject->m_id.get()); } -JSC_DEFINE_CUSTOM_GETTER(getterPath, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +JSC_DEFINE_CUSTOM_SETTER(idSetter, + (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, + JSC::EncodedJSValue value, JSC::PropertyName propertyName)) +{ + JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!thisObject) + return false; + + thisObject->m_id.set(globalObject->vm(), thisObject, JSValue::decode(value).toString(globalObject)); + return true; +} + +JSC_DEFINE_CUSTOM_GETTER(loadedGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { return JSValue::encode(jsUndefined()); } - return JSValue::encode(thisObject->m_id.get()); + + return JSValue::encode(jsBoolean(thisObject->hasEvaluated)); } -JSC_DEFINE_CUSTOM_GETTER(getterParent, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +JSC_DEFINE_CUSTOM_SETTER(loadedSetter, + (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, + JSC::EncodedJSValue value, JSC::PropertyName propertyName)) +{ + JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!thisObject) + return false; + + thisObject->hasEvaluated = JSValue::decode(value).toBoolean(globalObject); + + return true; +} + +JSC_DEFINE_CUSTOM_GETTER(parentGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { @@ -334,7 +383,29 @@ JSC_DEFINE_CUSTOM_GETTER(getterParent, (JSC::JSGlobalObject * globalObject, JSC: return JSValue::encode(jsUndefined()); } -JSC_DEFINE_CUSTOM_SETTER(setterPath, +JSC_DEFINE_CUSTOM_SETTER(parentSetter, + (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, + JSC::EncodedJSValue value, JSC::PropertyName propertyName)) +{ + JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!thisObject) + return false; + + thisObject->m_parent.set(globalObject->vm(), thisObject, JSValue::decode(value)); + + return true; +} + +JSC_DEFINE_CUSTOM_GETTER(pathGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + return JSValue::encode(jsUndefined()); + } + return JSValue::encode(thisObject->m_id.get()); +} + +JSC_DEFINE_CUSTOM_SETTER(pathSetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) { @@ -348,7 +419,7 @@ JSC_DEFINE_CUSTOM_SETTER(setterPath, extern "C" JSC::EncodedJSValue Resolver__propForRequireMainPaths(JSGlobalObject*); -JSC_DEFINE_CUSTOM_GETTER(getterPaths, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +JSC_DEFINE_CUSTOM_GETTER(pathsGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { @@ -363,17 +434,7 @@ JSC_DEFINE_CUSTOM_GETTER(getterPaths, (JSC::JSGlobalObject * globalObject, JSC:: return JSValue::encode(thisObject->m_paths.get()); } -JSC_DEFINE_CUSTOM_GETTER(getterLoaded, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (UNLIKELY(!thisObject)) { - return JSValue::encode(jsUndefined()); - } - - return JSValue::encode(jsBoolean(thisObject->hasEvaluated)); -} - -JSC_DEFINE_CUSTOM_SETTER(setterPaths, +JSC_DEFINE_CUSTOM_SETTER(pathsSetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) { @@ -385,62 +446,14 @@ JSC_DEFINE_CUSTOM_SETTER(setterPaths, return true; } -JSC_DEFINE_CUSTOM_SETTER(setterFilename, - (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, - JSC::EncodedJSValue value, JSC::PropertyName propertyName)) -{ - JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (!thisObject) - return false; - - thisObject->m_filename.set(globalObject->vm(), thisObject, JSValue::decode(value).toString(globalObject)); - return true; -} - -JSC_DEFINE_CUSTOM_SETTER(setterId, - (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, - JSC::EncodedJSValue value, JSC::PropertyName propertyName)) -{ - JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (!thisObject) - return false; - - thisObject->m_id.set(globalObject->vm(), thisObject, JSValue::decode(value).toString(globalObject)); - return true; -} -JSC_DEFINE_CUSTOM_SETTER(setterParent, - (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, - JSC::EncodedJSValue value, JSC::PropertyName propertyName)) -{ - JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (!thisObject) - return false; - - thisObject->m_parent.set(globalObject->vm(), thisObject, JSValue::decode(value)); - - return true; -} -JSC_DEFINE_CUSTOM_SETTER(setterLoaded, - (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, - JSC::EncodedJSValue value, JSC::PropertyName propertyName)) -{ - JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (!thisObject) - return false; - - thisObject->hasEvaluated = JSValue::decode(value).toBoolean(globalObject); - - return true; -} - static JSValue createChildren(VM& vm, JSObject* object) { return constructEmptyArray(object->globalObject(), nullptr, 0); } -JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * globalObject, CallFrame* callframe)) +JSC_DEFINE_HOST_FUNCTION(jsFunctionCommonJSModuleRecord_compile, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto* moduleObject = jsDynamicCast(callframe->thisValue()); + auto* moduleObject = jsDynamicCast(callFrame->thisValue()); if (!moduleObject) { return JSValue::encode(jsUndefined()); } @@ -448,42 +461,46 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); - String sourceString = callframe->argument(0).toWTFString(globalObject); + JSValue sourceValue = callFrame->argument(0); + auto sourceWTF = sourceValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, JSValue::encode({})); - String filenameString = callframe->argument(1).toWTFString(globalObject); + JSValue filenameValue = callFrame->argument(1); + auto filenameWTF = filenameValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, JSValue::encode({})); + auto filename = JSC::jsStringWithCache(vm, filenameWTF); + WTF::Vector dirnameArgs; + dirnameArgs.reserveInitialCapacity(1); + dirnameArgs.unsafeAppendWithoutCapacityCheck(JSValue::encode(filenameValue)); +#if OS(WINDOWS) + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, true, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#else + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, false, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#endif + String wrappedString = makeString( "(function(exports,require,module,__filename,__dirname){"_s, - sourceString, + sourceWTF, "\n})"_s); - SourceCode sourceCode = makeSource( + SourceCode sourceCode = JSC::makeSource( WTFMove(wrappedString), - SourceOrigin(URL::fileURLWithFileSystemPath(filenameString)), + SourceOrigin(URL::fileURLWithFileSystemPath(filenameWTF)), JSC::SourceTaintedOrigin::Untainted, - filenameString, + filenameWTF, WTF::TextPosition(), JSC::SourceProviderSourceType::Program); JSSourceCode* jsSourceCode = JSSourceCode::create(vm, WTFMove(sourceCode)); moduleObject->sourceCode.set(vm, moduleObject, jsSourceCode); - auto index = filenameString.reverseFind('/', filenameString.length()); - String dirnameString; - if (index != WTF::notFound) { - dirnameString = filenameString.substring(0, index); - } else { - dirnameString = "/"_s; - } - WTF::NakedPtr exception; evaluateCommonJSModuleOnce( vm, jsCast(globalObject), moduleObject, - jsString(vm, dirnameString), - jsString(vm, filenameString), + dirname, + filename, exception); if (exception) { @@ -495,15 +512,86 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * return JSValue::encode(jsUndefined()); } +extern "C" JSC::EncodedJSValue jsFunctionResolveSyncPrivate(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + auto* globalObject = jsCast(lexicalGlobalObject); + auto& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + JSValue moduleName = callFrame->argument(0); + JSValue from = callFrame->argument(1); + bool isESM = callFrame->argument(2).asBoolean(); + + if (moduleName.isUndefinedOrNull()) { + JSC::throwTypeError(globalObject, throwScope, "expected module name as a string"_s); + throwScope.release(); + return JSValue::encode(JSValue {}); + } + + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(JSValue {})); + + if (globalObject->onLoadPlugins.hasVirtualModules()) { + if (moduleName.isString()) { + auto moduleStr = moduleName.toWTFString(globalObject); + auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleStr, from.toWTFString(globalObject)); + if (resolvedString) { + if (moduleStr == resolvedString.value()) + return JSValue::encode(moduleName); + return JSValue::encode(jsString(vm, resolvedString.value())); + } + } + } + + if (!isESM) { + auto overrideHandler = globalObject->m_nodeModuleOverriddenResolveFilename.get(); + if (UNLIKELY(overrideHandler)) { + ASSERT(overrideHandler->isCallable()); + auto requireMap = globalObject->requireMap(); + auto* parentModuleObject = jsDynamicCast(requireMap->get(globalObject, from)); + + JSValue parentID = jsUndefined(); + if (parentModuleObject) { + parentID = parentModuleObject->id(); + } else { + parentID = from; + } + + auto parentIdStr = parentID.toWTFString(globalObject); + auto bunStr = Bun::toString(parentIdStr); + + MarkedArgumentBuffer args; + args.append(moduleName); + args.append(parentModuleObject); + args.append(jsBoolean(Bun__isBunMain(globalObject, &bunStr))); + + // `Module` will be cached because requesting it is the only way to access `Module._resolveFilename`. + auto* ModuleModuleObject = jsCast(requireMap->get(globalObject, JSC::jsStringWithCache(vm, "module"_s))); + auto ModuleExportsObject = ModuleModuleObject->exportsObject(); + + return JSValue::encode(JSC::call(globalObject, overrideHandler, JSC::getCallData(overrideHandler), ModuleExportsObject, args)); + } + } + + auto result = Bun__resolveSync(globalObject, JSValue::encode(moduleName), JSValue::encode(from), isESM); + + if (!JSValue::decode(result).isString()) { + JSC::throwException(globalObject, throwScope, JSValue::decode(result)); + return JSValue::encode(JSValue {}); + } + + throwScope.release(); + return result; +} + static const struct HashTableValue JSCommonJSModulePrototypeTableValues[] = { - { "_compile"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, functionCommonJSModuleRecord_compile, 2 } }, + { "_compile"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionCommonJSModuleRecord_compile, 2 } }, { "children"_s, static_cast(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, createChildren } }, - { "filename"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterFilename, setterFilename } }, - { "id"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterId, setterId } }, - { "loaded"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterLoaded, setterLoaded } }, - { "parent"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, getterParent, setterParent } }, - { "path"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterPath, setterPath } }, - { "paths"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterPaths, setterPaths } }, + { "filename"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, filenameGetter, filenameSetter } }, + { "id"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, idGetter, idSetter } }, + { "loaded"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, loadedGetter, loadedSetter } }, + { "parent"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, parentGetter, parentSetter } }, + { "path"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, pathGetter, pathSetter } }, + { "paths"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, pathsGetter, pathsSetter } }, }; class JSCommonJSModulePrototype final : public JSC::JSNonFinalObject { @@ -522,7 +610,7 @@ public: static JSC::Structure* createStructure( JSC::VM& vm, JSC::JSGlobalObject* globalObject, - JSC::JSValue prototype) + JSValue prototype) { auto* structure = JSC::Structure::create(vm, globalObject, prototype, TypeInfo(JSC::ObjectType, StructureFlags), info()); structure->setMayBePrototype(true); @@ -556,7 +644,7 @@ public: globalObject, clientData(vm)->builtinNames().requirePrivateName(), 2, - jsFunctionRequireCommonJS, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete); + jsFunctionRequirePrivate, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete); } }; @@ -577,7 +665,6 @@ JSC::Structure* JSCommonJSModule::createStructure( JSC::JSGlobalObject* globalObject) { auto& vm = globalObject->vm(); - auto* prototype = JSCommonJSModulePrototype::create(vm, globalObject, JSCommonJSModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); // Do not set the number of inline properties on this structure @@ -598,39 +685,41 @@ JSCommonJSModule* JSCommonJSModule::create( return cell; } -JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateCommonJSModule, (JSGlobalObject * globalObject, CallFrame* callframe)) +JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateCommonJSModule, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); - RELEASE_ASSERT(callframe->argumentCount() == 4); + RELEASE_ASSERT(callFrame->argumentCount() == 4); - auto id = callframe->uncheckedArgument(0).toWTFString(globalObject); - JSValue object = callframe->uncheckedArgument(1); - JSValue hasEvaluated = callframe->uncheckedArgument(2); + auto id = callFrame->uncheckedArgument(0).toString(globalObject); + JSValue object = callFrame->uncheckedArgument(1); + JSValue hasEvaluated = callFrame->uncheckedArgument(2); ASSERT(hasEvaluated.isBoolean()); - JSValue parent = callframe->uncheckedArgument(3); + JSValue parent = callFrame->uncheckedArgument(3); return JSValue::encode(JSCommonJSModule::create(jsCast(globalObject), id, object, hasEvaluated.isTrue(), parent)); } JSCommonJSModule* JSCommonJSModule::create( Zig::GlobalObject* globalObject, - const WTF::String& key, + JSC::JSString* id, JSValue exportsObject, bool hasEvaluated, JSValue parent) { auto& vm = globalObject->vm(); - JSString* requireMapKey = JSC::jsStringWithCache(vm, key); - auto index = key.reverseFind('/', key.length()); - JSString* dirname = jsEmptyString(vm); - if (index != WTF::notFound) { - dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index); - } + WTF::Vector dirnameArgs; + dirnameArgs.reserveInitialCapacity(1); + dirnameArgs.unsafeAppendWithoutCapacityCheck(JSValue::encode(id)); +#if OS(WINDOWS) + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, true, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#else + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, false, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#endif auto* out = JSCommonJSModule::create( vm, globalObject->CommonJSModuleObjectStructure(), - requireMapKey, requireMapKey, dirname, nullptr); + id, id, dirname, nullptr); out->putDirect( vm, @@ -837,8 +926,6 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, JSC::MarkedArgumentBuffer& exportValues) { auto result = this->exportsObject(); - - auto& vm = globalObject->vm(); populateESMExports(globalObject, result, exportNames, exportValues, this->ignoreESModuleAnnotation); } @@ -890,23 +977,82 @@ const JSC::ClassInfo JSCommonJSModule::s_info = { "Module"_s, &Base::s_info, nul const JSC::ClassInfo RequireResolveFunctionPrototype::s_info = { "resolve"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(RequireResolveFunctionPrototype) }; const JSC::ClassInfo RequireFunctionPrototype::s_info = { "require"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(RequireFunctionPrototype) }; -JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireCommonJS, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe)) +JSC_DEFINE_HOST_FUNCTION(jsFunctionRequirePrivate, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto* globalObject = jsCast(lexicalGlobalObject); auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); - JSCommonJSModule* thisObject = jsDynamicCast(callframe->thisValue()); - if (!thisObject) + JSCommonJSModule* thisObject = jsDynamicCast(callFrame->thisValue()); + if (!thisObject) { return throwVMTypeError(globalObject, throwScope); + } - JSValue specifierValue = callframe->argument(0); - WTF::String specifier = specifierValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); + JSValue specifierValue = callFrame->argument(0); + auto specifier = specifierValue.toWTFString(globalObject); + auto* moduleObject = jsCast(callFrame->argument(1)); + + auto requireUnbound = globalObject->requireFunctionUnbound(); + auto requireUnboundPrototype = requireUnbound->getPrototype(vm, globalObject); + if (requireUnboundPrototype.isObject()) { + JSObject* prototype = requireUnboundPrototype.getObject(); + auto extensionsValue = prototype->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "extensions"_s)); + if (extensionsValue.isObject()) { + auto clientData = WebCore::clientData(vm); + auto builtinNames = clientData->builtinNames(); + JSObject* extensionsObject = extensionsValue.getObject(); + bool structureChanged = extensionsObject->getDirect(vm, builtinNames.originalStructureIDPrivateName()) != jsNumber(extensionsObject->structureID().bits()); + if (UNLIKELY(structureChanged)) { + WTF::Vector basenameArgs; + basenameArgs.reserveInitialCapacity(1); + basenameArgs.unsafeAppendWithoutCapacityCheck(JSC::JSValue::encode(specifierValue)); +#if OS(WINDOWS) + auto basename = JSValue::decode(Bun__Path__basename(globalObject, true, reinterpret_cast(basenameArgs.data()), 1)).toWTFString(globalObject); +#else + auto basename = JSValue::decode(Bun__Path__basename(globalObject, false, reinterpret_cast(basenameArgs.data()), 1)).toWTFString(globalObject); +#endif + size_t index = 0; + uint16_t startIndex = 0; + JSFunction* extHandler = nullptr; + // Find longest registered extension. + while ((index = basename.find("."_s, startIndex)) != WTF::notFound) { + if (index == 0) { + // Skip dotfiles like .gitignore + continue; + } + auto extStr = basename.substring(index); + auto extValue = extensionsObject->get(globalObject, JSC::Identifier::fromString(vm, extStr)); + if (UNLIKELY(extValue.isCallable())) { + extHandler = jsCast(extValue); + break; + } + startIndex = index + 1; + } + // Fallback to ".js". + if (LIKELY(!extHandler)) { + auto extValue = extensionsObject->get(globalObject, JSC::Identifier::fromString(vm, ".js"_s)); + if (UNLIKELY(extValue.isCallable())) { + extHandler = jsCast(extValue); + } + } + if (UNLIKELY(extHandler)) { + JSC::CallData callData = JSC::getCallData(extHandler); + MarkedArgumentBuffer args; + args.append(moduleObject); // module + args.append(specifierValue); // id + // Call Module._extensions[ext](module, id) + JSC::call(globalObject, extHandler, callData, extensionsObject, args); + return JSValue::encode(moduleObject->exportsObject()); + } + } + } + } + // Special-case for "process" to just return the process object directly. if (UNLIKELY(specifier == "process"_s || specifier == "node:process"_s)) { - jsCast(callframe->argument(1))->putDirect(vm, builtinNames(vm).exportsPublicName(), globalObject->processObject(), 0); + moduleObject->putDirect(vm, Bun::builtinNames(vm).exportsPublicName(), globalObject->processObject(), 0); return JSValue::encode(globalObject->processObject()); } @@ -918,17 +1064,16 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireCommonJS, (JSGlobalObject * lexicalGlo BunString typeAttributeStr = { BunStringTag::Dead }; String typeAttribute = String(); - // We need to be able to wire in the "type" import attribute from bundled code.. // so we do it via CommonJS require(). - int32_t previousArgumentCount = callframe->argument(2).asInt32(); + int32_t previousArgumentCount = callFrame->argument(2).asInt32(); // If they called require(id), skip the check for the type attribute if (UNLIKELY(previousArgumentCount == 2)) { - JSValue val = callframe->argument(3); - if (val.isObject()) { - JSObject* obj = val.getObject(); + JSValue attrValue = callFrame->argument(3); + if (attrValue.isObject()) { + JSObject* attrObject = attrValue.getObject(); // This getter is expensive and rare. - if (auto typeValue = obj->getIfPropertyExists(globalObject, vm.propertyNames->type)) { + if (auto typeValue = attrObject->getIfPropertyExists(globalObject, vm.propertyNames->type)) { if (typeValue.isString()) { typeAttribute = typeValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); @@ -941,7 +1086,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireCommonJS, (JSGlobalObject * lexicalGlo JSValue fetchResult = Bun::fetchCommonJSModule( globalObject, - jsCast(callframe->argument(1)), + moduleObject, specifierValue, &specifierStr, &referrerStr, @@ -967,15 +1112,16 @@ bool JSCommonJSModule::evaluate( ResolvedSource source, bool isBuiltIn) { - auto& vm = globalObject->vm(); auto sourceProvider = Zig::SourceProvider::create(jsCast(globalObject), source, JSC::SourceProviderSourceType::Program, isBuiltIn); this->ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule; JSC::SourceCode rawInputSource( WTFMove(sourceProvider)); - if (this->hasEvaluated) + if (this->hasEvaluated) { return true; + } + auto& vm = globalObject->vm(); this->sourceCode.set(vm, this, JSC::JSSourceCode::create(vm, WTFMove(rawInputSource))); WTF::NakedPtr exception; @@ -1003,10 +1149,10 @@ std::optional createCommonJSModule( bool isBuiltIn) { JSCommonJSModule* moduleObject = nullptr; - WTF::String sourceURL = source.source_url.toWTFString(); - - JSValue specifierValue = Bun::toJS(globalObject, source.specifier); - JSValue entry = globalObject->requireMap()->get(globalObject, specifierValue); + auto sourceURL = source.source_url.toWTFString(); + auto sourceURLValue = Bun::toJS(globalObject, source.source_url); + auto specifierValue = Bun::toJS(globalObject, source.specifier); + auto entry = globalObject->requireMap()->get(globalObject, specifierValue); auto sourceProvider = Zig::SourceProvider::create(jsCast(globalObject), source, JSC::SourceProviderSourceType::Program, isBuiltIn); bool ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule; @@ -1018,24 +1164,25 @@ std::optional createCommonJSModule( if (!moduleObject) { auto& vm = globalObject->vm(); - auto* requireMapKey = jsStringWithCache(vm, sourceURL); - auto index = sourceURL.reverseFind('/', sourceURL.length()); - JSString* dirname = jsEmptyString(vm); - JSString* filename = requireMapKey; - if (index != WTF::notFound) { - dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index); - } - + auto* id = JSC::jsStringWithCache(vm, sourceURL); + WTF::Vector dirnameArgs; + dirnameArgs.reserveInitialCapacity(1); + dirnameArgs.unsafeAppendWithoutCapacityCheck(JSValue::encode(specifierValue)); +#if OS(WINDOWS) + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, true, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#else + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, false, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#endif moduleObject = JSCommonJSModule::create( vm, globalObject->CommonJSModuleObjectStructure(), - requireMapKey, filename, dirname, JSC::JSSourceCode::create(vm, SourceCode(WTFMove(sourceProvider)))); + id, id, dirname, JSC::JSSourceCode::create(vm, SourceCode(WTFMove(sourceProvider)))); moduleObject->putDirect(vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()), 0); - globalObject->requireMap()->set(globalObject, requireMapKey, moduleObject); + globalObject->requireMap()->set(globalObject, id, moduleObject); } moduleObject->ignoreESModuleAnnotation = ignoreESModuleAnnotation; @@ -1067,8 +1214,8 @@ std::optional createCommonJSModule( // so that it can be re-evaluated on the next require. globalObject->requireMap()->remove(globalObject, moduleObject->id()); - auto scope = DECLARE_THROW_SCOPE(vm); - throwException(globalObject, scope, exception.get()); + auto throwScope = DECLARE_THROW_SCOPE(vm); + throwException(globalObject, throwScope, exception.get()); exception.clear(); return; } @@ -1082,19 +1229,19 @@ std::optional createCommonJSModule( sourceURL)); } -JSObject* JSCommonJSModule::createBoundRequireFunction(VM& vm, JSGlobalObject* lexicalGlobalObject, const WTF::String& pathString) +JSObject* JSCommonJSModule::createBoundRequireFunction(VM& vm, JSGlobalObject* lexicalGlobalObject, JSC::JSString* filename) { - ASSERT(!pathString.startsWith("file://"_s)); + ASSERT(!filename->tryGetValue().startsWith("file://"_s)); auto* globalObject = jsCast(lexicalGlobalObject); - - JSString* filename = JSC::jsStringWithCache(vm, pathString); - auto index = pathString.reverseFind('/', pathString.length()); - JSString* dirname = jsEmptyString(vm); - if (index != WTF::notFound) { - dirname = JSC::jsSubstring(globalObject, filename, 0, index); - } - + WTF::Vector dirnameArgs; + dirnameArgs.reserveInitialCapacity(1); + dirnameArgs.unsafeAppendWithoutCapacityCheck(JSValue::encode(filename)); +#if OS(WINDOWS) + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, true, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#else + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, false, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#endif auto moduleObject = Bun::JSCommonJSModule::create( vm, globalObject->CommonJSModuleObjectStructure(), diff --git a/src/bun.js/bindings/CommonJSModuleRecord.h b/src/bun.js/bindings/CommonJSModuleRecord.h index 530369bf67..91041c578a 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.h +++ b/src/bun.js/bindings/CommonJSModuleRecord.h @@ -2,6 +2,8 @@ #include "root.h" #include "headers-handwritten.h" +extern "C" JSC_DECLARE_HOST_FUNCTION(jsFunctionResolveSyncPrivate); + namespace Zig { class GlobalObject; } @@ -15,7 +17,7 @@ class AbstractModuleRecord; namespace Bun { JSC_DECLARE_HOST_FUNCTION(jsFunctionCreateCommonJSModule); -JSC_DECLARE_HOST_FUNCTION(jsFunctionLoadModule); +JSC_DECLARE_HOST_FUNCTION(jsFunctionEvaluateCommonJSModule); void populateESMExports( JSC::JSGlobalObject* globalObject, @@ -61,15 +63,15 @@ public: static JSCommonJSModule* create( Zig::GlobalObject* globalObject, - const WTF::String& key, + JSC::JSString* id, JSValue exportsObject, bool hasEvaluated, JSValue parent); static JSCommonJSModule* create( Zig::GlobalObject* globalObject, - const WTF::String& key, + JSC::JSString* id, ResolvedSource resolvedSource); - static JSObject* createBoundRequireFunction(VM& vm, JSGlobalObject* lexicalGlobalObject, const WTF::String& pathString); + static JSObject* createBoundRequireFunction(VM& vm, JSGlobalObject* lexicalGlobalObject, JSC::JSString* filename); void toSyntheticSource(JSC::JSGlobalObject* globalObject, JSC::Identifier moduleKey, @@ -82,7 +84,6 @@ public: DECLARE_INFO; DECLARE_VISIT_CHILDREN; - static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); template @@ -106,12 +107,6 @@ public: } }; -JSCommonJSModule* createCommonJSModuleWithoutRunning( - Zig::GlobalObject* globalObject, - Ref sourceProvider, - const WTF::String& sourceURL, - ResolvedSource source); - JSC::Structure* createCommonJSModuleStructure( Zig::GlobalObject* globalObject); diff --git a/src/bun.js/bindings/ImportMetaObject.cpp b/src/bun.js/bindings/ImportMetaObject.cpp index de472a66d4..6f4c90be64 100644 --- a/src/bun.js/bindings/ImportMetaObject.cpp +++ b/src/bun.js/bindings/ImportMetaObject.cpp @@ -87,84 +87,12 @@ ALWAYS_INLINE bool isAbsolutePath(WTF::String input) #endif } +extern "C" JSC__JSValue Bun__Path__dirname(JSC__JSGlobalObject* arg0, bool arg1, JSC__JSValue* arg2, uint16_t arg3); + namespace Zig { using namespace JSC; using namespace WebCore; -static JSC::EncodedJSValue functionRequireResolve(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame, const WTF::String& fromStr) -{ - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - switch (callFrame->argumentCount()) { - case 0: { - // not "requires" because "require" could be confusing - JSC::throwTypeError(globalObject, scope, "require.resolve needs 1 argument (a string)"_s); - scope.release(); - return JSC::JSValue::encode(JSC::JSValue {}); - } - default: { - JSValue thisValue = callFrame->thisValue(); - JSC::JSValue moduleName = callFrame->argument(0); - - auto doIt = [&](const WTF::String& fromStr) -> JSC::EncodedJSValue { - Zig::GlobalObject* zigGlobalObject = jsCast(globalObject); - if (zigGlobalObject->onLoadPlugins.hasVirtualModules()) { - if (auto result = zigGlobalObject->onLoadPlugins.resolveVirtualModule(fromStr, String())) { - if (fromStr == result.value()) - return JSC::JSValue::encode(moduleName); - - return JSC::JSValue::encode(jsString(vm, result.value())); - } - } - - BunString from = Bun::toString(fromStr); - auto result = Bun__resolveSyncWithSource(globalObject, JSC::JSValue::encode(moduleName), &from, false); - - if (!JSC::JSValue::decode(result).isString()) { - JSC::throwException(globalObject, scope, JSC::JSValue::decode(result)); - return JSC::JSValue::encode(JSValue {}); - } - - scope.release(); - return result; - }; - - if (moduleName.isUndefinedOrNull()) { - JSC::throwTypeError(globalObject, scope, "require.resolve expects a string"_s); - scope.release(); - return JSC::JSValue::encode(JSC::JSValue {}); - } - - if (callFrame->argumentCount() > 1) { - JSC::JSValue fromValue = callFrame->argument(1); - - // require.resolve also supports a paths array - // we only support a single path - if (!fromValue.isUndefinedOrNull() && fromValue.isObject()) { - if (auto pathsObject = fromValue.getObject()->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "paths"_s))) { - if (pathsObject.isCell() && pathsObject.asCell()->type() == JSC::JSType::ArrayType) { - auto pathsArray = JSC::jsCast(pathsObject); - if (pathsArray->length() > 0) { - fromValue = pathsArray->getIndex(globalObject, 0); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); - } - } - } - } - - if (fromValue.isString()) { - WTF::String str = fromValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); - return doIt(str); - } - } - - return doIt(fromStr); - } - } -} - Zig::ImportMetaObject* Zig::ImportMetaObject::create(JSC::JSGlobalObject* globalObject, JSValue key) { if (WebCore::DOMURL* domURL = WebCoreCast(JSValue::encode(key))) { @@ -183,33 +111,20 @@ Zig::ImportMetaObject* Zig::ImportMetaObject::create(JSC::JSGlobalObject* global return create(globalObject, keyString); } -JSC_DECLARE_HOST_FUNCTION(jsFunctionRequireResolve); -JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireResolve, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - JSValue thisValue = callFrame->thisValue(); - WTF::String fromStr; - - if (thisValue.isString()) { - fromStr = thisValue.toWTFString(globalObject); - } - - return functionRequireResolve(globalObject, callFrame, fromStr); -} - -extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +JSC_DEFINE_HOST_FUNCTION(jsFunctionImportMeta_resolveSync, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto* globalObject = jsCast(lexicalGlobalObject); JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); JSValue thisValue = callFrame->thisValue(); - JSC::JSValue moduleName = callFrame->argument(0); - JSC::JSValue fromValue = callFrame->argument(1); + JSValue moduleName = callFrame->argument(0); + JSValue fromValue = callFrame->argument(1); if (moduleName.isUndefinedOrNull()) { - JSC::throwTypeError(globalObject, scope, "expects a string"_s); - scope.release(); - return JSC::JSValue::encode(JSC::JSValue {}); + JSC::throwTypeError(globalObject, throwScope, "expects a string"_s); + throwScope.release(); + return JSValue::encode(JSValue {}); } JSC__JSValue from; @@ -218,52 +133,53 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje if (callFrame->argumentCount() > 1) { if (callFrame->argumentCount() > 2) { - JSC::JSValue isESMValue = callFrame->argument(2); + JSValue isESMValue = callFrame->argument(2); if (isESMValue.isBoolean()) { isESM = isESMValue.toBoolean(globalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(JSValue {})); } } if (!fromValue.isUndefinedOrNull() && fromValue.isObject()) { - if (auto pathsObject = fromValue.getObject()->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "paths"_s))) { if (pathsObject.isCell() && pathsObject.asCell()->type() == JSC::JSType::ArrayType) { auto pathsArray = JSC::jsCast(pathsObject); if (pathsArray->length() > 0) { fromValue = pathsArray->getIndex(globalObject, 0); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(JSValue {})); } } } } else if (fromValue.isBoolean()) { isESM = fromValue.toBoolean(globalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(JSValue {})); fromValue = JSC::jsUndefined(); } if (fromValue.isString()) { - from = JSC::JSValue::encode(fromValue); + from = JSValue::encode(fromValue); } else if (thisValue.isString()) { - from = JSC::JSValue::encode(thisValue); + from = JSValue::encode(thisValue); } } else if (thisValue.isString()) { - from = JSC::JSValue::encode(thisValue); + from = JSValue::encode(thisValue); } else { JSC::JSObject* thisObject = JSC::jsDynamicCast(thisValue); if (UNLIKELY(!thisObject)) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "import.meta.resolveSync must be bound to an import.meta object"_s); - return JSC::JSValue::encode(JSC::JSValue {}); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSC::throwTypeError(globalObject, throwScope, "import.meta.resolveSync must be bound to an import.meta object"_s); + return JSValue::encode(JSValue {}); } auto clientData = WebCore::clientData(vm); - JSValue pathProperty = thisObject->getIfPropertyExists(globalObject, clientData->builtinNames().pathPublicName()); + auto builtinNames = clientData->builtinNames(); + + JSValue pathProperty = thisObject->getIfPropertyExists(globalObject, builtinNames.pathPublicName()); if (pathProperty && pathProperty.isString()) - from = JSC::JSValue::encode(pathProperty); + from = JSValue::encode(pathProperty); } if (globalObject->onLoadPlugins.hasVirtualModules()) { @@ -271,131 +187,64 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje auto moduleString = moduleName.toWTFString(globalObject); if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleString, JSValue::decode(from).toWTFString(globalObject))) { if (moduleString == resolvedString.value()) - return JSC::JSValue::encode(moduleName); - return JSC::JSValue::encode(jsString(vm, resolvedString.value())); + return JSValue::encode(moduleName); + return JSValue::encode(jsString(vm, resolvedString.value())); } } } - auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), from, isESM); + auto result = Bun__resolveSync(globalObject, JSValue::encode(moduleName), from, isESM); - if (!JSC::JSValue::decode(result).isString()) { - JSC::throwException(globalObject, scope, JSC::JSValue::decode(result)); - return JSC::JSValue::encode(JSC::JSValue {}); + if (!JSValue::decode(result).isString()) { + JSC::throwException(globalObject, throwScope, JSValue::decode(result)); + return JSValue::encode(JSValue {}); } - scope.release(); + throwScope.release(); return result; } -extern "C" bool Bun__isBunMain(JSC::JSGlobalObject* global, const BunString*); - -extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto* globalObject = jsDynamicCast(lexicalGlobalObject); - - JSC::JSValue moduleName = callFrame->argument(0); - JSValue from = callFrame->argument(1); - bool isESM = callFrame->argument(2).asBoolean(); - - if (moduleName.isUndefinedOrNull()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "expected module name as a string"_s); - scope.release(); - return JSC::JSValue::encode(JSC::JSValue {}); - } - - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); - - if (globalObject->onLoadPlugins.hasVirtualModules()) { - if (moduleName.isString()) { - auto moduleString = moduleName.toWTFString(globalObject); - if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleString, from.toWTFString(globalObject))) { - if (moduleString == resolvedString.value()) - return JSC::JSValue::encode(moduleName); - return JSC::JSValue::encode(jsString(vm, resolvedString.value())); - } - } - } - - if (!isESM) { - if (LIKELY(globalObject)) { - auto overrideHandler = globalObject->m_nodeModuleOverriddenResolveFilename.get(); - if (UNLIKELY(overrideHandler)) { - ASSERT(overrideHandler->isCallable()); - JSValue parentModuleObject = globalObject->requireMap()->get(globalObject, from); - - JSValue parentID = jsUndefined(); - if (auto* parent = jsDynamicCast(parentModuleObject)) { - parentID = parent->id(); - } else { - parentID = from; - } - - MarkedArgumentBuffer args; - args.append(moduleName); - args.append(parentModuleObject); - auto parentIdStr = parentID.toWTFString(globalObject); - auto bunStr = Bun::toString(parentIdStr); - args.append(jsBoolean(Bun__isBunMain(lexicalGlobalObject, &bunStr))); - - return JSValue::encode(JSC::call(lexicalGlobalObject, overrideHandler, JSC::getCallData(overrideHandler), parentModuleObject, args)); - } - } - } - - auto result = Bun__resolveSync(lexicalGlobalObject, JSC::JSValue::encode(moduleName), JSValue::encode(from), isESM); - - if (!JSC::JSValue::decode(result).isString()) { - JSC::throwException(lexicalGlobalObject, scope, JSC::JSValue::decode(result)); - return JSC::JSValue::encode(JSC::JSValue {}); - } - - scope.release(); - return result; -} - -JSC_DEFINE_HOST_FUNCTION(functionImportMeta__resolve, +JSC_DEFINE_HOST_FUNCTION(jsFunctionImportMeta_resolve, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto* globalObject = jsCast(lexicalGlobalObject); JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); switch (callFrame->argumentCount()) { case 0: { // not "requires" because "require" could be confusing - JSC::throwTypeError(globalObject, scope, "import.meta.resolve needs 1 argument (a string)"_s); - scope.release(); - return JSC::JSValue::encode(JSC::JSValue {}); + JSC::throwTypeError(globalObject, throwScope, "import.meta.resolve needs 1 argument (a string)"_s); + throwScope.release(); + return JSValue::encode(JSValue {}); } default: { - JSC::JSValue moduleName = callFrame->argument(0); + JSValue moduleName = callFrame->argument(0); if (moduleName.isUndefinedOrNull()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "import.meta.resolve expects a string"_s); - scope.release(); - return JSC::JSValue::encode(JSC::JSValue {}); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSC::throwTypeError(globalObject, throwScope, "import.meta.resolve expects a string"_s); + throwScope.release(); + return JSValue::encode(JSValue {}); } JSC__JSValue from; if (callFrame->argumentCount() > 1 && callFrame->argument(1).isString()) { - from = JSC::JSValue::encode(callFrame->argument(1)); + from = JSValue::encode(callFrame->argument(1)); } else { JSC::JSObject* thisObject = JSC::jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "import.meta.resolve must be bound to an import.meta object"_s); - return JSC::JSValue::encode(JSC::JSValue {}); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSC::throwTypeError(globalObject, throwScope, "import.meta.resolve must be bound to an import.meta object"_s); + return JSValue::encode(JSValue {}); } auto clientData = WebCore::clientData(vm); + auto builtinNames = clientData->builtinNames(); - from = JSC::JSValue::encode(thisObject->getIfPropertyExists(globalObject, clientData->builtinNames().pathPublicName())); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); + from = JSValue::encode(thisObject->getIfPropertyExists(globalObject, builtinNames.pathPublicName())); + RETURN_IF_EXCEPTION(throwScope, JSValue::encode(JSValue {})); } if (globalObject->onLoadPlugins.hasVirtualModules()) { @@ -403,13 +252,13 @@ JSC_DEFINE_HOST_FUNCTION(functionImportMeta__resolve, auto moduleString = moduleName.toWTFString(globalObject); if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleString, JSValue::decode(from).toWTFString(globalObject))) { if (moduleString == resolvedString.value()) - return JSC::JSValue::encode(JSPromise::resolvedPromise(globalObject, moduleName)); - return JSC::JSValue::encode(JSPromise::resolvedPromise(globalObject, jsString(vm, resolvedString.value()))); + return JSValue::encode(JSPromise::resolvedPromise(globalObject, moduleName)); + return JSValue::encode(JSPromise::resolvedPromise(globalObject, jsString(vm, resolvedString.value()))); } } } - return Bun__resolve(globalObject, JSC::JSValue::encode(moduleName), from, true); + return Bun__resolve(globalObject, JSValue::encode(moduleName), from, true); } } } @@ -429,6 +278,7 @@ Zig::ImportMetaObject* ImportMetaObject::create(JSC::VM& vm, JSC::JSGlobalObject ptr->finishCreation(vm); return ptr; } + Zig::ImportMetaObject* ImportMetaObject::create(JSC::JSGlobalObject* jslobalObject, JSC::JSString* keyString) { auto* globalObject = jsCast(jslobalObject); @@ -446,6 +296,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_url, (JSGlobalObject * globalO return JSValue::encode(thisObject->urlProperty.getInitializedOnMainThread(thisObject)); } + JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_dir, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { ImportMetaObject* thisObject = jsDynamicCast(JSValue::decode(thisValue)); @@ -454,6 +305,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_dir, (JSGlobalObject * globalO return JSValue::encode(thisObject->dirProperty.getInitializedOnMainThread(thisObject)); } + JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_file, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { ImportMetaObject* thisObject = jsDynamicCast(JSValue::decode(thisValue)); @@ -462,6 +314,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_file, (JSGlobalObject * global return JSValue::encode(thisObject->fileProperty.getInitializedOnMainThread(thisObject)); } + JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_path, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { ImportMetaObject* thisObject = jsDynamicCast(JSValue::decode(thisValue)); @@ -470,6 +323,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_path, (JSGlobalObject * global return JSValue::encode(thisObject->pathProperty.getInitializedOnMainThread(thisObject)); } + JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_require, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { ImportMetaObject* thisObject = jsDynamicCast(JSValue::decode(thisValue)); @@ -478,6 +332,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_require, (JSGlobalObject * glo return JSValue::encode(thisObject->requireProperty.getInitializedOnMainThread(thisObject)); } + JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_env, (JSGlobalObject * jsGlobalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { auto* globalObject = jsCast(jsGlobalObject); @@ -485,15 +340,15 @@ JSC_DEFINE_CUSTOM_GETTER(jsImportMetaObjectGetter_env, (JSGlobalObject * jsGloba } static const HashTableValue ImportMetaObjectPrototypeValues[] = { + { "env"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_env, 0 } }, { "dir"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_dir, 0 } }, { "dirname"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_dir, 0 } }, - { "env"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_env, 0 } }, { "file"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_file, 0 } }, { "filename"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_path, 0 } }, { "path"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_path, 0 } }, { "require"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_require, 0 } }, - { "resolve"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, functionImportMeta__resolve, 0 } }, - { "resolveSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, functionImportMeta__resolveSync, 0 } }, + { "resolve"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionImportMeta_resolve, 0 } }, + { "resolveSync"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionImportMeta_resolveSync, 0 } }, { "url"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsImportMetaObjectGetter_url, 0 } }, }; @@ -525,8 +380,8 @@ public: { Base::finishCreation(vm); - auto* clientData = WebCore::clientData(vm); - auto& builtinNames = clientData->builtinNames(); + auto clientData = WebCore::clientData(vm); + auto builtinNames = clientData->builtinNames(); reifyStaticProperties(vm, ImportMetaObject::info(), ImportMetaObjectPrototypeValues, *this); JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); @@ -558,9 +413,6 @@ JSC::Structure* ImportMetaObject::createStructure(JSC::VM& vm, JSC::JSGlobalObje globalObject, ImportMetaObjectPrototype::createStructure(vm, globalObject)); - auto clientData = WebCore::clientData(vm); - auto& builtinNames = clientData->builtinNames(); - return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), ImportMetaObject::info()); } @@ -570,52 +422,57 @@ void ImportMetaObject::finishCreation(VM& vm) ASSERT(inherits(info())); this->requireProperty.initLater([](const JSC::LazyProperty::Initializer& init) { - ImportMetaObject* meta = jsCast(init.owner); - - WTF::URL url = isAbsolutePath(meta->url) ? WTF::URL::fileURLWithFileSystemPath(meta->url) : WTF::URL(meta->url); - WTF::String path; + auto* meta = jsCast(init.owner); + auto globalObject = meta->globalObject(); + auto& vm = init.vm; + auto url = isAbsolutePath(meta->url) ? WTF::URL::fileURLWithFileSystemPath(meta->url) : WTF::URL(meta->url); + WTF::String filenameWTF; if (url.isValid()) { - if (url.protocolIsFile()) { - path = url.fileSystemPath(); + filenameWTF = url.fileSystemPath(); } else { - path = url.path().toString(); + filenameWTF = url.path().toString(); } } else { - path = meta->url; + filenameWTF = meta->url; } - - JSFunction* value = jsCast(Bun::JSCommonJSModule::createBoundRequireFunction(init.vm, meta->globalObject(), path)); + auto* filename = JSC::jsStringWithCache(vm, filenameWTF); + JSFunction* value = jsCast(Bun::JSCommonJSModule::createBoundRequireFunction(vm, globalObject, filename)); init.set(value); }); this->urlProperty.initLater([](const JSC::LazyProperty::Initializer& init) { - ImportMetaObject* meta = jsCast(init.owner); - WTF::URL url = isAbsolutePath(meta->url) ? WTF::URL::fileURLWithFileSystemPath(meta->url) : WTF::URL(meta->url); + auto* meta = jsCast(init.owner); + auto url = isAbsolutePath(meta->url) ? WTF::URL::fileURLWithFileSystemPath(meta->url) : WTF::URL(meta->url); init.set(jsString(init.vm, url.string())); }); this->dirProperty.initLater([](const JSC::LazyProperty::Initializer& init) { - ImportMetaObject* meta = jsCast(init.owner); - - WTF::URL url = isAbsolutePath(meta->url) ? WTF::URL::fileURLWithFileSystemPath(meta->url) : WTF::URL(meta->url); - WTF::String dirname; + auto* meta = jsCast(init.owner); + auto globalObject = meta->globalObject(); + auto& vm = init.vm; + auto url = isAbsolutePath(meta->url) ? WTF::URL::fileURLWithFileSystemPath(meta->url) : WTF::URL(meta->url); + WTF::String filenameWTF; if (url.isValid()) { if (url.protocolIsFile()) { - dirname = url.fileSystemPath(); + filenameWTF = url.fileSystemPath(); } else { - dirname = url.path().toString(); + filenameWTF = url.path().toString(); } + } else { + filenameWTF = meta->url; } - - if (dirname.endsWith(PLATFORM_SEP_s)) { - dirname = dirname.substring(0, dirname.length() - 1); - } else if (dirname.contains(PLATFORM_SEP)) { - dirname = dirname.substring(0, dirname.reverseFind(PLATFORM_SEP)); - } - - init.set(jsString(init.vm, dirname)); + auto filename = JSC::jsStringWithCache(vm, filenameWTF); + WTF::Vector dirnameArgs; + dirnameArgs.reserveInitialCapacity(1); + dirnameArgs.unsafeAppendWithoutCapacityCheck(JSValue::encode(filename)); +#if OS(WINDOWS) + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, true, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#else + auto dirname = JSValue::decode(Bun__Path__dirname(globalObject, false, reinterpret_cast(dirnameArgs.data()), 1)).toString(globalObject); +#endif + init.set(dirname); }); this->fileProperty.initLater([](const JSC::LazyProperty::Initializer& init) { ImportMetaObject* meta = jsCast(init.owner); diff --git a/src/bun.js/bindings/ImportMetaObject.h b/src/bun.js/bindings/ImportMetaObject.h index 02b911af05..8a4ffd51cf 100644 --- a/src/bun.js/bindings/ImportMetaObject.h +++ b/src/bun.js/bindings/ImportMetaObject.h @@ -8,11 +8,8 @@ #include "JSDOMWrapperCache.h" -extern "C" JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolveSync); -extern "C" JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolveSyncPrivate); extern "C" JSC::EncodedJSValue Bun__resolve(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm); extern "C" JSC::EncodedJSValue Bun__resolveSync(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm); -extern "C" JSC::EncodedJSValue Bun__resolveSyncWithSource(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, BunString* from, bool is_esm); namespace Zig { @@ -24,9 +21,6 @@ public: using Base = JSC::JSNonFinalObject; static ImportMetaObject* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, const WTF::String& url); - - static JSObject* createRequireFunction(VM& vm, JSGlobalObject* lexicalGlobalObject, const WTF::String& pathString); - static ImportMetaObject* create(JSC::JSGlobalObject* globalObject, JSC::JSString* keyString); static ImportMetaObject* create(JSC::JSGlobalObject* globalObject, JSValue keyString); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 0868cdb81e..d59a1e27d3 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -3663,21 +3663,19 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) // TODO: most/all of these private properties can be made as static globals. // i've noticed doing it as is will work somewhat but getDirect() wont be able to find them - putDirectBuiltinFunction(vm, this, builtinNames.createFIFOPrivateName(), streamInternalsCreateFIFOCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectBuiltinFunction(vm, this, builtinNames.createEmptyReadableStreamPrivateName(), readableStreamCreateEmptyReadableStreamCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectBuiltinFunction(vm, this, builtinNames.createUsedReadableStreamPrivateName(), readableStreamCreateUsedReadableStreamCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); putDirectBuiltinFunction(vm, this, builtinNames.consumeReadableStreamPrivateName(), readableStreamConsumeReadableStreamCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectBuiltinFunction(vm, this, builtinNames.createNativeReadableStreamPrivateName(), readableStreamCreateNativeReadableStreamCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectBuiltinFunction(vm, this, builtinNames.requireESMPrivateName(), importMetaObjectRequireESMCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectBuiltinFunction(vm, this, builtinNames.loadCJS2ESMPrivateName(), importMetaObjectLoadCJS2ESMCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectBuiltinFunction(vm, this, builtinNames.internalRequirePrivateName(), importMetaObjectInternalRequireCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectBuiltinFunction(vm, this, builtinNames.requireNativeModulePrivateName(), moduleRequireNativeModuleCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - - putDirectBuiltinFunction(vm, this, builtinNames.overridableRequirePrivateName(), moduleOverridableRequireCodeGenerator(vm), 0); - - putDirectNativeFunction(vm, this, builtinNames.createUninitializedArrayBufferPrivateName(), 1, functionCreateUninitializedArrayBuffer, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - putDirectNativeFunction(vm, this, builtinNames.resolveSyncPrivateName(), 1, functionImportMeta__resolveSyncPrivate, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.createEmptyReadableStreamPrivateName(), readableStreamCreateEmptyReadableStreamCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.createFIFOPrivateName(), streamInternalsCreateFIFOCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); putDirectNativeFunction(vm, this, builtinNames.createInternalModuleByIdPrivateName(), 1, InternalModuleRegistry::jsCreateInternalModuleById, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.createNativeReadableStreamPrivateName(), readableStreamCreateNativeReadableStreamCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectNativeFunction(vm, this, builtinNames.createUninitializedArrayBufferPrivateName(), 1, functionCreateUninitializedArrayBuffer, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.createUsedReadableStreamPrivateName(), readableStreamCreateUsedReadableStreamCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.internalRequirePrivateName(), importMetaObjectInternalRequireCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.loadCJS2ESMPrivateName(), importMetaObjectLoadCJS2ESMCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.overridableRequirePrivateName(), moduleOverridableRequireCodeGenerator(vm), 0); + putDirectBuiltinFunction(vm, this, builtinNames.requireESMPrivateName(), importMetaObjectRequireESMCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectBuiltinFunction(vm, this, builtinNames.requireNativeModulePrivateName(), moduleRequireNativeModuleCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + putDirectNativeFunction(vm, this, builtinNames.resolveSyncPrivateName(), 1, jsFunctionResolveSyncPrivate, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); putDirectNativeFunction(vm, this, builtinNames.createCommonJSModulePrivateName(), @@ -3689,7 +3687,7 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) putDirectNativeFunction(vm, this, builtinNames.evaluateCommonJSModulePrivateName(), 2, - Bun::jsFunctionLoadModule, + Bun::jsFunctionEvaluateCommonJSModule, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete | 0); @@ -3793,13 +3791,13 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_builtinInternalFunctions.visit(visitor); thisObject->m_commonStrings.visit(visitor); visitor.append(thisObject->m_assignToStream); + visitor.append(thisObject->m_nodeModuleOverriddenResolveFilename); visitor.append(thisObject->m_readableStreamToArrayBuffer); visitor.append(thisObject->m_readableStreamToArrayBufferResolve); visitor.append(thisObject->m_readableStreamToBlob); visitor.append(thisObject->m_readableStreamToJSON); visitor.append(thisObject->m_readableStreamToText); visitor.append(thisObject->m_readableStreamToFormData); - visitor.append(thisObject->m_nodeModuleOverriddenResolveFilename); visitor.append(thisObject->m_nextTickQueue); visitor.append(thisObject->m_errorConstructorPrepareStackTraceValue); @@ -3841,7 +3839,6 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_emitReadableNextTickFunction.visit(visitor); thisObject->m_JSBufferSubclassStructure.visit(visitor); thisObject->m_JSCryptoKey.visit(visitor); - thisObject->m_cryptoObject.visit(visitor); thisObject->m_JSDOMFileConstructor.visit(visitor); @@ -3857,6 +3854,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_lazyTestModuleObject.visit(visitor); thisObject->m_lazyPreloadTestModuleObject.visit(visitor); thisObject->m_testMatcherUtilsObject.visit(visitor); + thisObject->m_commonJSModuleObjectStructure.visit(visitor); thisObject->m_JSSQLStatementStructure.visit(visitor); thisObject->m_memoryFootprintStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index f7f961b6bd..6bae46d35f 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -268,7 +268,7 @@ public: bool hasProcessObject() const { return m_processObject.isInitialized(); } RefPtr performance(); - + JSC::JSObject* processObject() { return m_processObject.getInitializedOnMainThread(this); } JSC::JSObject* processEnvObject() { return m_processEnvObject.getInitializedOnMainThread(this); } JSC::JSObject* bunObject() { return m_bunObject.getInitializedOnMainThread(this); } @@ -362,8 +362,8 @@ public: mutable WriteBarrier m_readableStreamToText; mutable WriteBarrier m_readableStreamToFormData; - // This is set when doing `require('module')._resolveFilename = ...` - // a hack used by Next.js to inject their versions of webpack and react + // This is set when overriding `require('module')._resolveFilename = ...` + // used by projects like Next.js to inject their versions of webpack and react. mutable WriteBarrier m_nodeModuleOverriddenResolveFilename; mutable WriteBarrier m_nextTickQueue; diff --git a/src/bun.js/modules/NodeModuleModule.h b/src/bun.js/modules/NodeModuleModule.h index 3fb5e72f94..3310198500 100644 --- a/src/bun.js/modules/NodeModuleModule.h +++ b/src/bun.js/modules/NodeModuleModule.h @@ -10,6 +10,9 @@ using namespace Zig; using namespace JSC; +extern "C" JSC__JSValue Bun__Path__dirname(JSC__JSGlobalObject *arg0, bool arg1, + JSC__JSValue *arg2, uint16_t arg3); + // This is a mix of bun's builtin module names and also the ones reported by // node v20.4.0 static constexpr ASCIILiteral builtinModuleNames[] = { @@ -98,10 +101,9 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleModuleConstructor, // In node, this is supposed to be the actual CommonJSModule constructor. // We are cutting a huge corner by not doing all that work. // This code is only to support babel. - JSC::VM &vm = globalObject->vm(); - JSString *idString = JSC::jsString(vm, WTF::String("."_s)); - - JSString *dirname = jsEmptyString(vm); + auto &vm = globalObject->vm(); + auto *id = JSC::jsEmptyString(vm); + auto *dirname = JSC::jsStringWithCache(vm, "."_s); // TODO: handle when JSGlobalObject !== Zig::GlobalObject, such as in node:vm Structure *structure = static_cast(globalObject) @@ -112,18 +114,29 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleModuleConstructor, JSValue parentValue = callFrame->argument(1); auto scope = DECLARE_THROW_SCOPE(vm); + if (idValue.isString()) { - idString = idValue.toString(globalObject); + id = idValue.toString(globalObject); RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); - - auto index = idString->tryGetValue().reverseFind('/', idString->length()); - - if (index != WTF::notFound) { - dirname = JSC::jsSubstring(globalObject, idString, 0, index); - } + WTF::Vector dirnameArgs; + dirnameArgs.reserveInitialCapacity(1); + dirnameArgs.unsafeAppendWithoutCapacityCheck(JSValue::encode(idValue)); +#if OS(WINDOWS) + dirname = JSValue::decode( + Bun__Path__dirname( + globalObject, true, + reinterpret_cast(dirnameArgs.data()), 1)) + .toString(globalObject); +#else + dirname = JSValue::decode( + Bun__Path__dirname( + globalObject, false, + reinterpret_cast(dirnameArgs.data()), 1)) + .toString(globalObject); +#endif } - auto *out = Bun::JSCommonJSModule::create(vm, structure, idString, jsNull(), + auto *out = Bun::JSCommonJSModule::create(vm, structure, id, jsNull(), dirname, nullptr); if (!parentValue.isUndefined()) @@ -175,7 +188,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionWrap, (JSC::JSGlobalObject * globalObject, JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire, (JSC::JSGlobalObject * globalObject, JSC::CallFrame *callFrame)) { - JSC::VM &vm = globalObject->vm(); + auto &vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (callFrame->argumentCount() < 1) { throwTypeError(globalObject, scope, @@ -183,10 +196,9 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire, RELEASE_AND_RETURN(scope, JSC::JSValue::encode({})); } - auto val = callFrame->uncheckedArgument(0).toWTFString(globalObject); - - if (val.startsWith("file://"_s)) { - WTF::URL url(val); + auto filenameWTF = callFrame->uncheckedArgument(0).toWTFString(globalObject); + if (filenameWTF.startsWith("file://"_s)) { + WTF::URL url(filenameWTF); if (!url.isValid()) { throwTypeError(globalObject, scope, makeString("createRequire() was given an invalid URL '"_s, @@ -199,14 +211,16 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire, "createRequire() does not support non-file URLs"_s); RELEASE_AND_RETURN(scope, JSValue::encode({})); } - val = url.fileSystemPath(); + filenameWTF = url.fileSystemPath(); } + auto *filename = JSC::jsStringWithCache(vm, filenameWTF); RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); RELEASE_AND_RETURN( scope, JSValue::encode(Bun::JSCommonJSModule::createBoundRequireFunction( - vm, globalObject, val))); + vm, globalObject, filename))); } + extern "C" JSC::EncodedJSValue Resolver__nodeModulePathsForJS(JSGlobalObject *, CallFrame *); @@ -332,16 +346,14 @@ JSC_DEFINE_CUSTOM_SETTER(set_resolveFilename, return false; } -// These two setters are only used if you directly hit -// `Module.prototype.require` or `module.require`. When accessing the cjs -// require argument, this is a bound version of `require`, which calls into the -// overridden one. +// These accessors are defined for `Module.prototype.require`. +// The cjs `require` argument will also call into the overridden one. // // This require function also intentionally does not have .resolve on it, nor // does it have any of the other properties. // -// Note: allowing require to be overridable at all is only needed for Next.js to -// work (they do Module.prototype.require = ...) +// Note: allowing require to be overridable is needed for projects like Next.js +// to work (they do Module.prototype.require = ...) JSC_DEFINE_CUSTOM_GETTER(getterRequireFunction, (JSC::JSGlobalObject * globalObject, diff --git a/src/codegen/replacements.ts b/src/codegen/replacements.ts index 2d16667949..b861a68b93 100644 --- a/src/codegen/replacements.ts +++ b/src/codegen/replacements.ts @@ -33,6 +33,7 @@ export const globalsToPrefix = [ "ArrayBuffer", "Buffer", "Infinity", + "isFinite", "Loader", "Promise", "ReadableByteStreamController", @@ -41,17 +42,15 @@ export const globalsToPrefix = [ "ReadableStreamBYOBRequest", "ReadableStreamDefaultController", "ReadableStreamDefaultReader", + "RegExp", + "String", "TransformStream", "TransformStreamDefaultController", "Uint8Array", - "String", - "Buffer", - "RegExp", + "undefined", "WritableStream", "WritableStreamDefaultController", "WritableStreamDefaultWriter", - "isFinite", - "undefined", ]; // These enums map to $IdToLabel and $LabelToId diff --git a/src/js/builtins.d.ts b/src/js/builtins.d.ts index c0df84c58f..ac7457b6f1 100644 --- a/src/js/builtins.d.ts +++ b/src/js/builtins.d.ts @@ -407,6 +407,8 @@ declare function $streamErrored(): TODO; declare function $streamReadable(): TODO; declare function $streamWaiting(): TODO; declare function $streamWritable(): TODO; +declare function $stringIndexOfInternal(searchString: string, position?: number): number; +declare function $stringSubstring(indexStart: number, indexEnd?: number): string; declare function $structuredCloneForStream(): TODO; declare function $syscall(): TODO; declare function $textDecoderStreamDecoder(): TODO; @@ -538,3 +540,8 @@ declare var $Buffer: { declare interface Error { code?: string; } + +declare var $Object: { + $create(o: object | null): any; + $create(o: object | null, properties: PropertyDescriptorMap & ThisType): any; +}; diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index 3128c3d6c5..e573bc33b9 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -150,6 +150,7 @@ using namespace JSC; macro(once) \ macro(options) \ macro(origin) \ + macro(originalStructureID) \ macro(overridableRequire) \ macro(ownerReadableStream) \ macro(parse) \ @@ -217,6 +218,7 @@ using namespace JSC; macro(strategySizeAlgorithm) \ macro(stream) \ macro(structuredCloneForStream) \ + macro(structureChanged) \ macro(syscall) \ macro(textDecoderStreamDecoder) \ macro(textDecoderStreamTransform) \ diff --git a/src/js/builtins/Module.ts b/src/js/builtins/Module.ts index 01ca8db21c..ad0192858e 100644 --- a/src/js/builtins/Module.ts +++ b/src/js/builtins/Module.ts @@ -11,8 +11,12 @@ export function require(this: CommonJSModuleRecord, id: string) { // overridableRequire can be overridden by setting `Module.prototype.require` $overriddenName = "require"; $visibility = "Private"; -export function overridableRequire(this: CommonJSModuleRecord, id: string) { - const existing = $requireMap.$get(id) || $requireMap.$get((id = $resolveSync(id, this.path, false))); +export function overridableRequire(this: CommonJSModuleRecord, id: string, type?: { type: string }) { + let existing = $requireMap.$get(id); + if (existing === undefined) { + id = $resolveSync(id, this.path, false); + existing = $requireMap.$get(id); + } if (existing) { // Scenario where this is necessary: // @@ -34,11 +38,9 @@ export function overridableRequire(this: CommonJSModuleRecord, id: string) { $evaluateCommonJSModule(existing); return existing.exports; } - if (id.endsWith(".node")) { return $internalRequire(id); } - // To handle import/export cycles, we need to create a module object and put // it into the map before we import it. const mod = $createCommonJSModule(id, {}, false, this); @@ -49,38 +51,38 @@ export function overridableRequire(this: CommonJSModuleRecord, id: string) { // // Note: we do not need to wrap this in a try/catch, if it throws the C++ code will // clear the module from the map. - // - var out = this.$require( - id, - mod, - // did they pass a { type } object? - $argumentCount(), - // the object containing a "type" attribute, if they passed one - // maybe this will be "paths" in the future too. - arguments[1], - ); - - // -1 means we need to lookup the module from the ESM registry. - if (out === -1) { + if ( + this.$require( + id, + mod, + // Did they pass a { type } object? + // Use `@argumentCount()` to avoid cloned arguments allocation. + $argumentCount(), + // The object containing a "type" attribute, if they passed one + // maybe this will be "paths" in the future too. + type, + ) === -1 + ) { + // -1 means we need to lookup the module from the ESM registry. try { - out = $requireESM(id); + $requireESM(id); } catch (exception) { // Since the ESM code is mostly JS, we need to handle exceptions here. $requireMap.$delete(id); throw exception; } - const esm = Loader.registry.$get(id); - // If we can pull out a ModuleNamespaceObject, let's do it. - if (esm?.evaluated && (esm.state ?? 0) >= $ModuleReady) { + if (esm && esm.evaluated && (esm.state ?? 0) >= $ModuleReady) { const namespace = Loader.getModuleNamespaceObject(esm!.module); - return (mod.exports = - // if they choose a module - namespace.__esModule ? namespace : Object.create(namespace, { __esModule: { value: true } })); + const exports = namespace.__esModule + ? // If they choose a module. + namespace + : $Object.$create(namespace, { __esModule: { value: true } }); + mod.exports = exports; + return exports; } } - $evaluateCommonJSModule(mod); return mod.exports; } @@ -93,7 +95,7 @@ export function requireResolve(this: string | { path: string }, id: string) { $visibility = "Private"; export function requireNativeModule(id: string) { let esm = Loader.registry.$get(id); - if (esm?.evaluated && (esm.state ?? 0) >= $ModuleReady) { + if (esm && esm.evaluated && (esm.state ?? 0) >= $ModuleReady) { const exports = Loader.getModuleNamespaceObject(esm.module); return exports.default; } diff --git a/test/js/node/module/moduleExtensionsChange-fixture.cjs b/test/js/node/module/moduleExtensionsChange-fixture.cjs new file mode 100644 index 0000000000..beb840b5e7 --- /dev/null +++ b/test/js/node/module/moduleExtensionsChange-fixture.cjs @@ -0,0 +1 @@ +module.exports = "original"; diff --git a/test/js/node/module/moduleExtensionsChange.cjs b/test/js/node/module/moduleExtensionsChange.cjs new file mode 100644 index 0000000000..f26fc73375 --- /dev/null +++ b/test/js/node/module/moduleExtensionsChange.cjs @@ -0,0 +1,51 @@ +const { expect, test } = require("bun:test"); +const Module = require("node:module"); + +test("Module._extensions change", () => { + const oldCjs = Module._extensions[".cjs"]; + const oldJs = Module._extensions[".js"]; +debugger; + // Test default behavior. + const defaultResult = require("./moduleExtensionsChange-fixture.cjs"); + expect(defaultResult).toBe("original"); + + // Reset. + delete Module._cache[require.resolve("./moduleExtensionsChange-fixture.cjs")]; + + // Test .cjs extension override. + Module._extensions[".cjs"] = function (mod, filename) { + mod._compile(`module.exports = "winner";`, filename); + }; + const changedCjsResult = require("./moduleExtensionsChange-fixture.cjs"); + expect(changedCjsResult).toBe("winner"); + + // Reset. + delete Module._cache[require.resolve("./moduleExtensionsChange-fixture.cjs")]; + if (oldCjs) { + Module._extensions['.cjs'] = oldCjs; + } else { + delete Module._extensions['.cjs']; + } + + // Test reverted behavior. + const revertedResult = require("./moduleExtensionsChange-fixture.cjs"); + expect(revertedResult).toBe("original"); + + // Reset. + delete Module._cache[require.resolve("./moduleExtensionsChange-fixture.cjs")]; + + // Test fallback to .js. + Module._extensions[".cjs"] = function (mod, filename) { + mod._compile(`module.exports = "winner";`, filename); + }; + const changedJsResult = require("./moduleExtensionsChange-fixture.cjs"); + expect(changedJsResult).toBe("winner"); + + // Reset. + delete Module._cache[require.resolve("./moduleExtensionsChange-fixture.cjs")]; + if (oldJs) { + Module._extensions['.js'] = oldJs; + } else { + delete Module._extensions['.js']; + } +}); \ No newline at end of file diff --git a/test/js/node/module/modulePrototypeOverwrite.cjs b/test/js/node/module/modulePrototypeOverwrite.cjs index 4e84026a6a..a57fb94a18 100644 --- a/test/js/node/module/modulePrototypeOverwrite.cjs +++ b/test/js/node/module/modulePrototypeOverwrite.cjs @@ -1,17 +1,19 @@ +const { expect, test } = require("bun:test"); +const Module = require("node:module"); + // This behavior is required for Next.js to work -const eql = require("assert").deepStrictEqual; -const Module = require("module"); - -const old = Module.prototype.require; -Module.prototype.require = function (str) { - if (str === "hook") return "winner"; - return { - wrap: old.call(this, str), +test("Module.prototype.require overwrite", () => { + const old = Module.prototype.require; + Module.prototype.require = function (id) { + if (id === "hook") { + return "winner"; + } + return { + wrap: old.call(this, id), + }; }; -}; - -// this context has the new require -const result = require("./modulePrototypeOverwrite-fixture.cjs"); -eql(result, { wrap: "winner" }); - -console.log("--pass--"); + // This context has the new require + const result = require("./modulePrototypeOverwrite-fixture.cjs"); + Module.prototype.require = old; + expect(result).toEqual({ wrap: "winner" }); +}); \ No newline at end of file diff --git a/test/js/node/module/node-module-module.test.js b/test/js/node/module/node-module-module.test.js index c5453e4ede..12cd57b4f4 100644 --- a/test/js/node/module/node-module-module.test.js +++ b/test/js/node/module/node-module-module.test.js @@ -1,9 +1,8 @@ // @known-failing-on-windows: 1 failing import { expect, test } from "bun:test"; import { bunEnv, bunExe } from "harness"; -import { _nodeModulePaths, builtinModules, isBuiltin, wrap } from "module"; -import Module from "module"; -import path from "path"; +import Module, { _nodeModulePaths, builtinModules, isBuiltin, wrap } from "node:module"; +import path from "node:path"; test("builtinModules exists", () => { expect(Array.isArray(builtinModules)).toBe(true); diff --git a/test/js/node/module/resolveFilenameOverwrite.cjs b/test/js/node/module/resolveFilenameOverwrite.cjs index e6a3670c39..37b8a63a7c 100644 --- a/test/js/node/module/resolveFilenameOverwrite.cjs +++ b/test/js/node/module/resolveFilenameOverwrite.cjs @@ -1,15 +1,26 @@ +const { expect, test } = require("bun:test"); +const Module = require("node:module"); +const path = require("node:path"); + // This behavior is required for Next.js to work -const eql = require("assert").strictEqual; -const path = require("path"); -const Module = require("module"); - -const original = Module._resolveFilename; -Module._resolveFilename = (specifier, parent, isMain) => { - eql(specifier.endsWith("💔"), true); - eql(parent.filename, path.join(__dirname, "./resolveFilenameOverwrite.cjs")); - return path.join(__dirname, "./resolveFilenameOverwrite-fixture.cjs"); -}; -eql(require("overwriting _resolveFilename broke 💔"), "winner"); -Module._resolveFilename = original; - -console.log("--pass--"); +test("Module._resolveFilename overwrite", () => { + let assertions = 0; + const old = Module._resolveFilename; + Module._resolveFilename = function (request, parent, isMain) { + expect(request.endsWith("💔")).toBe(true); + assertions++; + expect(parent.filename).toBe(path.join(__dirname, "./resolveFilenameOverwrite.cjs")); + assertions++; + expect(isMain).toBe(true); + assertions++; + expect(this).toBe(Module); + assertions++; + return path.join(__dirname, "./resolveFilenameOverwrite-fixture.cjs"); + }; + const result = require("overwriting _resolveFilename broke 💔"); + Module._resolveFilename = old; + expect(result).toBe("winner"); + assertions++; + // TODO: Replace with `expect.assertions(3)` once implemented. + expect(assertions).toBe(5); +});