diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index baf862139f..e564df962a 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -103,7 +103,7 @@ static bool canPerformFastEnumeration(Structure* s) 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) +static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* functionGlobalObject, JSCommonJSModule* moduleObject, JSString* dirname, JSValue filename, WTF::NakedPtr& exception) { SourceCode code = std::move(moduleObject->sourceCode); @@ -152,7 +152,7 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj } // This will return 0 if there was a syntax error or an allocation failure - JSValue fnValue = JSC::evaluate(globalObject, code, jsUndefined(), exception); + JSValue fnValue = JSC::evaluate(functionGlobalObject, code, jsUndefined(), exception); if (UNLIKELY(exception.get() || fnValue.isEmpty())) { return false; @@ -180,12 +180,12 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj // // fn(exports, require, module, __filename, __dirname) { /* code */ }(exports, require, module, __filename, __dirname) // - JSC::call(globalObject, fn, callData, moduleObject, args, exception); + JSC::call(functionGlobalObject, fn, callData, moduleObject, args, exception); return exception.get() == nullptr; } -bool JSCommonJSModule::load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::NakedPtr& exception) +bool JSCommonJSModule::load(JSC::VM& vm, Zig::GlobalObject* globalObject, JSGlobalObject* functionGlobalObject, WTF::NakedPtr& exception) { if (this->hasEvaluated || this->sourceCode.isNull()) { return true; @@ -193,16 +193,19 @@ bool JSCommonJSModule::load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::N evaluateCommonJSModuleOnce( globalObject->vm(), - jsCast(globalObject), + globalObject, + functionGlobalObject, 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()); + if (LIKELY(functionGlobalObject == globalObject)) { + // 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; } @@ -213,7 +216,7 @@ bool JSCommonJSModule::load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::N JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalObject, CallFrame* callframe)) { auto& vm = lexicalGlobalObject->vm(); - auto* globalObject = jsCast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto throwScope = DECLARE_THROW_SCOPE(vm); JSCommonJSModule* moduleObject = jsDynamicCast(callframe->argument(0)); if (!moduleObject) { @@ -222,7 +225,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionLoadModule, (JSGlobalObject * lexicalGlobalOb WTF::NakedPtr exception; - if (!moduleObject->load(vm, globalObject, exception)) { + if (!moduleObject->load(vm, globalObject, lexicalGlobalObject, exception)) { throwException(globalObject, throwScope, exception.get()); exception.clear(); return {}; @@ -500,7 +503,7 @@ static JSValue createChildren(VM& vm, JSObject* object) return constructEmptyArray(object->globalObject(), nullptr, 0); } -JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * globalObject, CallFrame* callframe)) +JSC_DEFINE_HOST_FUNCTION(jsFunctionCommonJSModuleRecord_compile, (JSGlobalObject * globalObject, CallFrame* callframe)) { auto* moduleObject = jsDynamicCast(callframe->thisValue()); if (!moduleObject) { @@ -543,7 +546,8 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * WTF::NakedPtr exception; evaluateCommonJSModuleOnce( vm, - jsCast(globalObject), + defaultGlobalObject(globalObject), + globalObject, moduleObject, jsString(vm, dirnameString), jsString(vm, filenameString), @@ -559,7 +563,7 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * } static const struct HashTableValue JSCommonJSModulePrototypeTableValues[] = { - { "_compile"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, functionCommonJSModuleRecord_compile, 2 } }, + { "_compile"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionCommonJSModuleRecord_compile, 2 } }, { "children"_s, static_cast(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, createChildren } }, { "filename"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterFilename, setterFilename } }, { "id"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterId, setterId } }, @@ -1074,7 +1078,7 @@ bool JSCommonJSModule::evaluate( this->sourceCode = JSC::SourceCode(WTFMove(sourceProvider)); WTF::NakedPtr exception; - evaluateCommonJSModuleOnce(vm, globalObject, this, this->m_dirname.get(), this->m_filename.get(), exception); + evaluateCommonJSModuleOnce(vm, globalObject, globalObject, this, this->m_dirname.get(), this->m_filename.get(), exception); if (exception.get()) { // On error, remove the module from the require map/ @@ -1157,6 +1161,7 @@ std::optional createCommonJSModule( if (!evaluateCommonJSModuleOnce( vm, globalObject, + globalObject, moduleObject, moduleObject->m_dirname.get(), moduleObject->m_filename.get(), exception)) { diff --git a/src/bun.js/bindings/CommonJSModuleRecord.h b/src/bun.js/bindings/CommonJSModuleRecord.h index 70721f0842..e8dc9bb70e 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.h +++ b/src/bun.js/bindings/CommonJSModuleRecord.h @@ -23,6 +23,7 @@ using namespace JSC; JSC_DECLARE_HOST_FUNCTION(jsFunctionCreateCommonJSModule); JSC_DECLARE_HOST_FUNCTION(jsFunctionLoadModule); +JSC_DECLARE_HOST_FUNCTION(jsFunctionCommonJSModuleRecord_compile); void populateESMExports( JSC::JSGlobalObject* globalObject, @@ -108,7 +109,7 @@ public: void setExportsObject(JSC::JSValue exportsObject); JSValue id(); - bool load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::NakedPtr&); + bool load(JSC::VM& vm, Zig::GlobalObject* globalObject, JSGlobalObject* functionGlobalObject, WTF::NakedPtr&); DECLARE_INFO; DECLARE_VISIT_CHILDREN; diff --git a/src/bun.js/modules/NodeModuleModule.cpp b/src/bun.js/modules/NodeModuleModule.cpp index 46d0cf36fb..eacae50252 100644 --- a/src/bun.js/modules/NodeModuleModule.cpp +++ b/src/bun.js/modules/NodeModuleModule.cpp @@ -15,6 +15,7 @@ #include "NodeModuleModule.h" +#include "CommonJSModuleRecord.h" #include "ErrorCode.h" #include #include @@ -624,10 +625,30 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionInitPaths, (JSGlobalObject * globalObject, return JSC::JSValue::encode(JSC::jsUndefined()); } +JSC_DEFINE_CUSTOM_GETTER(getterModuleConstructor, + (JSGlobalObject * lexicalGlobalObject, + JSC::EncodedJSValue thisValue, + JSC::PropertyName propertyName)) { + auto *globalObject = defaultGlobalObject(lexicalGlobalObject); + + return JSValue::encode( + globalObject->m_nodeModuleConstructor.getInitializedOnMainThread( + globalObject)); +} + +JSC_DEFINE_CUSTOM_SETTER(setterModuleConstructor, + (JSGlobalObject * lexicalGlobalObject, + JSC::EncodedJSValue thisValue, + JSC::EncodedJSValue value, + JSC::PropertyName propertyName)) { + return false; +} + static JSValue getModulePrototypeObject(VM &vm, JSObject *moduleObject) { auto *globalObject = defaultGlobalObject(moduleObject->globalObject()); auto prototype = - constructEmptyObject(globalObject, globalObject->objectPrototype(), 2); + constructEmptyObject(globalObject, globalObject->objectPrototype(), 3); + prototype->structure()->setMayBePrototype(true); prototype->putDirectCustomAccessor( vm, WebCore::clientData(vm)->builtinNames().requirePublicName(), @@ -635,6 +656,18 @@ static JSValue getModulePrototypeObject(VM &vm, JSObject *moduleObject) { setterRequireFunction), 0); + prototype->putDirectCustomAccessor( + vm, vm.propertyNames->constructor, + JSC::CustomGetterSetter::create(vm, getterModuleConstructor, + setterModuleConstructor), + PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + + prototype->putDirectNativeFunction( + vm, globalObject, Identifier::fromString(vm, "_compile"_s), 1, + jsFunctionCommonJSModuleRecord_compile, + JSC::ImplementationVisibility::Public, NoIntrinsic, + JSC::PropertyAttribute::DontEnum | 0); + return prototype; } diff --git a/test/js/node/module/node-module-module.test.js b/test/js/node/module/node-module-module.test.js index 9c44c9656e..798df24ef3 100644 --- a/test/js/node/module/node-module-module.test.js +++ b/test/js/node/module/node-module-module.test.js @@ -23,6 +23,14 @@ test("module.globalPaths exists", () => { expect(Array.isArray(require("module").globalPaths)).toBe(true); }); +test("_compile exists", () => { + const m = new Module("asdf"); + Module.prototype._compile.call(m, "exports.foo = 1; return 42", "asdf"); + expect(m.exports.foo).toBe(1); + Module.prototype._compile.call(m, "exports.foo = 2; return 42", "asdf"); + expect(m.exports.foo).toBe(2); +}); + test("createRequire trailing slash", () => { const req = createRequire(import.meta.dir + "/"); expect(req.resolve("./node-module-module.test.js")).toBe(