/** * How this works * * CommonJS modules are transpiled by Bun's transpiler to the following: * * (function (exports, require, module) { ... code })(exports, require, module) * * Then, at runtime, we create a JSCommonJSModule object. * * On this special object, we override the setter for the "exports" property in * a non-observable way using a CustomGetterSetter. * * When the setter is called, we set the internal "exports" property to the * value passed in and we also update the requireMap with the new value. * * After the CommonJS module is executed, we: * - Store the exports value in the requireMap (again) * - Loop through the keys of the exports object and re-export as ES Module * named exports * * If an exception occurs, we remove the entry from the requireMap. * * How cyclical dependencies are handled: * * Before executing the CommonJS module, we set the exports object in the * requireMap to an empty object. When the CommonJS module is required again, we * return the exports object from the requireMap. The values should be in sync * while the module is being executed, unless module.exports is re-assigned to a * different value. In that case, it will have a stale value. */ #include "headers.h" #include "JavaScriptCore/CallData.h" #include "JavaScriptCore/Synchronousness.h" #include "JavaScriptCore/JSCast.h" #include #include "root.h" #include "JavaScriptCore/SourceCode.h" #include "headers-handwritten.h" #include "ZigGlobalObject.h" #include #include #include #include #include #include #include #include #include #include "BunClientData.h" #include #include "ImportMetaObject.h" #include #include #include #include #include #include #include #include "ModuleLoader.h" #include #include #include #include "ZigSourceProvider.h" #include #include "CommonJSModuleRecord.h" #include #include #include #include #include "PathInlines.h" #include "wtf/NakedPtr.h" #include "wtf/URL.h" #include "wtf/text/StringImpl.h" extern "C" bool Bun__isBunMain(JSC::JSGlobalObject* global, const BunString*); namespace Bun { using namespace JSC; JSC_DECLARE_HOST_FUNCTION(jsFunctionRequireCommonJS); static bool canPerformFastEnumeration(Structure* s) { if (s->typeInfo().overridesGetOwnPropertySlot()) return false; if (s->typeInfo().overridesAnyFormOfGetOwnPropertyNames()) return false; if (hasIndexedProperties(s->indexingType())) return false; if (s->hasAnyKindOfGetterSetterProperties()) return false; if (s->isUncacheableDictionary()) return false; if (s->hasUnderscoreProtoPropertyExcludingOriginalProto()) return false; return true; } extern "C" bool Bun__VM__specifierIsEvalEntryPoint(void*, EncodedJSValue); extern "C" void Bun__VM__setEntryPointEvalResultCJS(void*, EncodedJSValue); static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObject, JSCommonJSModule* moduleObject, JSString* dirname, JSValue filename, WTF::NakedPtr& exception) { SourceCode code = std::move(moduleObject->sourceCode); // If an exception occurred somewhere else, we might have cleared the source code. if (UNLIKELY(code.isNull())) { auto throwScope = DECLARE_THROW_SCOPE(vm); throwException(globalObject, throwScope, createError(globalObject, "Failed to evaluate module"_s)); exception = throwScope.exception(); return false; } JSFunction* resolveFunction = nullptr; JSFunction* requireFunction = nullptr; const auto initializeModuleObject = [&]() { resolveFunction = JSC::JSBoundFunction::create(vm, globalObject, globalObject->requireResolveFunctionUnbound(), moduleObject->id(), ArgList(), 1, globalObject->commonStrings().resolveString(globalObject)); requireFunction = JSC::JSBoundFunction::create(vm, globalObject, globalObject->requireFunctionUnbound(), moduleObject, ArgList(), 1, globalObject->commonStrings().requireString(globalObject)); requireFunction->putDirect(vm, vm.propertyNames->resolve, resolveFunction, 0); moduleObject->putDirect(vm, WebCore::clientData(vm)->builtinNames().requirePublicName(), requireFunction, 0); moduleObject->hasEvaluated = true; }; if (UNLIKELY(Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(filename)))) { initializeModuleObject(); // Using same approach as node, `arguments` in the entry point isn't defined // https://github.com/nodejs/node/blob/592c6907bfe1922f36240e9df076be1864c3d1bd/lib/internal/process/execution.js#L92 globalObject->putDirect(vm, builtinNames(vm).exportsPublicName(), moduleObject->exportsObject(), 0); globalObject->putDirect(vm, builtinNames(vm).requirePublicName(), requireFunction, 0); globalObject->putDirect(vm, Identifier::fromLatin1(vm, "module"_s), moduleObject, 0); globalObject->putDirect(vm, Identifier::fromLatin1(vm, "__filename"_s), filename, 0); globalObject->putDirect(vm, Identifier::fromLatin1(vm, "__dirname"_s), dirname, 0); JSValue result = JSC::evaluate(globalObject, code, jsUndefined(), exception); if (UNLIKELY(exception.get() || result.isEmpty())) { return false; } Bun__VM__setEntryPointEvalResultCJS(globalObject->bunVM(), JSValue::encode(result)); return true; } // Report the cost of the sourcemap if (moduleObject->source_map_memory_cost > 0) { vm.heap.reportExtraMemoryAllocated(moduleObject, moduleObject->source_map_memory_cost); } // This will return 0 if there was a syntax error or an allocation failure JSValue fnValue = JSC::evaluate(globalObject, code, jsUndefined(), exception); if (UNLIKELY(exception.get() || fnValue.isEmpty())) { return false; } JSObject* fn = fnValue.getObject(); if (UNLIKELY(!fn)) { exception = Exception::create(vm, createTypeError(globalObject, "Expected CommonJS module to have a function wrapper. If you weren't messing around with Bun's internals, this is a bug in Bun"_s)); return false; } JSC::CallData callData = JSC::getCallData(fn); if (UNLIKELY(callData.type == CallData::Type::None)) { exception = Exception::create(vm, createTypeError(globalObject, "Expected CommonJS module to have a function wrapper. If you weren't messing around with Bun's internals, this is a bug in Bun"_s)); return false; } initializeModuleObject(); MarkedArgumentBuffer args; args.append(moduleObject->exportsObject()); // exports args.append(requireFunction); // require args.append(moduleObject); // module args.append(filename); // filename args.append(dirname); // dirname if (auto* jsFunction = jsDynamicCast(fn)) { if (jsFunction->jsExecutable()->parameterCount() > 5) { // it expects ImportMetaObject args.append(Zig::ImportMetaObject::create(globalObject, filename)); } } // Clear the source code as early as possible. code = {}; // Call the CommonJS module wrapper function. // // fn(exports, require, module, __filename, __dirname) { /* code */ }(exports, require, module, __filename, __dirname) // JSC::profiledCall(globalObject, ProfilingReason::API, fn, callData, moduleObject, args, exception); return exception.get() == nullptr; } bool JSCommonJSModule::load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::NakedPtr& exception) { if (this->hasEvaluated || this->sourceCode.isNull()) { return true; } evaluateCommonJSModuleOnce( globalObject->vm(), jsCast(globalObject), this, this->m_dirname.get(), this->m_filename.get(), exception); if (exception.get()) { // On error, remove the module from the require map/ // so that it can be re-evaluated on the next require. globalObject->requireMap()->remove(globalObject, this->id()); return false; } return true; } JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe)) { auto& vm = lexicalGlobalObject->vm(); auto* globalObject = jsCast(lexicalGlobalObject); auto throwScope = DECLARE_THROW_SCOPE(vm); JSCommonJSModule* moduleObject = jsDynamicCast(callframe->argument(0)); if (!moduleObject) { RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true))); } WTF::NakedPtr exception; if (!moduleObject->load(vm, globalObject, exception)) { throwException(globalObject, throwScope, exception.get()); exception.clear(); return {}; } RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true))); } JSC_DEFINE_HOST_FUNCTION(requireResolvePathsFunction, (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)) { Zig::GlobalObject* thisObject = jsCast(globalObject); return JSValue::encode(thisObject->lazyRequireCacheObject()); } JSC_DEFINE_CUSTOM_SETTER(jsRequireCacheSetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) { JSObject* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (!thisObject) return false; thisObject->putDirect(globalObject->vm(), propertyName, JSValue::decode(value), 0); return true; } static const HashTableValue RequireResolveFunctionPrototypeValues[] = { { "paths"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, requireResolvePathsFunction, 1 } }, }; static const HashTableValue RequireFunctionPrototypeValues[] = { { "cache"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsRequireCacheGetter, jsRequireCacheSetter } }, }; Structure* RequireFunctionPrototype::createStructure( JSC::VM& vm, JSC::JSGlobalObject* globalObject) { auto* structure = Structure::create(vm, globalObject, globalObject->functionPrototype(), TypeInfo(ObjectType, StructureFlags), info()); structure->setMayBePrototype(true); return structure; } Structure* RequireResolveFunctionPrototype::createStructure( JSC::VM& vm, JSC::JSGlobalObject* globalObject) { auto* structure = Structure::create(vm, globalObject, globalObject->functionPrototype(), TypeInfo(ObjectType, StructureFlags), info()); structure->setMayBePrototype(true); return structure; } 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); return prototype; } 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, vm.propertyNames->resolve, jsCast(globalObject)->requireResolveFunctionUnbound(), 0); return prototype; } void RequireFunctionPrototype::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); auto* globalObject = this->globalObject(); reifyStaticProperties(vm, info(), RequireFunctionPrototypeValues, *this); JSC::JSFunction* requireDotMainFunction = JSFunction::create( vm, globalObject, moduleMainCodeGenerator(vm), globalObject->globalScope()); this->putDirectAccessor( globalObject, JSC::Identifier::fromString(vm, "main"_s), JSC::GetterSetter::create(vm, globalObject, requireDotMainFunction, requireDotMainFunction), PropertyAttribute::Accessor | PropertyAttribute::ReadOnly | 0); auto extensions = constructEmptyObject(globalObject); 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); this->putDirect(vm, JSC::Identifier::fromString(vm, "extensions"_s), extensions, 0); } JSC_DEFINE_CUSTOM_GETTER(getterFilename, (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_filename.get()); } JSC_DEFINE_CUSTOM_GETTER(getterId, (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_GETTER(getterPath, (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_dirname.get()); } JSC_DEFINE_CUSTOM_GETTER(getterParent, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { return JSValue::encode(jsUndefined()); } if (thisObject->m_overridenParent) { return JSValue::encode(thisObject->m_overridenParent.get()); } if (thisObject->m_parent) { auto* parent = thisObject->m_parent.get(); return JSValue::encode(parent); } // initialize parent by checking if it is the main module. we do this lazily because most people // dont need `module.parent` and creating commonjs module records is done a ton. auto idValue = thisObject->m_id.get(); if (idValue) { auto id = idValue->value(globalObject); auto idStr = Bun::toString(id); if (Bun__isBunMain(globalObject, &idStr)) { thisObject->m_overridenParent.set(globalObject->vm(), thisObject, jsNull()); return JSValue::encode(jsNull()); } } return JSValue::encode(jsUndefined()); } JSC_DEFINE_CUSTOM_SETTER(setterPath, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (!thisObject) return false; thisObject->m_dirname.set(globalObject->vm(), thisObject, JSValue::decode(value).toString(globalObject)); return true; } extern "C" JSC::EncodedJSValue Resolver__propForRequireMainPaths(JSGlobalObject*); JSC_DEFINE_CUSTOM_GETTER(getterPaths, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { return JSValue::encode(jsUndefined()); } if (!thisObject->m_paths) { JSValue paths = JSValue::decode(Resolver__propForRequireMainPaths(globalObject)); thisObject->m_paths.set(globalObject->vm(), thisObject, paths); } 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::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) { JSCommonJSModule* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (!thisObject) return false; thisObject->m_paths.set(globalObject->vm(), thisObject, JSValue::decode(value)); 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; JSValue decodedValue = JSValue::decode(value); if (auto* parent = jsDynamicCast(decodedValue)) { thisObject->m_parent = parent; thisObject->m_overridenParent.clear(); } else { thisObject->m_parent = {}; thisObject->m_overridenParent.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)) { auto* moduleObject = jsDynamicCast(callframe->thisValue()); if (!moduleObject) { return JSValue::encode(jsUndefined()); } auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); String sourceString = callframe->argument(0).toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); JSValue filenameValue = callframe->argument(1); String filenameString = filenameValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); String wrappedString = makeString( "(function(exports,require,module,__filename,__dirname){"_s, sourceString, "\n})"_s); moduleObject->sourceCode = makeSource( WTFMove(wrappedString), SourceOrigin(URL::fileURLWithFileSystemPath(filenameString)), JSC::SourceTaintedOrigin::Untainted, filenameString, WTF::TextPosition(), JSC::SourceProviderSourceType::Program); EncodedJSValue encodedFilename = JSValue::encode(filenameValue); #if OS(WINDOWS) JSValue dirnameValue = JSValue::decode(Bun__Path__dirname(globalObject, true, &encodedFilename, 1)); #else JSValue dirnameValue = JSValue::decode(Bun__Path__dirname(globalObject, false, &encodedFilename, 1)); #endif RETURN_IF_EXCEPTION(throwScope, {}); String dirnameString = dirnameValue.toWTFString(globalObject); WTF::NakedPtr exception; evaluateCommonJSModuleOnce( vm, jsCast(globalObject), moduleObject, jsString(vm, dirnameString), jsString(vm, filenameString), exception); if (exception) { throwException(globalObject, throwScope, exception.get()); exception.clear(); return {}; } return JSValue::encode(jsUndefined()); } static const struct HashTableValue JSCommonJSModulePrototypeTableValues[] = { { "_compile"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, functionCommonJSModuleRecord_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 } }, }; class JSCommonJSModulePrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; static JSCommonJSModulePrototype* create( JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) { JSCommonJSModulePrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSCommonJSModulePrototype(vm, structure); prototype->finishCreation(vm, globalObject); return prototype; } static JSC::Structure* createStructure( JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { auto* structure = JSC::Structure::create(vm, globalObject, prototype, TypeInfo(JSC::ObjectType, StructureFlags), info()); structure->setMayBePrototype(true); return structure; } DECLARE_INFO; JSCommonJSModulePrototype( JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure) { } template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCommonJSModulePrototype, Base); return &vm.plainObjectSpace(); } void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) { Base::finishCreation(vm); ASSERT(inherits(info())); reifyStaticProperties(vm, info(), JSCommonJSModulePrototypeTableValues, *this); this->putDirectNativeFunction( vm, globalObject, clientData(vm)->builtinNames().requirePrivateName(), 2, jsFunctionRequireCommonJS, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete); } }; const JSC::ClassInfo JSCommonJSModulePrototype::s_info = { "ModulePrototype"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSModulePrototype) }; void JSCommonJSModule::finishCreation(JSC::VM& vm, JSC::JSString* id, JSValue filename, JSC::JSString* dirname, const JSC::SourceCode& sourceCode) { Base::finishCreation(vm); ASSERT(inherits(info())); m_id.set(vm, this, id); m_filename.set(vm, this, filename); m_dirname.set(vm, this, dirname); this->sourceCode = sourceCode; } 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 // there may be an off-by-one error in the Structure which causes `require.id` to become the require return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), NonArray); } JSCommonJSModule* JSCommonJSModule::create( JSC::VM& vm, JSC::Structure* structure, JSC::JSString* id, JSValue filename, JSC::JSString* dirname, const JSC::SourceCode& sourceCode) { JSCommonJSModule* cell = new (NotNull, JSC::allocateCell(vm)) JSCommonJSModule(vm, structure); cell->finishCreation(vm, id, filename, dirname, sourceCode); return cell; } JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateCommonJSModule, (JSGlobalObject * globalObject, CallFrame* callframe)) { RELEASE_ASSERT(callframe->argumentCount() == 4); 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); return JSValue::encode(JSCommonJSModule::create(jsCast(globalObject), id, object, hasEvaluated.isTrue(), parent)); } JSCommonJSModule* JSCommonJSModule::create( Zig::GlobalObject* globalObject, JSC::JSString* requireMapKey, JSValue exportsObject, bool hasEvaluated, JSValue parent) { auto& vm = globalObject->vm(); auto key = requireMapKey->value(globalObject); auto index = key->reverseFind(PLATFORM_SEP, key->length()); JSString* dirname; if (index != WTF::notFound) { dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index); } else { dirname = jsEmptyString(vm); } auto* out = JSCommonJSModule::create( vm, globalObject->CommonJSModuleObjectStructure(), requireMapKey, requireMapKey, dirname, SourceCode()); out->putDirect( vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), exportsObject, 0); out->hasEvaluated = hasEvaluated; if (parent && parent.isCell()) { if (auto* parentModule = jsDynamicCast(parent)) { out->m_parent = JSC::Weak(parentModule); } else { out->m_overridenParent.set(vm, out, parent); } } else if (parent) { out->m_overridenParent.set(vm, out, parent); } return out; } JSCommonJSModule* JSCommonJSModule::create( Zig::GlobalObject* globalObject, const WTF::String& key, JSValue exportsObject, bool hasEvaluated, JSValue parent) { auto& vm = globalObject->vm(); auto* requireMapKey = JSC::jsStringWithCache(vm, key); return JSCommonJSModule::create(globalObject, requireMapKey, exportsObject, hasEvaluated, parent); } size_t JSCommonJSModule::estimatedSize(JSC::JSCell* cell, JSC::VM& vm) { auto* thisObject = jsCast(cell); size_t additionalSize = 0; if (!thisObject->sourceCode.isNull() && !thisObject->sourceCode.view().isEmpty()) { additionalSize += thisObject->sourceCode.view().length(); if (!thisObject->sourceCode.view().is8Bit()) { additionalSize *= 2; } } additionalSize += thisObject->source_map_memory_cost; return Base::estimatedSize(cell, vm) + additionalSize; } void JSCommonJSModule::destroy(JSC::JSCell* cell) { static_cast(cell)->JSCommonJSModule::~JSCommonJSModule(); } JSCommonJSModule::~JSCommonJSModule() { } bool JSCommonJSModule::evaluate( Zig::GlobalObject* globalObject, const WTF::String& key, const SyntheticSourceProvider::SyntheticSourceGenerator& generator) { Vector propertyNames; JSC::MarkedArgumentBuffer arguments; auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); generator(globalObject, JSC::Identifier::fromString(vm, key), propertyNames, arguments); RETURN_IF_EXCEPTION(throwScope, false); // This goes off of the assumption that you only call this `evaluate` using a generator that explicitly // assigns the `default` export first. JSValue defaultValue = arguments.at(0); this->putDirect(vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), defaultValue, 0); this->hasEvaluated = true; RELEASE_AND_RETURN(throwScope, true); } void populateESMExports( JSC::JSGlobalObject* globalObject, JSValue result, Vector& exportNames, JSC::MarkedArgumentBuffer& exportValues, bool ignoreESModuleAnnotation) { auto& vm = globalObject->vm(); const Identifier& esModuleMarker = vm.propertyNames->__esModule; // Bun's intepretation of the "__esModule" annotation: // // - If a "default" export does not exist OR the __esModule annotation is not present, then we // set the default export to the exports object // // - If a "default" export also exists, then we set the default export // to the value of it (matching Babel behavior) // // https://stackoverflow.com/questions/50943704/whats-the-purpose-of-object-definepropertyexports-esmodule-value-0 // https://github.com/nodejs/node/issues/40891 // https://github.com/evanw/bundler-esm-cjs-tests // https://github.com/evanw/esbuild/issues/1591 // https://github.com/oven-sh/bun/issues/3383 // // Note that this interpretation is slightly different // // - We do not ignore when "type": "module" or when the file // extension is ".mjs". Build tools determine that based on the // caller's behavior, but in a JS runtime, there is only one ModuleNamespaceObject. // // It would be possible to match the behavior at runtime, but // it would need further engine changes which do not match the ES Module spec // // - We ignore the value of the annotation. We only look for the // existence of the value being set. This is for performance reasons, but also // this annotation is meant for tooling and the only usages of setting // it to something that does NOT evaluate to "true" I could find were in // unit tests of build tools. Happy to revisit this if users file an issue. bool needsToAssignDefault = true; if (auto* exports = result.getObject()) { bool hasESModuleMarker = false; if (!ignoreESModuleAnnotation) { auto catchScope = DECLARE_CATCH_SCOPE(vm); PropertySlot slot(exports, PropertySlot::InternalMethodType::VMInquiry, &vm); if (exports->getPropertySlot(globalObject, esModuleMarker, slot)) { JSValue value = slot.getValue(globalObject, esModuleMarker); if (!value.isUndefinedOrNull()) { if (value.pureToBoolean() == TriState::True) { hasESModuleMarker = true; } } } if (catchScope.exception()) { catchScope.clearException(); } } auto* structure = exports->structure(); uint32_t size = structure->inlineSize() + structure->outOfLineSize(); exportNames.reserveCapacity(size + 2); exportValues.ensureCapacity(size + 2); auto catchScope = DECLARE_CATCH_SCOPE(vm); if (catchScope.exception()) { catchScope.clearException(); } if (hasESModuleMarker) { if (canPerformFastEnumeration(structure)) { exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { auto key = entry.key(); if (key->isSymbol() || key == esModuleMarker) return true; needsToAssignDefault = needsToAssignDefault && key != vm.propertyNames->defaultKeyword; JSValue value = exports->getDirect(entry.offset()); exportNames.append(Identifier::fromUid(vm, key)); exportValues.append(value); return true; }); } else { JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude); if (catchScope.exception()) { catchScope.clearExceptionExceptTermination(); return; } for (auto property : properties) { if (UNLIKELY(property.isEmpty() || property.isNull() || property == esModuleMarker || property.isPrivateName() || property.isSymbol())) continue; // ignore constructor if (property == vm.propertyNames->constructor) continue; JSC::PropertySlot slot(exports, PropertySlot::InternalMethodType::Get); if (!exports->getPropertySlot(globalObject, property, slot)) continue; // Allow DontEnum properties which are not getter/setters // https://github.com/oven-sh/bun/issues/4432 if (slot.attributes() & PropertyAttribute::DontEnum) { if (!(slot.isValue() || slot.isCustom())) { continue; } } exportNames.append(property); JSValue getterResult = slot.getValue(globalObject, property); // If it throws, we keep them in the exports list, but mark it as undefined // This is consistent with what Node.js does. if (catchScope.exception()) { catchScope.clearException(); getterResult = jsUndefined(); } exportValues.append(getterResult); needsToAssignDefault = needsToAssignDefault && property != vm.propertyNames->defaultKeyword; } } } else if (canPerformFastEnumeration(structure)) { exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { auto key = entry.key(); if (key->isSymbol() || key == vm.propertyNames->defaultKeyword) return true; JSValue value = exports->getDirect(entry.offset()); exportNames.append(Identifier::fromUid(vm, key)); exportValues.append(value); return true; }); } else { JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Include); if (catchScope.exception()) { catchScope.clearExceptionExceptTermination(); return; } for (auto property : properties) { if (UNLIKELY(property.isEmpty() || property.isNull() || property == vm.propertyNames->defaultKeyword || property.isPrivateName() || property.isSymbol())) continue; // ignore constructor if (property == vm.propertyNames->constructor) continue; JSC::PropertySlot slot(exports, PropertySlot::InternalMethodType::Get); if (!exports->getPropertySlot(globalObject, property, slot)) continue; if (slot.attributes() & PropertyAttribute::DontEnum) { // Allow DontEnum properties which are not getter/setters // https://github.com/oven-sh/bun/issues/4432 if (!(slot.isValue() || slot.isCustom())) { continue; } } exportNames.append(property); JSValue getterResult = slot.getValue(globalObject, property); // If it throws, we keep them in the exports list, but mark it as undefined // This is consistent with what Node.js does. if (catchScope.exception()) { catchScope.clearException(); getterResult = jsUndefined(); } exportValues.append(getterResult); } } } if (needsToAssignDefault) { exportNames.append(vm.propertyNames->defaultKeyword); exportValues.append(result); } } void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, const JSC::Identifier& moduleKey, Vector& exportNames, JSC::MarkedArgumentBuffer& exportValues) { auto result = this->exportsObject(); populateESMExports(globalObject, result, exportNames, exportValues, this->ignoreESModuleAnnotation); } void JSCommonJSModule::setExportsObject(JSC::JSValue exportsObject) { this->putDirect(vm(), JSC::PropertyName(clientData(vm())->builtinNames().exportsPublicName()), exportsObject, 0); } JSValue JSCommonJSModule::exportsObject() { return this->get(globalObject(), JSC::PropertyName(clientData(vm())->builtinNames().exportsPublicName())); } JSValue JSCommonJSModule::id() { return m_id.get(); } Structure* createCommonJSModuleStructure( Zig::GlobalObject* globalObject) { return JSCommonJSModule::createStructure(globalObject); } template void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) { JSCommonJSModule* thisObject = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); visitor.append(thisObject->m_id); visitor.append(thisObject->m_filename); visitor.append(thisObject->m_dirname); visitor.append(thisObject->m_paths); visitor.append(thisObject->m_overridenParent); visitor.reportExtraMemoryVisited(thisObject->source_map_memory_cost); } DEFINE_VISIT_CHILDREN(JSCommonJSModule); void JSCommonJSModule::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) { auto* thisObject = jsCast(cell); if (auto* id = thisObject->m_id.get()) { if (!id->isRope()) { auto label = id->tryGetValue(false); analyzer.setLabelForCell(cell, makeString("CommonJS Module: "_s, StringView(label))); } else { analyzer.setLabelForCell(cell, "CommonJS Module"_s); } } else { analyzer.setLabelForCell(cell, "CommonJS Module"_s); } Base::analyzeHeap(cell, analyzer); } const JSC::ClassInfo JSCommonJSModule::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSModule) }; 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)) { auto* globalObject = jsCast(lexicalGlobalObject); auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); 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, {}); WTF::String referrer = thisObject->id().toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); BunString specifierStr = Bun::toString(specifier); BunString referrerStr = Bun::toString(referrer); 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(); // 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(); // This getter is expensive and rare. if (auto typeValue = obj->getIfPropertyExists(globalObject, vm.propertyNames->type)) { if (typeValue.isString()) { typeAttribute = typeValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); typeAttributeStr = Bun::toString(typeAttribute); } } RETURN_IF_EXCEPTION(throwScope, {}); } } JSValue fetchResult = Bun::fetchCommonJSModule( globalObject, jsCast(callframe->argument(1)), specifierValue, &specifierStr, &referrerStr, LIKELY(typeAttribute.isEmpty()) ? nullptr : &typeAttributeStr); RELEASE_AND_RETURN(throwScope, JSValue::encode(fetchResult)); } void RequireResolveFunctionPrototype::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); reifyStaticProperties(vm, info(), RequireResolveFunctionPrototypeValues, *this); JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); } bool JSCommonJSModule::evaluate( Zig::GlobalObject* globalObject, const WTF::String& key, 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; if (this->hasEvaluated) return true; this->sourceCode = JSC::SourceCode(WTFMove(sourceProvider)); this->source_map_memory_cost = source.source_map_memory_cost; WTF::NakedPtr exception; evaluateCommonJSModuleOnce(vm, globalObject, this, this->m_dirname.get(), this->m_filename.get(), exception); if (exception.get()) { // On error, remove the module from the require map/ // so that it can be re-evaluated on the next require. globalObject->requireMap()->remove(globalObject, this->id()); auto throwScope = DECLARE_THROW_SCOPE(vm); throwException(globalObject, throwScope, exception.get()); exception.clear(); return false; } return true; } std::optional createCommonJSModule( Zig::GlobalObject* globalObject, JSValue specifierValue, ResolvedSource& source, bool isBuiltIn) { JSCommonJSModule* moduleObject = nullptr; WTF::String sourceURL = source.source_url.toWTFString(); JSValue entry = globalObject->requireMap()->get(globalObject, specifierValue); bool ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule; SourceOrigin sourceOrigin; if (entry) { moduleObject = jsDynamicCast(entry); } if (!moduleObject) { auto& vm = globalObject->vm(); auto* requireMapKey = specifierValue.toString(globalObject); auto index = sourceURL.reverseFind(PLATFORM_SEP, sourceURL.length()); JSString* dirname; JSString* filename = requireMapKey; if (index != WTF::notFound) { dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index); } else { dirname = jsEmptyString(vm); } auto sourceProvider = Zig::SourceProvider::create(jsCast(globalObject), source, JSC::SourceProviderSourceType::Program, isBuiltIn); sourceOrigin = sourceProvider->sourceOrigin(); moduleObject = JSCommonJSModule::create( vm, globalObject->CommonJSModuleObjectStructure(), requireMapKey, filename, dirname, WTFMove(JSC::SourceCode(WTFMove(sourceProvider)))); moduleObject->putDirect(vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()), 0); globalObject->requireMap()->set(globalObject, requireMapKey, moduleObject); } else { sourceOrigin = Zig::toSourceOrigin(sourceURL, isBuiltIn); } moduleObject->ignoreESModuleAnnotation = ignoreESModuleAnnotation; moduleObject->source_map_memory_cost = source.source_map_memory_cost; return JSC::SourceCode( JSC::SyntheticSourceProvider::create( [](JSC::JSGlobalObject* lexicalGlobalObject, const JSC::Identifier& moduleKey, Vector& exportNames, JSC::MarkedArgumentBuffer& exportValues) -> void { auto* globalObject = jsCast(lexicalGlobalObject); auto& vm = globalObject->vm(); JSValue keyValue = identifierToJSValue(vm, moduleKey); JSValue entry = globalObject->requireMap()->get(globalObject, keyValue); if (entry) { if (auto* moduleObject = jsDynamicCast(entry)) { if (!moduleObject->hasEvaluated) { WTF::NakedPtr exception; if (!evaluateCommonJSModuleOnce( vm, globalObject, moduleObject, moduleObject->m_dirname.get(), moduleObject->m_filename.get(), exception)) { // On error, remove the module from the require map // 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()); exception.clear(); return; } } moduleObject->toSyntheticSource(globalObject, moduleKey, exportNames, exportValues); } } else { // require map was cleared of the entry } }, sourceOrigin, sourceURL)); } JSObject* JSCommonJSModule::createBoundRequireFunction(VM& vm, JSGlobalObject* lexicalGlobalObject, const WTF::String& pathString) { ASSERT(!pathString.startsWith("file://"_s)); auto* globalObject = jsCast(lexicalGlobalObject); JSString* filename = JSC::jsStringWithCache(vm, pathString); auto index = pathString.reverseFind(PLATFORM_SEP, pathString.length()); JSString* dirname; if (index != WTF::notFound) { dirname = JSC::jsSubstring(globalObject, filename, 0, index); } else { dirname = jsEmptyString(vm); } auto moduleObject = Bun::JSCommonJSModule::create( vm, globalObject->CommonJSModuleObjectStructure(), filename, filename, dirname, SourceCode()); JSFunction* requireFunction = JSC::JSBoundFunction::create(vm, globalObject, globalObject->requireFunctionUnbound(), moduleObject, ArgList(), 1, globalObject->commonStrings().requireString(globalObject)); JSFunction* resolveFunction = JSC::JSBoundFunction::create(vm, globalObject, globalObject->requireResolveFunctionUnbound(), moduleObject, ArgList(), 1, globalObject->commonStrings().resolveString(globalObject)); requireFunction->putDirect(vm, vm.propertyNames->resolve, resolveFunction, 0); return requireFunction; } } // namespace Bun