aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/CommonJSModuleRecord.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/bindings/CommonJSModuleRecord.cpp')
-rw-r--r--src/bun.js/bindings/CommonJSModuleRecord.cpp379
1 files changed, 194 insertions, 185 deletions
diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp
index a32d722d9..1df7e0edf 100644
--- a/src/bun.js/bindings/CommonJSModuleRecord.cpp
+++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp
@@ -62,6 +62,8 @@
#include <JavaScriptCore/JSMap.h>
#include <JavaScriptCore/JSMapInlines.h>
+#include <JavaScriptCore/JSModuleEnvironment.h>
+#include <JavaScriptCore/SyntheticModuleRecord.h>
namespace Bun {
using namespace JSC;
@@ -247,7 +249,6 @@ const JSC::ClassInfo JSCommonJSModule::s_info = { "Module"_s, &Base::s_info, nul
JSCommonJSModule* createCommonJSModuleObject(
Zig::GlobalObject* globalObject,
- const ResolvedSource& source,
const WTF::String& sourceURL,
JSC::JSValue exportsObjectValue,
JSC::JSValue requireFunctionValue)
@@ -287,6 +288,180 @@ static bool canPerformFastEnumeration(Structure* s)
return true;
}
+JSC::JSValue evaluateCommonJSModule(
+ Zig::GlobalObject* globalObject,
+ JSC::SyntheticModuleRecord* syntheticModuleRecord,
+ EvalExecutable* executable)
+{
+ auto sourceURL = syntheticModuleRecord->moduleKey().string().string();
+ auto& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto* requireMapKey = jsString(vm, sourceURL);
+
+ unsigned int exportCount = syntheticModuleRecord->exportEntries().size();
+ JSC::JSObject* exportsObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
+
+ auto index = sourceURL.reverseFind('/', sourceURL.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);
+
+ JSC::Structure* scopeExtensionObjectStructure = globalObject->commonJSFunctionArgumentsStructure();
+ JSC::JSObject* scopeExtensionObject = JSC::constructEmptyObject(
+ vm,
+ scopeExtensionObjectStructure);
+
+ auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL);
+
+ auto* moduleObject = createCommonJSModuleObject(globalObject,
+ sourceURL,
+ exportsObject,
+ requireFunction);
+
+ scopeExtensionObject->putDirectOffset(
+ vm,
+ 0,
+ moduleObject);
+
+ scopeExtensionObject->putDirectOffset(
+ vm,
+ 1,
+ exportsObject);
+
+ scopeExtensionObject->putDirectOffset(
+ vm,
+ 2,
+ dirname);
+
+ scopeExtensionObject->putDirectOffset(
+ vm,
+ 3,
+ filename);
+
+ scopeExtensionObject->putDirectOffset(
+ vm,
+ 4,
+ requireFunction);
+
+ if (UNLIKELY(throwScope.exception())) {
+ globalObject->requireMap()->remove(globalObject, requireMapKey);
+ throwScope.release();
+ return {};
+ }
+
+ auto catchScope = DECLARE_CATCH_SCOPE(vm);
+
+ // Where the magic happens.
+ //
+ // A `with` scope is created containing { module, exports, require }.
+ // We eval() the CommonJS module code
+ // with that scope.
+ //
+ // Doing it that way saves us a roundtrip through C++ <> JS.
+ //
+ // Sidenote: another implementation could use
+ // FunctionExecutable. It looks like there are lots of arguments
+ // to pass to that and it isn't used directly much, so that
+ // seems harder to do correctly.
+ {
+ JSWithScope* withScope = JSWithScope::create(vm, globalObject, globalObject->globalScope(), scopeExtensionObject);
+ auto* globalExtension = globalObject->globalScopeExtension();
+ globalObject->setGlobalScopeExtension(withScope);
+ vm.interpreter.executeEval(executable, globalObject, globalObject->globalScope());
+ globalObject->setGlobalScopeExtension(globalExtension);
+ syntheticModuleRecord->setUserValue(vm, jsUndefined());
+ }
+
+ if (throwScope.exception()) {
+ globalObject->requireMap()->remove(globalObject, requireMapKey);
+ throwScope.release();
+ return {};
+ }
+
+ JSValue result = moduleObject->exportsObject();
+
+ globalObject->requireMap()->set(globalObject, requireMapKey, result);
+
+ // The developer can do something like:
+ //
+ // Object.defineProperty(module, 'exports', {get: getter})
+ //
+ // In which case, the exports object is now a GetterSetter object.
+ //
+ // 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
+ //
+ result = moduleObject->getIfPropertyExists(globalObject, clientData->builtinNames().exportsPublicName());
+
+ 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);
+ throwScope.release();
+ }
+ }
+
+ auto* moduleEnvironment = syntheticModuleRecord->moduleEnvironment();
+
+ if (result && result.isObject()) {
+ auto* jsExportsObject = jsCast<JSC::JSObject*>(result);
+ auto defaultKeyword = vm.propertyNames->defaultKeyword;
+ for (auto& exportEntry : syntheticModuleRecord->exportEntries()) {
+ PropertyName exportName = exportEntry.value.localName;
+
+ if (exportName == defaultKeyword) {
+ continue;
+ } else if (exportName.isSymbol())
+ continue;
+
+ JSValue exportValue = jsExportsObject->getIfPropertyExists(globalObject, exportName);
+ if (UNLIKELY(catchScope.exception())) {
+ catchScope.clearException();
+ continue;
+ }
+
+ constexpr bool shouldThrowReadOnlyError = false;
+ constexpr bool ignoreReadOnlyErrors = true;
+ bool putResult = false;
+ symbolTablePutTouchWatchpointSet(moduleEnvironment, globalObject, exportName, exportValue, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
+ }
+ }
+
+ if (result) {
+ constexpr bool shouldThrowReadOnlyError = false;
+ constexpr bool ignoreReadOnlyErrors = true;
+ bool putResult = false;
+ PropertyName exportName = vm.propertyNames->defaultKeyword;
+ JSValue exportValue = result;
+ symbolTablePutTouchWatchpointSet(moduleEnvironment, globalObject, exportName, exportValue, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
+ }
+
+ {
+ constexpr bool shouldThrowReadOnlyError = false;
+ constexpr bool ignoreReadOnlyErrors = true;
+ bool putResult = false;
+ PropertyName exportName = Identifier::fromUid(vm.symbolRegistry().symbolForKey("module"_s));
+ JSValue exportValue = moduleObject;
+ symbolTablePutTouchWatchpointSet(moduleEnvironment, globalObject, exportName, exportValue, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
+ }
+
+ return {};
+}
+
JSC::SourceCode createCommonJSModule(
Zig::GlobalObject* globalObject,
ResolvedSource source)
@@ -299,218 +474,52 @@ JSC::SourceCode createCommonJSModule(
[source, sourceURL](JSC::JSGlobalObject* lexicalGlobalObject,
JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4>& exportNames,
- JSC::MarkedArgumentBuffer& exportValues) -> void {
+ JSC::MarkedArgumentBuffer& exportValues) -> JSValue {
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
- auto& vm = globalObject->vm();
- auto throwScope = DECLARE_THROW_SCOPE(vm);
auto sourceCodeString = Zig::toString(source.source_code);
- auto* requireMapKey = jsString(vm, sourceURL);
-
- JSC::JSObject* exportsObject = source.commonJSExportsLen < 64
- ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen)
- : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
-
- auto index = sourceURL.reverseFind('/', sourceURL.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& vm = globalObject->vm();
JSC::SourceCode inputSource(
JSC::StringSourceProvider::create(sourceCodeString,
JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL)),
sourceURL, TextPosition()));
- JSC::Structure* scopeExtensionObjectStructure = globalObject->commonJSFunctionArgumentsStructure();
- JSC::JSObject* scopeExtensionObject = JSC::constructEmptyObject(
- vm,
- scopeExtensionObjectStructure);
-
- auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL);
-
- auto* moduleObject = createCommonJSModuleObject(globalObject,
- source,
- sourceURL,
- exportsObject,
- requireFunction);
- scopeExtensionObject->putDirectOffset(
- vm,
- 0,
- moduleObject);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 1,
- exportsObject);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 2,
- dirname);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 3,
- filename);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 4,
- requireFunction);
-
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* executable = JSC::DirectEvalExecutable::create(
globalObject, inputSource, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None,
false, false, EvalContextType::None, nullptr, nullptr, ECMAMode::sloppy());
if (UNLIKELY(!executable && !throwScope.exception())) {
- // I'm not sure if this case happens, but it's better to be safe than sorry.
- throwSyntaxError(globalObject, throwScope, "Failed to compile CommonJS module."_s);
+ throwSyntaxError(globalObject, throwScope, "Failed to create CommonJS module"_s);
}
if (UNLIKELY(throwScope.exception())) {
- globalObject->requireMap()->remove(globalObject, requireMapKey);
throwScope.release();
- return;
+ return jsUndefined();
}
- auto catchScope = DECLARE_CATCH_SCOPE(vm);
-
- // Where the magic happens.
- //
- // A `with` scope is created containing { module, exports, require }.
- // We eval() the CommonJS module code
- // with that scope.
- //
- // Doing it that way saves us a roundtrip through C++ <> JS.
- //
- // Sidenote: another implementation could use
- // FunctionExecutable. It looks like there are lots of arguments
- // to pass to that and it isn't used directly much, so that
- // seems harder to do correctly.
- {
- JSWithScope* withScope = JSWithScope::create(vm, globalObject, globalObject->globalScope(), scopeExtensionObject);
- vm.interpreter.executeEval(executable, globalObject, withScope);
-
- if (UNLIKELY(catchScope.exception())) {
- auto returnedException = catchScope.exception();
- catchScope.clearException();
- JSC::throwException(globalObject, throwScope, returnedException);
- }
- }
-
- if (throwScope.exception()) {
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- throwScope.release();
- return;
- }
-
- JSValue result = moduleObject->exportsObject();
-
- // The developer can do something like:
- //
- // Object.defineProperty(module, 'exports', {get: getter})
- //
- // In which case, the exports object is now a GetterSetter object.
- //
- // 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
- //
- result = moduleObject->getIfPropertyExists(globalObject, clientData->builtinNames().exportsPublicName());
-
- 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);
- throwScope.release();
- return;
- }
- }
-
- globalObject->requireMap()->set(globalObject, requireMapKey, result);
+ size_t exportNamesLen = source.commonJSExportsLen > 0 && source.commonJSExportsLen < std::numeric_limits<uint32_t>::max() ? source.commonJSExportsLen : 0;
+ exportNames.reserveCapacity(exportNamesLen + 3);
+ exportValues.ensureCapacity(exportNamesLen + 3);
exportNames.append(vm.propertyNames->defaultKeyword);
- exportValues.append(result);
+ exportValues.append(jsUndefined());
// 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));
- // This strong reference exists because otherwise it will crash when the finalizer runs.
+ // This exists to tell ImportMetaObject.ts that this is a CommonJS module.
exportNames.append(Identifier::fromUid(vm.symbolRegistry().symbolForKey("module"_s)));
- exportValues.append(moduleObject);
-
- if (result.isObject()) {
- auto* exports = asObject(result);
-
- auto* structure = exports->structure();
- uint32_t size = structure->inlineSize() + structure->outOfLineSize();
- exportNames.reserveCapacity(size + 3);
- exportValues.ensureCapacity(size + 3);
-
- 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 {
- JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
- exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude);
- if (throwScope.exception()) {
- throwScope.release();
- return;
- }
-
- for (auto property : properties) {
- if (UNLIKELY(property.isEmpty() || property.isNull()))
- continue;
-
- // ignore constructor
- if (property == vm.propertyNames->constructor)
- continue;
-
- if (property.isSymbol() || property.isPrivateName() || property == vm.propertyNames->defaultKeyword)
- 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);
- }
- }
+ exportValues.append(jsUndefined());
+
+ for (size_t i = 0; i < exportNamesLen; i++) {
+ auto str = Zig::toStringCopy(source.commonJSExports[i]);
+ exportNames.append(Identifier::fromString(vm, str));
+ exportValues.append(jsUndefined());
}
+ vm.heap.collectAsync();
+ return executable;
},
SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL)),
sourceURL));