Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ declare function $redirect(): TODO;
declare function $relative(): TODO;
declare function $require(): TODO;
declare function $requireESM(path: string): any;
declare function $requireESMIntoModule(this: JSCommonJSModule, id: string): void;
declare const $requireMap: Map<string, JSCommonJSModule>;
declare const $internalModuleRegistry: InternalFieldObject<any[]>;
declare function $resolve(name: string, from: string): Promise<string>;
Expand Down
1 change: 1 addition & 0 deletions src/js/builtins/BunBuiltinNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ using namespace JSC;
macro(removeAbortAlgorithmFromSignal) \
macro(require) \
macro(requireESM) \
macro(requireESMIntoModule) \
macro(requireMap) \
macro(requireNativeModule) \
macro(resolveSync) \
Expand Down
74 changes: 22 additions & 52 deletions src/js/builtins/CommonJS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,34 +106,8 @@ export function overridableRequire(this: JSCommonJSModule, originalId: string, o

// -1 means we need to lookup the module from the ESM registry.
if (out === -1) {
try {
out = $requireESM(id);
} catch (exception) {
// Since the ESM code is mostly JS, we need to handle exceptions here.
$requireMap.$delete(id);
throw exception;
}

// If we can pull out a ModuleNamespaceObject, let's do it.
const namespace = $esmNamespaceForCjs(id);
if (namespace !== undefined) {
// In Bun, when __esModule is not defined, it's a CustomAccessor on the prototype.
// Various libraries expect __esModule to be set when using ESM from require().
// We don't want to always inject the __esModule export into every module,
// And creating an Object wrapper causes the actual exports to not be own properties.
// So instead of either of those, we make it so that the __esModule property can be set at runtime.
// It only supports "true" and undefined. Anything non-truthy is treated as undefined.
// https://github.com/oven-sh/bun/issues/14411
if (namespace.__esModule === undefined) {
try {
namespace.__esModule = true;
} catch {
// https://github.com/oven-sh/bun/issues/17816
}
}

return (mod.exports = namespace["module.exports"] ?? namespace);
}
$requireESMIntoModule.$call(mod, id);
return mod.exports;
}

const c = $evaluateCommonJSModule(mod, this);
Expand Down Expand Up @@ -313,49 +287,45 @@ function loadEsmIntoCjs__dead(resolvedSpecifier: string) {
}
*/

// Also bound as import.meta.bakeBuiltin, so this returns the namespace unshaped.
$visibility = "Private";
export function requireESM(this, resolved: string) {
var exports = $esmNamespaceForCjs(resolved);
if (exports === undefined) {
exports = $loadEsmIntoCjs(resolved);
}
if (exports === undefined) {
throw new TypeError(`require() failed to evaluate module "${resolved}". This is an internal consistentency error.`);
}
return exports;
}

export function requireESMFromHijackedExtension(this: JSCommonJSModule, id: string) {
// Also called from C++ by the builtin Module._extensions loaders (JSCommonJSExtensions.cpp).
$visibility = "Private";
export function requireESMIntoModule(this: JSCommonJSModule, id: string) {
$assert(this);
let namespace: any;
try {
$requireESM(id);
namespace = $requireESM(id);
} catch (exception) {
// Since the ESM code is mostly JS, we need to handle exceptions here.
$requireMap.$delete(id);
throw exception;
}

// If we can pull out a ModuleNamespaceObject, let's do it.
const namespace = $esmNamespaceForCjs(id);
if (namespace !== undefined) {
// In Bun, when __esModule is not defined, it's a CustomAccessor on the prototype.
// Various libraries expect __esModule to be set when using ESM from require().
// We don't want to always inject the __esModule export into every module,
// And creating an Object wrapper causes the actual exports to not be own properties.
// So instead of either of those, we make it so that the __esModule property can be set at runtime.
// It only supports "true" and undefined. Anything non-truthy is treated as undefined.
// https://github.com/oven-sh/bun/issues/14411
if (namespace.__esModule === undefined) {
try {
namespace.__esModule = true;
} catch {
// https://github.com/oven-sh/bun/issues/17816
}
// In Bun, when __esModule is not defined, it's a CustomAccessor on the prototype.
// Various libraries expect __esModule to be set when using ESM from require().
// We don't want to always inject the __esModule export into every module,
// And creating an Object wrapper causes the actual exports to not be own properties.
// So instead of either of those, we make it so that the __esModule property can be set at runtime.
// It only supports "true" and undefined. Anything non-truthy is treated as undefined.
// https://github.com/oven-sh/bun/issues/14411
Comment thread
robobun marked this conversation as resolved.
if (namespace.__esModule === undefined) {
try {
namespace.__esModule = true;
} catch {
// https://github.com/oven-sh/bun/issues/17816
}

this.exports = namespace["module.exports"] ?? namespace;
return;
}

this.exports = namespace["module.exports"] ?? namespace;
}

$visibility = "Private";
Expand Down
6 changes: 3 additions & 3 deletions src/jsc/bindings/JSCommonJSExtensions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,13 @@ JSC::EncodedJSValue builtinLoader(JSC::JSGlobalObject* globalObject, JSC::CallFr
RETURN_IF_EXCEPTION(scope, {});
if (result == jsNumber(-1)) {
// ESM
JSC::JSFunction* requireESM = global->requireESMFromHijackedExtension();
JSC::JSFunction* requireESMIntoModule = global->requireESMIntoModuleFunction();
JSC::MarkedArgumentBuffer args;
args.append(specifier);
JSC::CallData callData = JSC::getCallData(requireESM);
JSC::CallData callData = JSC::getCallData(requireESMIntoModule);
ASSERT(callData.type == JSC::CallData::Type::JS);
NakedPtr<JSC::Exception> returnedException = nullptr;
JSC::profiledCall(global, JSC::ProfilingReason::API, requireESM, callData, mod, args, returnedException);
JSC::profiledCall(global, JSC::ProfilingReason::API, requireESMIntoModule, callData, mod, args, returnedException);
if (returnedException) [[unlikely]] {
throwException(globalObject, scope, returnedException->value());
return {};
Expand Down
50 changes: 31 additions & 19 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,12 @@ static bool isModuleEvaluated(JSC::AbstractModuleRecord* record)
return record->moduleEnvironmentMayBeNull() != nullptr;
}

// The entry loadModuleSync() loads; registryEntry() would also match one registered by an import with a type attribute.
static JSC::ModuleRegistryEntry* javaScriptRegistryEntry(JSC::JSModuleLoader* loader, const JSC::Identifier& key)
{
return loader->moduleMap().get({ key.impl(), JSC::ScriptFetchParameters::Type::JavaScript }).get();
}

JSC_DEFINE_HOST_FUNCTION(functionEsmNamespaceForCjs, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
auto& vm = JSC::getVM(globalObject);
Expand Down Expand Up @@ -825,18 +831,26 @@ JSC_DEFINE_HOST_FUNCTION(functionEsmLoadSync, (JSC::JSGlobalObject * lexicalGlob

auto* loader = globalObject->moduleLoader();
bool entryExistedBefore = false;
if (auto* entry = loader->registryEntry(key)) {
if (auto* existing = loader->registryEntry(key)) {
entryExistedBefore = true;
if (isModuleEvaluated(entry->record())) {
auto* ns = entry->record()->getModuleNamespace(globalObject, false);
if (isModuleEvaluated(existing->record())) {
auto* ns = existing->record()->getModuleNamespace(globalObject, false);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(ns);
}
}

// Held across the load: the module's top level may `delete require.cache[key]`, which only unlinks the entry from the registry.
JSC::ModuleRegistryEntry* entry = javaScriptRegistryEntry(loader, key);

JSPromise* promise = loader->loadModuleSync(globalObject, key, nullptr, nullptr);
RETURN_IF_EXCEPTION(scope, {});

// Builtin ES modules and module mocks have no entry until the load creates it.
if (!entry)
entry = javaScriptRegistryEntry(loader, key);
JSC::AbstractModuleRecord* record = entry ? entry->record() : nullptr;

switch (promise->status()) {
case JSPromise::Status::Fulfilled:
break;
Expand All @@ -859,12 +873,10 @@ JSC_DEFINE_HOST_FUNCTION(functionEsmLoadSync, (JSC::JSGlobalObject * lexicalGlob
// it OR any dependency has top-level await, in which case bindings can
// still be in TDZ and we must throw the "async module" error instead
// of returning a half-initialized namespace.
if (auto* entry = loader->registryEntry(key)) {
if (auto* cyclic = dynamicDowncast<JSC::CyclicModuleRecord>(entry->record())) {
auto status = cyclic->status();
if ((status == JSC::CyclicModuleRecord::Status::Evaluating || status == JSC::CyclicModuleRecord::Status::Evaluated) && !cyclic->hasTLA() && !cyclic->evaluationError())
break;
}
if (auto* cyclic = dynamicDowncast<JSC::CyclicModuleRecord>(record)) {
auto status = cyclic->status();
if ((status == JSC::CyclicModuleRecord::Status::Evaluating || status == JSC::CyclicModuleRecord::Status::Evaluated) && !cyclic->hasTLA() && !cyclic->evaluationError())
break;
}
// Only drop the entry we created. If the entry already existed (an
// outer import() is mid-load, or the module is EvaluatingAsync from a
Expand All @@ -878,9 +890,8 @@ JSC_DEFINE_HOST_FUNCTION(functionEsmLoadSync, (JSC::JSGlobalObject * lexicalGlob
}
}

auto* entry = loader->registryEntry(key);
if (!entry || !entry->record()) [[unlikely]]
return throwVMTypeError(globalObject, scope, makeString("require() failed to evaluate module \""_s, keyString, "\". This is an internal consistentency error."_s));
if (!record) [[unlikely]]
return throwVMTypeError(globalObject, scope, makeString("require() failed to evaluate module \""_s, keyString, "\". This is an internal consistency error."_s));

// The loadModule promise resolved, so the entire graph linked + evaluated
// synchronously. We deliberately do NOT gate on CyclicModuleRecord::status()
Expand All @@ -891,7 +902,6 @@ JSC_DEFINE_HOST_FUNCTION(functionEsmLoadSync, (JSC::JSGlobalObject * lexicalGlob
// the namespace in that state matches the old loader's behaviour and Node's
// require(esm) cycle semantics. evaluationError() still surfaces a real
// throw from the module body.
auto* record = entry->record();
if (auto* cyclic = dynamicDowncast<JSC::CyclicModuleRecord>(record)) {
if (JSValue err = cyclic->evaluationError()) {
scope.throwException(globalObject, err);
Expand Down Expand Up @@ -2958,17 +2968,17 @@ JSC_DEFINE_CUSTOM_GETTER(getConsoleStderr, (JSGlobalObject * globalObject, Encod
return JSValue::encode(stderrValue);
}

// The CommonJS `require()` machinery (`@requireESM`, `@loadEsmIntoCjs`,
// `@internalRequire`) is only ever reached from inside CommonJS modules. A
// The CommonJS `require()` machinery (`@requireESM`, `@requireESMIntoModule`,
// `@loadEsmIntoCjs`, `@internalRequire`) is only ever reached from inside CommonJS modules. A
Comment thread
robobun marked this conversation as resolved.
// process whose entry point is ESM (the common case for short scripts) never
// touches it, so parsing/compiling these builtins during global object setup is
// pure startup overhead. Register lazy custom-value getters instead: the first
// `@`-reference from builtin code materializes the function and replaces the
// accessor with the plain builtin function for subsequent fast access.
//
// Note: these private names are only consumed via `op_get_from_scope` from
// builtin JS (`$requireESM`, `$loadEsmIntoCjs`, `$internalRequire`), never via
// `getDirect`, so a custom accessor is a transparent substitute. (`@create*ReadableStream`
// builtin JS (`$requireESM`, `$requireESMIntoModule`, `$loadEsmIntoCjs`, `$internalRequire`),
// never via `getDirect`, so a custom accessor is a transparent substitute. (`@create*ReadableStream`
Comment thread
robobun marked this conversation as resolved.
// next to these *do* have `getDirect` callers and must stay eager.)
#define BUN_DEFINE_LAZY_GLOBAL_BUILTIN_GETTER(getterName, codeGenerator, attributes) \
JSC_DEFINE_CUSTOM_GETTER(getterName, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue, PropertyName name)) \
Expand All @@ -2979,6 +2989,7 @@ JSC_DEFINE_CUSTOM_GETTER(getConsoleStderr, (JSGlobalObject * globalObject, Encod
return JSValue::encode(fn); \
}
BUN_DEFINE_LAZY_GLOBAL_BUILTIN_GETTER(getRequireESMBuiltin, commonJSRequireESMCodeGenerator, PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly)
BUN_DEFINE_LAZY_GLOBAL_BUILTIN_GETTER(getRequireESMIntoModuleBuiltin, commonJSRequireESMIntoModuleCodeGenerator, PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly)
BUN_DEFINE_LAZY_GLOBAL_BUILTIN_GETTER(getLoadEsmIntoCjsBuiltin, commonJSLoadEsmIntoCjsCodeGenerator, PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly)
BUN_DEFINE_LAZY_GLOBAL_BUILTIN_GETTER(getInternalRequireBuiltin, commonJSInternalRequireCodeGenerator, PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly)
#undef BUN_DEFINE_LAZY_GLOBAL_BUILTIN_GETTER
Expand Down Expand Up @@ -3160,10 +3171,11 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
// i've noticed doing it as is will work somewhat but getDirect() wont be able to find them

putDirectBuiltinFunction(vm, this, builtinNames.createFIFOPrivateName(), fifoCreateFIFOCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
// These three are CommonJS-only and never reached on an ESM startup path; install
// These four are CommonJS-only and never reached on an ESM startup path; install
// lazy getters so their source isn't parsed during global object construction.
// (See getRequireESMBuiltin / getLoadEsmIntoCjsBuiltin / getInternalRequireBuiltin above.)
// (See getRequireESMBuiltin / getRequireESMIntoModuleBuiltin / getLoadEsmIntoCjsBuiltin / getInternalRequireBuiltin above.)
putDirectCustomAccessor(vm, builtinNames.requireESMPrivateName(), JSC::CustomGetterSetter::create(vm, getRequireESMBuiltin, nullptr), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomValue);
putDirectCustomAccessor(vm, builtinNames.requireESMIntoModulePrivateName(), JSC::CustomGetterSetter::create(vm, getRequireESMIntoModuleBuiltin, nullptr), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomValue);
putDirectCustomAccessor(vm, builtinNames.loadEsmIntoCjsPrivateName(), JSC::CustomGetterSetter::create(vm, getLoadEsmIntoCjsBuiltin, nullptr), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomValue);
putDirectCustomAccessor(vm, builtinNames.internalRequirePrivateName(), JSC::CustomGetterSetter::create(vm, getInternalRequireBuiltin, nullptr), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomValue);

Expand Down
4 changes: 2 additions & 2 deletions src/jsc/bindings/ZigGlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ class GlobalObject : public Bun::GlobalScope {
JSObject* lazyRequireCacheObject() const { return m_lazyRequireCacheObject.getInitializedOnMainThread(this); }
Bun::JSCommonJSExtensions* lazyRequireExtensionsObject() const { return m_lazyRequireExtensionsObject.getInitializedOnMainThread(this); }
JSC::JSFunction* modulePrototypeUnderscoreCompileFunction() const { return m_modulePrototypeUnderscoreCompileFunction.getInitializedOnMainThread(this); }
JSC::JSFunction* requireESMFromHijackedExtension() const { return m_commonJSRequireESMFromHijackedExtensionFunction.getInitializedOnMainThread(this); }
JSC::JSFunction* requireESMIntoModuleFunction() const { return m_commonJSRequireESMIntoModuleFunction.getInitializedOnMainThread(this); }

Structure* NodeVMGlobalObjectStructure() const { return m_cachedNodeVMGlobalObjectStructure.getInitializedOnMainThread(this); }
Structure* globalProxyStructure() const { return m_cachedGlobalProxyStructure.getInitializedOnMainThread(this); }
Expand Down Expand Up @@ -519,7 +519,7 @@ class GlobalObject : public Bun::GlobalScope {
V(public, LazyPropertyOfGlobalObject<JSCell>, m_moduleResolveFilenameFunction) \
V(public, LazyPropertyOfGlobalObject<JSCell>, m_moduleRunMainFunction) \
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_modulePrototypeUnderscoreCompileFunction) \
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_commonJSRequireESMFromHijackedExtensionFunction) \
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_commonJSRequireESMIntoModuleFunction) \
V(public, LazyPropertyOfGlobalObject<JSObject>, m_nodeModuleConstructor) \
V(public, LazyPropertyOfGlobalObject<Structure>, m_nodeModuleSourceMapEntryStructure) \
V(public, LazyPropertyOfGlobalObject<Structure>, m_nodeModuleSourceMapOriginStructure) \
Expand Down
6 changes: 3 additions & 3 deletions src/jsc/modules/NodeModuleModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1169,10 +1169,10 @@ void addNodeModuleConstructorProperties(JSC::VM& vm,
init.set(resolveFilenameFunction);
});

globalObject->m_commonJSRequireESMFromHijackedExtensionFunction.initLater(
globalObject->m_commonJSRequireESMIntoModuleFunction.initLater(
[](const Zig::GlobalObject::Initializer<JSFunction>& init) {
JSC::JSFunction* requireESM = JSC::JSFunction::create(init.vm, init.owner, commonJSRequireESMFromHijackedExtensionCodeGenerator(init.vm), init.owner);
init.set(requireESM);
JSC::JSFunction* requireESMIntoModule = JSC::JSFunction::create(init.vm, init.owner, commonJSRequireESMIntoModuleCodeGenerator(init.vm), init.owner);
init.set(requireESMIntoModule);
});

globalObject->m_lazyRequireCacheObject.initLater(
Expand Down
Loading
Loading