aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/CommonJSModuleRecord.cpp
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-07-11 19:14:34 -0700
committerGravatar GitHub <noreply@github.com> 2023-07-11 19:14:34 -0700
commitcbb88672f217a90db1aa1eb29cd92d5d9035b22b (patch)
tree43a00501f3cde495967e116f0b660777051551f8 /src/bun.js/bindings/CommonJSModuleRecord.cpp
parent1f900cff453700b19bca2acadfe26da4468c1282 (diff)
parent34b0e7a2bbd8bf8097341cdb0075d0908283e834 (diff)
downloadbun-jarred/esm-conditions.tar.gz
bun-jarred/esm-conditions.tar.zst
bun-jarred/esm-conditions.zip
Merge branch 'main' into jarred/esm-conditionsjarred/esm-conditions
Diffstat (limited to 'src/bun.js/bindings/CommonJSModuleRecord.cpp')
-rw-r--r--src/bun.js/bindings/CommonJSModuleRecord.cpp1116
1 files changed, 786 insertions, 330 deletions
diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp
index 1cee1091b..8adba197c 100644
--- a/src/bun.js/bindings/CommonJSModuleRecord.cpp
+++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp
@@ -59,458 +59,914 @@
#include <JavaScriptCore/DFGAbstractHeap.h>
#include <JavaScriptCore/Completion.h>
+#include "ModuleLoader.h"
#include <JavaScriptCore/JSMap.h>
#include <JavaScriptCore/JSMapInlines.h>
#include <JavaScriptCore/GetterSetter.h>
#include "ZigSourceProvider.h"
+#include "JavaScriptCore/FunctionPrototype.h"
+#include "CommonJSModuleRecord.h"
+#include <JavaScriptCore/JSModuleNamespaceObject.h>
+#include <JavaScriptCore/JSSourceCode.h>
+#include <JavaScriptCore/LazyPropertyInlines.h>
namespace Bun {
using namespace JSC;
-class JSCommonJSModule final : public JSC::JSNonFinalObject {
-public:
- using Base = JSC::JSNonFinalObject;
- static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesPut;
+JSC_DECLARE_HOST_FUNCTION(jsFunctionRequireCommonJS);
- mutable JSC::WriteBarrier<JSC::Unknown> m_exportsObject;
- mutable JSC::WriteBarrier<JSC::JSString> m_id;
+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;
+}
- void finishCreation(JSC::VM& vm, JSC::JSValue exportsObject, JSC::JSString* id, JSC::JSString* filename, JSC::JSString* dirname, JSC::JSValue requireFunction)
- {
- Base::finishCreation(vm);
- ASSERT(inherits(vm, info()));
- m_exportsObject.set(vm, this, exportsObject);
- m_id.set(vm, this, id);
+static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObject, JSCommonJSModule* moduleObject, JSString* dirname, JSString* filename, WTF::NakedPtr<Exception>& exception)
+{
+ JSC::Structure* thisObjectStructure = globalObject->commonJSFunctionArgumentsStructure();
+ JSC::JSObject* thisObject = JSC::constructEmptyObject(
+ vm,
+ thisObjectStructure);
+ thisObject->putDirectOffset(
+ vm,
+ 0,
+ moduleObject);
- this->putDirectOffset(
- vm,
- 0,
- exportsObject);
+ thisObject->putDirectOffset(
+ vm,
+ 1,
+ dirname);
- this->putDirectOffset(
- vm,
- 1,
- id);
+ thisObject->putDirectOffset(
+ vm,
+ 2,
+ filename);
- this->putDirectOffset(
- vm,
- 2,
- filename);
+ moduleObject->hasEvaluated = true;
+ globalObject->m_BunCommonJSModuleValue.set(vm, globalObject, thisObject);
- this->putDirectOffset(
- vm,
- 3,
- jsBoolean(false));
+ JSValue empty = JSC::evaluate(globalObject, moduleObject->sourceCode.get()->sourceCode(), thisObject, exception);
+ moduleObject->sourceCode.clear();
- this->putDirectOffset(
- vm,
- 4,
- dirname);
+ return exception.get() == nullptr;
+}
- this->putDirectOffset(
- vm,
- 5,
- jsUndefined());
+JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe))
+{
+ auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm());
+ JSCommonJSModule* moduleObject = jsDynamicCast<JSCommonJSModule*>(callframe->argument(0));
+ if (!moduleObject) {
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true)));
+ }
+
+ if (moduleObject->hasEvaluated || !moduleObject->sourceCode) {
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true)));
}
- static JSC::Structure* createStructure(
+ WTF::NakedPtr<Exception> exception;
+
+ evaluateCommonJSModuleOnce(
+ globalObject->vm(),
+ jsCast<Zig::GlobalObject*>(globalObject),
+ moduleObject,
+ moduleObject->m_dirname.get(),
+ moduleObject->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, moduleObject->id());
+
+ throwException(globalObject, throwScope, exception.get());
+ exception.clear();
+ return JSValue::encode({});
+ }
+
+ 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));
+}
+
+static const HashTableValue RequireResolveFunctionPrototypeValues[] = {
+ { "paths"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, requireResolvePathsFunction, 1 } },
+};
+
+class RequireResolveFunctionPrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+ static RequireResolveFunctionPrototype* create(
JSC::JSGlobalObject* globalObject)
{
auto& vm = globalObject->vm();
- JSC::Structure* structure = JSC::Structure::create(
- vm,
- globalObject,
- globalObject->objectPrototype(),
- JSC::TypeInfo(JSC::ObjectType, JSCommonJSModule::StructureFlags),
- JSCommonJSModule::info(),
- JSC::NonArray,
- 6);
- JSC::PropertyOffset offset;
- auto clientData = WebCore::clientData(vm);
+ auto* structure = RequireResolveFunctionPrototype::createStructure(vm, globalObject, globalObject->functionPrototype());
+ RequireResolveFunctionPrototype* prototype = new (NotNull, JSC::allocateCell<RequireResolveFunctionPrototype>(vm)) RequireResolveFunctionPrototype(vm, structure);
+ prototype->finishCreation(vm);
+ return prototype;
+ }
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "exports"_s),
- 0,
- offset);
+ DECLARE_INFO;
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "id"_s),
- 0,
- offset);
+ RequireResolveFunctionPrototype(
+ JSC::VM& vm,
+ JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "filename"_s),
- 0,
- offset);
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.plainObjectSpace();
+ }
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "loaded"_s),
- 0,
- offset);
+ void finishCreation(JSC::VM& vm);
+};
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "path"_s),
- 0,
- offset);
+static const HashTableValue RequireFunctionPrototypeValues[] = {
+ { "cache"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, Zig::jsRequireCacheGetter, Zig::jsRequireCacheSetter } },
+};
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "require"_s),
- 0,
- offset);
+class RequireFunctionPrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+ static RequireFunctionPrototype* create(
+ JSC::JSGlobalObject* globalObject)
+ {
+ auto& vm = globalObject->vm();
- return structure;
+ auto* structure = RequireFunctionPrototype::createStructure(vm, globalObject, globalObject->functionPrototype());
+ RequireFunctionPrototype* prototype = new (NotNull, JSC::allocateCell<RequireFunctionPrototype>(vm)) RequireFunctionPrototype(vm, structure);
+ prototype->finishCreation(vm);
+
+ JSFunction* resolveFunction = JSFunction::create(vm, moduleRequireResolveCodeGenerator(vm), globalObject->globalScope(), JSFunction::createStructure(vm, globalObject, RequireResolveFunctionPrototype::create(globalObject)));
+ prototype->putDirect(vm, JSC::Identifier::fromString(vm, "resolve"_s), resolveFunction, PropertyAttribute::Function | 0);
+
+ return prototype;
}
- static JSCommonJSModule* create(
+ RequireFunctionPrototype(
JSC::VM& vm,
- JSC::Structure* structure,
- JSC::JSValue exportsObject,
- JSC::JSString* id,
- JSC::JSString* filename,
- JSC::JSString* dirname,
- JSC::JSValue requireFunction)
+ JSC::Structure* structure)
+ : Base(vm, structure)
{
- JSCommonJSModule* cell = new (NotNull, JSC::allocateCell<JSCommonJSModule>(vm)) JSCommonJSModule(vm, structure);
- cell->finishCreation(vm, exportsObject, id, filename, dirname, requireFunction);
- return cell;
}
- JSValue exportsObject()
+ DECLARE_INFO;
+
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
- return m_exportsObject.get();
+ return &vm.plainObjectSpace();
}
- JSValue id()
+ void finishCreation(JSC::VM& vm)
{
- return m_id.get();
+ Base::finishCreation(vm);
+ ASSERT(inherits(vm, info()));
+
+ reifyStaticProperties(vm, info(), RequireFunctionPrototypeValues, *this);
+ JSC::JSFunction* requireDotMainFunction = JSFunction::create(
+ vm,
+ moduleMainCodeGenerator(vm),
+ globalObject()->globalScope());
+
+ this->putDirect(
+ vm,
+ JSC::Identifier::fromString(vm, "main"_s),
+ JSC::GetterSetter::create(vm, globalObject(), requireDotMainFunction, JSValue()),
+ PropertyAttribute::Builtin | PropertyAttribute::Accessor | PropertyAttribute::ReadOnly | 0);
+ this->putDirect(vm, JSC::Identifier::fromString(vm, "extensions"_s), constructEmptyObject(globalObject()), 0);
}
+};
- DECLARE_VISIT_CHILDREN;
+JSC_DEFINE_CUSTOM_GETTER(getterFilename, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
+{
+ JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(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<JSCommonJSModule*>(JSValue::decode(thisValue));
+ if (UNLIKELY(!thisObject)) {
+ return JSValue::encode(jsUndefined());
+ }
+ return JSValue::encode(thisObject->m_id.get());
+}
- static bool put(
- JSC::JSCell* cell,
- JSC::JSGlobalObject* globalObject,
- JSC::PropertyName propertyName,
- JSC::JSValue value,
- JSC::PutPropertySlot& slot)
- {
+JSC_DEFINE_CUSTOM_GETTER(getterPath, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
+{
+ JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
+ if (UNLIKELY(!thisObject)) {
+ return JSValue::encode(jsUndefined());
+ }
+ return JSValue::encode(thisObject->m_id.get());
+}
- auto& vm = globalObject->vm();
- auto* clientData = WebCore::clientData(vm);
- auto throwScope = DECLARE_THROW_SCOPE(vm);
+JSC_DEFINE_CUSTOM_SETTER(setterPath,
+ (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
+ JSC::EncodedJSValue value, JSC::PropertyName propertyName))
+{
+ JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
+ if (!thisObject)
+ return false;
- if (propertyName == clientData->builtinNames().exportsPublicName()) {
- JSCommonJSModule* thisObject = jsCast<JSCommonJSModule*>(cell);
- ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ thisObject->m_id.set(globalObject->vm(), thisObject, JSValue::decode(value).toString(globalObject));
+ return true;
+}
- // It will crash if we attempt to assign Object.defineProperty() result to a JSMap*.
- if (UNLIKELY(slot.thisValue() != thisObject))
- RELEASE_AND_RETURN(throwScope, JSObject::definePropertyOnReceiver(globalObject, propertyName, value, slot));
+extern "C" EncodedJSValue Resolver__propForRequireMainPaths(JSGlobalObject*);
- JSValue prevValue = thisObject->m_exportsObject.get();
+JSC_DEFINE_CUSTOM_GETTER(getterPaths, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
+{
+ JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
+ if (UNLIKELY(!thisObject)) {
+ return JSValue::encode(jsUndefined());
+ }
- // TODO: refactor this to not go through ESM path and we don't need to do this check.
- // IF we do this on every call, it causes GC to happen in a place that it may not be able to.
- // This breaks loading Bluebird in some cases, for example.
- // We need to update the require map "live" because otherwise the code in Discord.js will break
- // The bug is something to do with exception handling which causes GC to happen in the error path and then boom.
- if (prevValue != value && (!prevValue.isCell() || !value.isCell() || prevValue.asCell()->type() != value.asCell()->type())) {
- jsCast<Zig::GlobalObject*>(globalObject)->requireMap()->set(globalObject, thisObject->id(), value);
- }
+ if (!thisObject->m_paths) {
+ JSValue paths = JSValue::decode(Resolver__propForRequireMainPaths(globalObject));
+ thisObject->m_paths.set(globalObject->vm(), thisObject, paths);
+ }
- thisObject->m_exportsObject.set(vm, thisObject, value);
- }
+ return JSValue::encode(thisObject->m_paths.get());
+}
- RELEASE_AND_RETURN(throwScope, Base::put(cell, globalObject, propertyName, value, slot));
+JSC_DEFINE_CUSTOM_SETTER(setterPaths,
+ (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
+ JSC::EncodedJSValue value, JSC::PropertyName propertyName))
+{
+ JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(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<JSCommonJSModule*>(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<JSCommonJSModule*>(JSValue::decode(thisValue));
+ if (!thisObject)
+ return false;
+
+ thisObject->m_id.set(globalObject->vm(), thisObject, JSValue::decode(value).toString(globalObject));
+ return true;
+}
+
+static JSValue createLoaded(VM& vm, JSObject* object)
+{
+ JSCommonJSModule* cjs = jsCast<JSCommonJSModule*>(object);
+ return jsBoolean(cjs->hasEvaluated);
+}
+static JSValue createParent(VM& vm, JSObject* object)
+{
+ return jsUndefined();
+}
+static JSValue createChildren(VM& vm, JSObject* object)
+{
+ return constructEmptyArray(object->globalObject(), nullptr, 0);
+}
+
+static const struct HashTableValue JSCommonJSModulePrototypeTableValues[] = {
+ { "children"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback | PropertyAttribute::DontEnum | 0), NoIntrinsic, { HashTableValue::LazyPropertyType, createChildren } },
+ { "filename"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterFilename, setterFilename } },
+ { "id"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterId, setterId } },
+ { "loaded"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback | PropertyAttribute::DontEnum | 0), NoIntrinsic, { HashTableValue::LazyPropertyType, createLoaded } },
+ { "parent"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback | PropertyAttribute::DontEnum | 0), NoIntrinsic, { HashTableValue::LazyPropertyType, createParent } },
+ { "path"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterPath, setterPath } },
+ { "paths"_s, static_cast<unsigned>(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<JSCommonJSModulePrototype>(vm)) JSCommonJSModulePrototype(vm, structure);
+ prototype->finishCreation(vm, globalObject);
+ return prototype;
}
DECLARE_INFO;
- template<typename, SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+
+ JSCommonJSModulePrototype(
+ JSC::VM& vm,
+ JSC::Structure* structure)
+ : Base(vm, structure)
{
- if constexpr (mode == JSC::SubspaceAccess::Concurrently)
- return nullptr;
- return WebCore::subspaceForImpl<JSCommonJSModule, WebCore::UseCustomHeapCellType::No>(
- vm,
- [](auto& spaces) { return spaces.m_clientSubspaceForCommonJSModuleRecord.get(); },
- [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCommonJSModuleRecord = std::forward<decltype(space)>(space); },
- [](auto& spaces) { return spaces.m_subspaceForCommonJSModuleRecord.get(); },
- [](auto& spaces, auto&& space) { spaces.m_subspaceForCommonJSModuleRecord = std::forward<decltype(space)>(space); });
}
- JSCommonJSModule(JSC::VM& vm, JSC::Structure* structure)
- : Base(vm, structure)
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.plainObjectSpace();
+ }
+
+ void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
+ Base::finishCreation(vm);
+ ASSERT(inherits(vm, info()));
+ reifyStaticProperties(vm, JSCommonJSModule::info(), JSCommonJSModulePrototypeTableValues, *this);
+
+ JSFunction* requireFunction = JSFunction::create(
+ vm,
+ moduleRequireCodeGenerator(vm),
+ globalObject->globalScope(),
+ JSFunction::createStructure(vm, globalObject, RequireFunctionPrototype::create(globalObject)));
+
+ this->putDirect(vm, clientData(vm)->builtinNames().requirePublicName(), requireFunction, PropertyAttribute::Builtin | PropertyAttribute::Function | 0);
+
+ this->putDirectNativeFunction(
+ vm,
+ globalObject,
+ clientData(vm)->builtinNames().requirePrivateName(),
+ 2,
+ jsFunctionRequireCommonJS, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
}
};
-Structure* createCommonJSModuleStructure(
- Zig::GlobalObject* globalObject)
+const JSC::ClassInfo JSCommonJSModulePrototype::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSModulePrototype) };
+
+void JSCommonJSModule::finishCreation(JSC::VM& vm, JSC::JSString* id, JSC::JSString* filename, JSC::JSString* dirname, JSC::JSSourceCode* sourceCode)
{
- return JSCommonJSModule::createStructure(globalObject);
+ Base::finishCreation(vm);
+ ASSERT(inherits(vm, info()));
+ m_id.set(vm, this, id);
+ m_filename.set(vm, this, filename);
+ m_dirname.set(vm, this, dirname);
+ this->sourceCode.set(vm, this, sourceCode);
}
-template<typename Visitor>
-void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+JSC::Structure* JSCommonJSModule::createStructure(
+ JSC::JSGlobalObject* globalObject)
{
- JSCommonJSModule* thisObject = jsCast<JSCommonJSModule*>(cell);
- ASSERT_GC_OBJECT_INHERITS(thisObject, info());
- Base::visitChildren(thisObject, visitor);
- visitor.append(thisObject->m_exportsObject);
- visitor.append(thisObject->m_id);
-}
+ auto& vm = globalObject->vm();
-DEFINE_VISIT_CHILDREN(JSCommonJSModule);
-const JSC::ClassInfo JSCommonJSModule::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSModule) };
+ auto* prototype = JSCommonJSModulePrototype::create(vm, globalObject, JSCommonJSModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
-static bool canPerformFastEnumeration(Structure* s)
+ // 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,
+ JSC::JSString* filename,
+ JSC::JSString* dirname,
+ JSC::JSSourceCode* sourceCode)
{
- 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;
+ JSCommonJSModule* cell = new (NotNull, JSC::allocateCell<JSCommonJSModule>(vm)) JSCommonJSModule(vm, structure);
+ cell->finishCreation(vm, id, filename, dirname, sourceCode);
+ return cell;
}
-JSValue evaluateCommonJSModule(
- Zig::GlobalObject* globalObject,
- Ref<Zig::SourceProvider> sourceProvider,
- const WTF::String& sourceURL,
- ResolvedSource source)
+JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateCommonJSModule, (JSGlobalObject * globalObject, CallFrame* callframe))
{
auto& vm = globalObject->vm();
- auto throwScope = DECLARE_THROW_SCOPE(vm);
- auto* requireMapKey = jsString(vm, sourceURL);
+ auto id = callframe->argument(0).toWTFString(globalObject);
+
+ JSValue object = callframe->argument(1);
+
+ return JSValue::encode(
+ JSCommonJSModule::create(
+ jsCast<Zig::GlobalObject*>(globalObject),
+ id,
+ object, callframe->argument(2).isBoolean() && callframe->argument(2).asBoolean()));
+}
- JSC::JSObject* exportsObject = source.commonJSExportsLen < 64
- ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen)
- : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
- auto index = sourceURL.reverseFind('/', sourceURL.length());
+JSCommonJSModule* JSCommonJSModule::create(
+ Zig::GlobalObject* globalObject,
+ const WTF::String& key,
+ JSValue exportsObject,
+ bool hasEvaluated)
+{
+ auto& vm = globalObject->vm();
+ JSString* requireMapKey = JSC::jsStringWithCache(vm, key);
+ auto index = key.reverseFind('/', key.length());
JSString* dirname = jsEmptyString(vm);
JSString* filename = requireMapKey;
if (index != WTF::notFound) {
dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index);
}
- globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject);
- auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL);
-
- JSC::SourceCode inputSource(
- WTFMove(sourceProvider));
-
- auto* moduleObject = JSCommonJSModule::create(
+ auto* out = JSCommonJSModule::create(
vm,
globalObject->CommonJSModuleObjectStructure(),
- exportsObject,
- requireMapKey, filename, dirname, requireFunction);
+ requireMapKey, filename, dirname, nullptr);
- if (UNLIKELY(throwScope.exception())) {
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- RELEASE_AND_RETURN(throwScope, JSValue());
- }
+ out->putDirect(vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), exportsObject, exportsObject.isCell() && exportsObject.isCallable() ? JSC::PropertyAttribute::Function | 0 : 0);
+ out->hasEvaluated = hasEvaluated;
+ return out;
+}
- JSC::Structure* thisObjectStructure = globalObject->commonJSFunctionArgumentsStructure();
- JSC::JSObject* thisObject = JSC::constructEmptyObject(
- vm,
- thisObjectStructure);
- thisObject->putDirectOffset(
- vm,
- 0,
- moduleObject);
+void JSCommonJSModule::destroy(JSC::JSCell* cell)
+{
+ static_cast<JSCommonJSModule*>(cell)->JSCommonJSModule::~JSCommonJSModule();
+}
- thisObject->putDirectOffset(
- vm,
- 1,
- exportsObject);
+JSCommonJSModule::~JSCommonJSModule()
+{
+}
- thisObject->putDirectOffset(
- vm,
- 2,
- dirname);
+bool JSCommonJSModule::evaluate(
+ Zig::GlobalObject* globalObject,
+ const WTF::String& key,
+ const SyntheticSourceProvider::SyntheticSourceGenerator& generator)
+{
+ Vector<JSC::Identifier, 4> 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);
+
+ bool needsPut = false;
+ auto getDefaultValue = [&]() -> JSValue {
+ size_t defaultValueIndex = propertyNames.find(vm.propertyNames->defaultKeyword);
+ auto cjsSymbol = Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s));
+
+ if (defaultValueIndex != notFound && propertyNames.contains(cjsSymbol)) {
+ JSValue current = arguments.at(defaultValueIndex);
+ needsPut = true;
+ return current;
+ }
- thisObject->putDirectOffset(
- vm,
- 3,
- filename);
+ size_t count = propertyNames.size();
+ JSValue existingDefaultObject = this->getIfPropertyExists(globalObject, WebCore::clientData(vm)->builtinNames().exportsPublicName());
+ JSObject* defaultObject;
- thisObject->putDirectOffset(
- vm,
- 4,
- requireFunction);
+ if (existingDefaultObject && existingDefaultObject.isObject()) {
+ defaultObject = jsCast<JSObject*>(existingDefaultObject);
+ } else {
+ defaultObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
+ needsPut = true;
+ }
- {
- WTF::NakedPtr<Exception> exception;
- globalObject->m_BunCommonJSModuleValue.set(vm, globalObject, thisObject);
- JSC::evaluate(globalObject, inputSource, globalObject->globalThis(), exception);
-
- if (exception.get()) {
- throwScope.throwException(globalObject, exception->value());
- exception.clear();
- RELEASE_AND_RETURN(throwScope, JSValue());
+ for (size_t i = 0; i < count; ++i) {
+ auto prop = propertyNames[i];
+ unsigned attributes = 0;
+
+ JSValue value = arguments.at(i);
+
+ if (prop.isSymbol()) {
+ attributes |= JSC::PropertyAttribute::DontEnum;
+ }
+
+ if (value.isCell() && value.isCallable()) {
+ attributes |= JSC::PropertyAttribute::Function;
+ }
+
+ defaultObject->putDirect(vm, prop, value, attributes);
+ }
+
+ return defaultObject;
+ };
+
+ JSValue defaultValue = getDefaultValue();
+ if (needsPut) {
+ unsigned attributes = 0;
+
+ if (defaultValue.isCell() && defaultValue.isCallable()) {
+ attributes |= JSC::PropertyAttribute::Function;
}
- }
- if (UNLIKELY(throwScope.exception())) {
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- RELEASE_AND_RETURN(throwScope, JSValue());
+ this->putDirect(vm, WebCore::clientData(vm)->builtinNames().exportsPublicName(), defaultValue, attributes);
}
- JSValue result = moduleObject->exportsObject();
+ this->hasEvaluated = true;
+ RELEASE_AND_RETURN(throwScope, true);
+}
+
+void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject,
+ JSC::Identifier moduleKey,
+ Vector<JSC::Identifier, 4>& exportNames,
+ JSC::MarkedArgumentBuffer& exportValues)
+{
+ auto result = this->exportsObject();
+
+ auto& vm = globalObject->vm();
+
+ // This exists to tell ImportMetaObject.ts that this is a CommonJS module.
+ exportNames.append(Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s)));
+ exportValues.append(jsNumber(0));
- // The developer can do something like:
+ // Bun's intepretation of the "__esModule" annotation:
//
- // Object.defineProperty(module, 'exports', {get: getter})
+ // - If a "default" export does not exist OR the __esModule annotation is not present, then we
+ // set the default export to the exports object
//
- // In which case, the exports object is now a GetterSetter object.
+ // - If a "default" export also exists, then we set the default export
+ // to the value of it (matching Babel behavior)
//
- // We can't return a GetterSetter object to ESM code, so we need to call it.
- if (!result.isEmpty() && (result.isGetterSetter() || result.isCustomGetterSetter())) {
- auto* clientData = WebCore::clientData(vm);
-
- // TODO: is there a faster way to call these getters? We shouldn't need to do a full property lookup.
- //
- // we use getIfPropertyExists just incase a pathological devleoper did:
- //
- // - Object.defineProperty(module, 'exports', {get: getter})
- // - delete module.exports
- //
- if (result.isGetterSetter()) {
- JSC::GetterSetter* getter = jsCast<JSC::GetterSetter*>(result);
- result = getter->callGetter(globalObject, moduleObject);
- } else {
- result = moduleObject->getIfPropertyExists(globalObject, clientData->builtinNames().exportsPublicName());
+ // 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 (result.isObject()) {
+ auto* exports = asObject(result);
+
+ 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);
+
+ Identifier esModuleMarker = builtinNames(vm).__esModulePublicName();
+ bool hasESModuleMarker = !this->ignoreESModuleAnnotation && exports->hasProperty(globalObject, esModuleMarker);
+ if (catchScope.exception()) {
+ catchScope.clearException();
}
- if (UNLIKELY(throwScope.exception())) {
- // Unlike getters on properties of the exports object
- // When the exports object itself is a getter and it throws
- // There's not a lot we can do
- // so we surface that error
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- RELEASE_AND_RETURN(throwScope, JSValue());
+ if (hasESModuleMarker) {
+ if (canPerformFastEnumeration(structure)) {
+ exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
+ auto key = entry.key();
+ if (key->isSymbol() || entry.attributes() & PropertyAttribute::DontEnum || 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;
+
+ 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() || entry.attributes() & PropertyAttribute::DontEnum || 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::Exclude);
+ 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;
+
+ 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);
+ }
}
}
- globalObject->requireMap()->set(globalObject, requireMapKey, result);
+ if (needsToAssignDefault) {
+ exportNames.append(vm.propertyNames->defaultKeyword);
+ exportValues.append(result);
+ }
+}
+
+JSValue JSCommonJSModule::exportsObject()
+{
+ return this->get(globalObject(), JSC::PropertyName(clientData(vm())->builtinNames().exportsPublicName()));
+}
- return result;
+JSValue JSCommonJSModule::id()
+{
+ return m_id.get();
+}
+
+bool JSCommonJSModule::put(
+ JSC::JSCell* cell,
+ JSC::JSGlobalObject* globalObject,
+ JSC::PropertyName propertyName,
+ JSC::JSValue value,
+ JSC::PutPropertySlot& slot)
+{
+
+ auto& vm = globalObject->vm();
+ auto* clientData = WebCore::clientData(vm);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ RELEASE_AND_RETURN(throwScope, Base::put(cell, globalObject, propertyName, value, slot));
+}
+
+template<typename, SubspaceAccess mode> JSC::GCClient::IsoSubspace* JSCommonJSModule::subspaceFor(JSC::VM& vm)
+{
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<JSCommonJSModule, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForCommonJSModuleRecord.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCommonJSModuleRecord = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForCommonJSModuleRecord.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForCommonJSModuleRecord = std::forward<decltype(space)>(space); });
+}
+
+Structure* createCommonJSModuleStructure(
+ Zig::GlobalObject* globalObject)
+{
+ return JSCommonJSModule::createStructure(globalObject);
}
-JSC::SourceCode createCommonJSModule(
+template<typename Visitor>
+void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ JSCommonJSModule* thisObject = jsCast<JSCommonJSModule*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->m_id);
+ visitor.append(thisObject->sourceCode);
+ visitor.append(thisObject->m_filename);
+ visitor.append(thisObject->m_dirname);
+ visitor.append(thisObject->m_paths);
+}
+
+DEFINE_VISIT_CHILDREN(JSCommonJSModule);
+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<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(callframe->thisValue());
+ if (!thisObject)
+ return throwVMTypeError(globalObject, throwScope);
+
+ JSValue specifierValue = callframe->argument(0);
+ WTF::String specifier = specifierValue.toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(throwScope, {});
+
+ // Special-case for "process" to just return the process object directly.
+ if (UNLIKELY(specifier == "process"_s || specifier == "node:process"_s)) {
+ jsDynamicCast<JSCommonJSModule*>(callframe->argument(1))->putDirect(vm, builtinNames(vm).exportsPublicName(), globalObject->processObject(), 0);
+ return JSValue::encode(globalObject->processObject());
+ }
+
+ WTF::String referrer = thisObject->id().toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(throwScope, {});
+
+ BunString specifierStr = Bun::toString(specifier);
+ BunString referrerStr = Bun::toString(referrer);
+
+ JSValue fetchResult = Bun::fetchCommonJSModule(
+ globalObject,
+ jsDynamicCast<JSCommonJSModule*>(callframe->argument(1)),
+ specifierValue,
+ &specifierStr,
+ &referrerStr);
+
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(fetchResult));
+}
+
+void RequireResolveFunctionPrototype::finishCreation(JSC::VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(vm, info()));
+
+ reifyStaticProperties(vm, RequireResolveFunctionPrototype::info(), RequireResolveFunctionPrototypeValues, *this);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+bool JSCommonJSModule::evaluate(
Zig::GlobalObject* globalObject,
+ const WTF::String& key,
ResolvedSource source)
{
- auto sourceURL = Zig::toStringCopy(source.source_url);
- auto sourceProvider = Zig::SourceProvider::create(globalObject, source, JSC::SourceProviderSourceType::Program);
+ auto& vm = globalObject->vm();
+ auto sourceProvider = Zig::SourceProvider::create(jsCast<Zig::GlobalObject*>(globalObject), source, JSC::SourceProviderSourceType::Program);
+ this->ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule;
+ JSC::SourceCode rawInputSource(
+ WTFMove(sourceProvider));
- return JSC::SourceCode(
- JSC::SyntheticSourceProvider::create(
- [source, sourceProvider = WTFMove(sourceProvider), sourceURL](JSC::JSGlobalObject* globalObject,
- JSC::Identifier moduleKey,
- Vector<JSC::Identifier, 4>& exportNames,
- JSC::MarkedArgumentBuffer& exportValues) -> void {
- JSValue result = evaluateCommonJSModule(
- jsCast<Zig::GlobalObject*>(globalObject),
- WTFMove(sourceProvider),
- sourceURL,
- source);
+ if (this->hasEvaluated)
+ return true;
- if (!result) {
- return;
- }
+ this->sourceCode.set(vm, this, JSC::JSSourceCode::create(vm, WTFMove(rawInputSource)));
- auto& vm = globalObject->vm();
+ WTF::NakedPtr<JSC::Exception> exception;
- exportNames.append(vm.propertyNames->defaultKeyword);
- exportValues.append(result);
-
- // This exists to tell ImportMetaObject.ts that this is a CommonJS module.
- exportNames.append(Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s)));
- exportValues.append(jsNumber(0));
-
- if (result.isObject()) {
- DeferGCForAWhile deferGC(vm);
- auto* exports = asObject(result);
-
- auto* structure = exports->structure();
- uint32_t size = structure->inlineSize() + structure->outOfLineSize();
- exportNames.reserveCapacity(size + 2);
- exportValues.ensureCapacity(size + 2);
-
- if (canPerformFastEnumeration(structure)) {
- exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
- auto key = entry.key();
- if (key->isSymbol() || key == vm.propertyNames->defaultKeyword || entry.attributes() & PropertyAttribute::DontEnum)
- return true;
-
- exportNames.append(Identifier::fromUid(vm, key));
-
- JSValue value = exports->getDirect(entry.offset());
-
- exportValues.append(value);
- return true;
- });
- } else {
- auto catchScope = DECLARE_CATCH_SCOPE(vm);
- JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
- exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude);
- if (catchScope.exception()) {
- catchScope.clearExceptionExceptTermination();
- return;
- }
+ 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<JSC::SourceCode> createCommonJSModule(
+ Zig::GlobalObject* globalObject,
+ ResolvedSource source)
+{
+ JSCommonJSModule* moduleObject;
+ WTF::String sourceURL = toStringCopy(source.source_url);
+
+ JSValue specifierValue = Bun::toJS(globalObject, source.specifier);
+ JSValue entry = globalObject->requireMap()->get(globalObject, specifierValue);
- for (auto property : properties) {
- if (UNLIKELY(property.isEmpty() || property.isNull() || property.isPrivateName() || property.isSymbol()))
- continue;
+ auto sourceProvider = Zig::SourceProvider::create(jsCast<Zig::GlobalObject*>(globalObject), source, JSC::SourceProviderSourceType::Program);
+ bool ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule;
+ SourceOrigin sourceOrigin = sourceProvider->sourceOrigin();
- // ignore constructor
- if (property == vm.propertyNames->constructor || property == vm.propertyNames->defaultKeyword)
- continue;
+ if (entry) {
+ moduleObject = jsDynamicCast<JSCommonJSModule*>(entry);
+ }
+
+ 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);
+ }
- JSC::PropertySlot slot(exports, PropertySlot::InternalMethodType::Get);
- if (!exports->getPropertySlot(globalObject, property, slot))
- continue;
+ JSC::SourceCode rawInputSource(
+ WTFMove(sourceProvider));
- exportNames.append(property);
+ moduleObject = JSCommonJSModule::create(
+ vm,
+ globalObject->CommonJSModuleObjectStructure(),
+ requireMapKey, filename, dirname, JSC::JSSourceCode::create(vm, WTFMove(rawInputSource)));
- JSValue getterResult = slot.getValue(globalObject, property);
+ moduleObject->putDirect(vm,
+ WebCore::clientData(vm)->builtinNames().exportsPublicName(),
+ JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()), 0);
- // 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();
- }
+ globalObject->requireMap()->set(globalObject, requireMapKey, moduleObject);
+ }
+
+ moduleObject->ignoreESModuleAnnotation = ignoreESModuleAnnotation;
+
+ return JSC::SourceCode(
+ JSC::SyntheticSourceProvider::create(
+ [](JSC::JSGlobalObject* lexicalGlobalObject,
+ JSC::Identifier moduleKey,
+ Vector<JSC::Identifier, 4>& exportNames,
+ JSC::MarkedArgumentBuffer& exportValues) -> void {
+ auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto& vm = globalObject->vm();
- exportValues.append(getterResult);
+ JSValue keyValue = identifierToJSValue(vm, moduleKey);
+ JSValue entry = globalObject->requireMap()->get(globalObject, keyValue);
+
+ if (entry) {
+ if (auto* moduleObject = jsDynamicCast<JSCommonJSModule*>(entry)) {
+ if (!moduleObject->hasEvaluated) {
+ WTF::NakedPtr<JSC::Exception> 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);
}
}
},
- SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL)),
+ sourceOrigin,
sourceURL));
}
-
} \ No newline at end of file