Refactor node:module (#14227)

This commit is contained in:
Jarred Sumner
2024-10-03 00:54:56 -07:00
committed by GitHub
parent dd6554294e
commit 4d4dd1c180
15 changed files with 969 additions and 592 deletions

View File

@@ -399,6 +399,7 @@ set(BUN_OBJECT_LUT_SOURCES
${CWD}/src/bun.js/bindings/BunProcess.cpp
${CWD}/src/bun.js/bindings/ProcessBindingConstants.cpp
${CWD}/src/bun.js/bindings/ProcessBindingNatives.cpp
${CWD}/src/bun.js/modules/NodeModuleModule.cpp
)
set(BUN_OBJECT_LUT_OUTPUTS
@@ -408,8 +409,10 @@ set(BUN_OBJECT_LUT_OUTPUTS
${CODEGEN_PATH}/BunProcess.lut.h
${CODEGEN_PATH}/ProcessBindingConstants.lut.h
${CODEGEN_PATH}/ProcessBindingNatives.lut.h
${CODEGEN_PATH}/NodeModuleModule.lut.h
)
macro(WEBKIT_ADD_SOURCE_DEPENDENCIES _source _deps)
set(_tmp)
get_source_file_property(_tmp ${_source} OBJECT_DEPENDS)

View File

@@ -942,6 +942,11 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject,
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()));
@@ -1004,12 +1009,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireCommonJS, (JSGlobalObject * lexicalGlo
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)) {
jsCast<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, {});

View File

@@ -105,6 +105,7 @@ public:
JSC::MarkedArgumentBuffer& exportValues);
JSValue exportsObject();
void setExportsObject(JSC::JSValue exportsObject);
JSValue id();
bool load(JSC::VM& vm, Zig::GlobalObject* globalObject, WTF::NakedPtr<JSC::Exception>&);
@@ -157,7 +158,7 @@ public:
using Base = JSC::JSNonFinalObject;
static RequireResolveFunctionPrototype* create(JSC::JSGlobalObject* globalObject);
static Structure* createStructure(VM& vm, JSC::JSGlobalObject* globalObject);
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
DECLARE_INFO;
@@ -183,7 +184,7 @@ public:
using Base = JSC::JSNonFinalObject;
static RequireFunctionPrototype* create(JSC::JSGlobalObject* globalObject);
static Structure* createStructure(VM& vm, JSC::JSGlobalObject* globalObject);
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
DECLARE_INFO;

View File

@@ -303,26 +303,28 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo
if (!isESM) {
if (LIKELY(globalObject)) {
auto overrideHandler = globalObject->m_nodeModuleOverriddenResolveFilename.get();
if (UNLIKELY(overrideHandler)) {
ASSERT(overrideHandler->isCallable());
JSValue parentModuleObject = globalObject->requireMap()->get(globalObject, from);
if (UNLIKELY(globalObject->hasOverridenModuleResolveFilenameFunction)) {
auto overrideHandler = jsCast<JSObject*>(globalObject->m_moduleResolveFilenameFunction.getInitializedOnMainThread(globalObject));
if (UNLIKELY(overrideHandler)) {
ASSERT(overrideHandler->isCallable());
JSValue parentModuleObject = globalObject->requireMap()->get(globalObject, from);
JSValue parentID = jsUndefined();
if (auto* parent = jsDynamicCast<Bun::JSCommonJSModule*>(parentModuleObject)) {
parentID = parent->id();
} else {
parentID = from;
JSValue parentID = jsUndefined();
if (auto* parent = jsDynamicCast<Bun::JSCommonJSModule*>(parentModuleObject)) {
parentID = parent->id();
} else {
parentID = from;
}
MarkedArgumentBuffer args;
args.append(moduleName);
args.append(parentModuleObject);
auto parentIdStr = parentID.toWTFString(globalObject);
auto bunStr = Bun::toString(parentIdStr);
args.append(jsBoolean(Bun__isBunMain(lexicalGlobalObject, &bunStr)));
return JSValue::encode(JSC::call(lexicalGlobalObject, overrideHandler, JSC::getCallData(overrideHandler), parentModuleObject, args));
}
MarkedArgumentBuffer args;
args.append(moduleName);
args.append(parentModuleObject);
auto parentIdStr = parentID.toWTFString(globalObject);
auto bunStr = Bun::toString(parentIdStr);
args.append(jsBoolean(Bun__isBunMain(lexicalGlobalObject, &bunStr)));
return JSValue::encode(JSC::call(lexicalGlobalObject, overrideHandler, JSC::getCallData(overrideHandler), parentModuleObject, args));
}
}
}

View File

@@ -517,6 +517,16 @@ JSValue fetchCommonJSModule(
auto tag = res->result.value.tag;
switch (tag) {
case SyntheticModuleType::NodeModule: {
target->setExportsObject(globalObject->m_nodeModuleConstructor.getInitializedOnMainThread(globalObject));
target->hasEvaluated = true;
RELEASE_AND_RETURN(scope, target);
}
case SyntheticModuleType::NodeProcess: {
target->setExportsObject(globalObject->processObject());
target->hasEvaluated = true;
RELEASE_AND_RETURN(scope, target);
}
// Generated native module cases
#define CASE(str, name) \
case SyntheticModuleType::name: { \
@@ -524,7 +534,7 @@ JSValue fetchCommonJSModule(
RETURN_IF_EXCEPTION(scope, {}); \
RELEASE_AND_RETURN(scope, target); \
}
BUN_FOREACH_NATIVE_MODULE(CASE)
BUN_FOREACH_CJS_NATIVE_MODULE(CASE)
#undef CASE
case SyntheticModuleType::ESM: {
@@ -746,7 +756,7 @@ static JSValue fetchESMSourceCode(
auto source = JSC::SourceCode(JSC::SyntheticSourceProvider::create(generateNativeModule_##name, JSC::SourceOrigin(), WTFMove(moduleKey))); \
return rejectOrResolve(JSSourceCode::create(vm, WTFMove(source))); \
}
BUN_FOREACH_NATIVE_MODULE(CASE)
BUN_FOREACH_ESM_NATIVE_MODULE(CASE)
#undef CASE
// CommonJS modules from src/js/*

View File

@@ -149,6 +149,7 @@
#include "ErrorCode.h"
#include "v8/shim/GlobalInternals.h"
#include "EventLoopTask.h"
#include "NodeModuleModule.h"
#include <JavaScriptCore/JSCBytecodeCacheVersion.h>
#if ENABLE(REMOTE_INSPECTOR)
@@ -2685,6 +2686,8 @@ void GlobalObject::finishCreation(VM& vm)
m_commonStrings.initialize();
Bun::addNodeModuleConstructorProperties(vm, this);
m_JSDOMFileConstructor.initLater(
[](const Initializer<JSObject>& init) {
JSObject* fileConstructor = Bun::createJSDOMFileConstructor(init.vm, init.owner);
@@ -3602,7 +3605,6 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.append(thisObject->m_readableStreamToJSON);
visitor.append(thisObject->m_readableStreamToText);
visitor.append(thisObject->m_readableStreamToFormData);
visitor.append(thisObject->m_nodeModuleOverriddenResolveFilename);
visitor.append(thisObject->m_nextTickQueue);
visitor.append(thisObject->m_errorConstructorPrepareStackTraceValue);
@@ -3612,6 +3614,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.append(thisObject->m_currentNapiHandleScopeImpl);
thisObject->m_moduleResolveFilenameFunction.visit(visitor);
thisObject->m_nodeModuleConstructor.visit(visitor);
thisObject->m_asyncBoundFunctionStructure.visit(visitor);
thisObject->m_bunObject.visit(visitor);
thisObject->m_cachedNodeVMGlobalObjectStructure.visit(visitor);
@@ -3965,7 +3969,8 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderImportModule(JSGlobalObject* j
ASSERT(startRefCount == moduleName.impl()->refCount());
if (!resolved.success) {
throwException(scope, resolved.result.err, globalObject);
return JSC::JSInternalPromise::rejectedPromiseWithCaughtException(globalObject, scope);
auto* promise = JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure());
return promise->rejectWithCaughtException(globalObject, scope);
}
JSC::Identifier resolvedIdentifier;

View File

@@ -395,9 +395,8 @@ public:
mutable WriteBarrier<JSFunction> m_readableStreamToText;
mutable WriteBarrier<JSFunction> m_readableStreamToFormData;
// This is set when doing `require('module')._resolveFilename = ...`
// a hack used by Next.js to inject their versions of webpack and react
mutable WriteBarrier<JSFunction> m_nodeModuleOverriddenResolveFilename;
LazyProperty<JSGlobalObject, JSCell> m_moduleResolveFilenameFunction;
LazyProperty<JSGlobalObject, JSObject> m_nodeModuleConstructor;
mutable WriteBarrier<Unknown> m_nextTickQueue;
@@ -586,6 +585,8 @@ public:
LazyProperty<JSGlobalObject, JSObject> m_performanceObject;
LazyProperty<JSGlobalObject, JSObject> m_processObject;
bool hasOverridenModuleResolveFilenameFunction = false;
private:
DOMGuardedObjectSet m_guardedObjects WTF_GUARDED_BY_LOCK(m_gcLock);
WebCore::SubtleCrypto* m_subtleCrypto = nullptr;

View File

@@ -0,0 +1,841 @@
#include "root.h"
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/VM.h>
#include <JavaScriptCore/JSString.h>
#include "headers-handwritten.h"
#include "PathInlines.h"
#include "ZigGlobalObject.h"
#include "headers.h"
#include <JavaScriptCore/FunctionPrototype.h>
#include "NodeModuleModule.h"
#include "ErrorCode.h"
#include <JavaScriptCore/LazyPropertyInlines.h>
#include <JavaScriptCore/VMTrapsInlines.h>
namespace Bun {
using namespace JSC;
BUN_DECLARE_HOST_FUNCTION(Resolver__nodeModulePathsForJS);
JSC_DECLARE_HOST_FUNCTION(jsFunctionDebugNoop);
JSC_DECLARE_HOST_FUNCTION(jsFunctionFindPath);
JSC_DECLARE_HOST_FUNCTION(jsFunctionFindSourceMap);
JSC_DECLARE_HOST_FUNCTION(jsFunctionIsBuiltinModule);
JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire);
JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeModuleModuleConstructor);
JSC_DECLARE_HOST_FUNCTION(jsFunctionResolveFileName);
JSC_DECLARE_HOST_FUNCTION(jsFunctionResolveLookupPaths);
JSC_DECLARE_HOST_FUNCTION(jsFunctionSourceMap);
JSC_DECLARE_HOST_FUNCTION(jsFunctionSyncBuiltinExports);
JSC_DECLARE_HOST_FUNCTION(jsFunctionWrap);
JSC_DECLARE_CUSTOM_GETTER(getterRequireFunction);
JSC_DECLARE_CUSTOM_SETTER(setterRequireFunction);
// This is a mix of bun's builtin module names and also the ones reported by
// node v20.4.0
static constexpr ASCIILiteral builtinModuleNames[] = {
"_http_agent"_s,
"_http_client"_s,
"_http_common"_s,
"_http_incoming"_s,
"_http_outgoing"_s,
"_http_server"_s,
"_stream_duplex"_s,
"_stream_passthrough"_s,
"_stream_readable"_s,
"_stream_transform"_s,
"_stream_wrap"_s,
"_stream_writable"_s,
"_tls_common"_s,
"_tls_wrap"_s,
"assert"_s,
"assert/strict"_s,
"async_hooks"_s,
"buffer"_s,
"bun"_s,
"bun:ffi"_s,
"bun:jsc"_s,
"bun:sqlite"_s,
"bun:test"_s,
"bun:wrap"_s,
"child_process"_s,
"cluster"_s,
"console"_s,
"constants"_s,
"crypto"_s,
"detect-libc"_s,
"dgram"_s,
"diagnostics_channel"_s,
"dns"_s,
"dns/promises"_s,
"domain"_s,
"events"_s,
"fs"_s,
"fs/promises"_s,
"http"_s,
"http2"_s,
"https"_s,
"inspector"_s,
"inspector/promises"_s,
"module"_s,
"net"_s,
"os"_s,
"path"_s,
"path/posix"_s,
"path/win32"_s,
"perf_hooks"_s,
"process"_s,
"punycode"_s,
"querystring"_s,
"readline"_s,
"readline/promises"_s,
"repl"_s,
"stream"_s,
"stream/consumers"_s,
"stream/promises"_s,
"stream/web"_s,
"string_decoder"_s,
"sys"_s,
"timers"_s,
"timers/promises"_s,
"tls"_s,
"trace_events"_s,
"tty"_s,
"undici"_s,
"url"_s,
"util"_s,
"util/types"_s,
"v8"_s,
"vm"_s,
"wasi"_s,
"worker_threads"_s,
"ws"_s,
"zlib"_s,
};
template <std::size_t N, class T> consteval std::size_t countof(T (&)[N]) {
return N;
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionDebugNoop,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleModuleCall,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleModuleConstructor,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
// In node, this is supposed to be the actual CommonJSModule constructor.
// We are cutting a huge corner by not doing all that work.
// This code is only to support babel.
JSC::VM &vm = globalObject->vm();
JSString *idString = JSC::jsString(vm, WTF::String("."_s));
JSString *dirname = jsEmptyString(vm);
// TODO: handle when JSGlobalObject !== Zig::GlobalObject, such as in node:vm
Structure *structure = static_cast<Zig::GlobalObject *>(globalObject)
->CommonJSModuleObjectStructure();
// TODO: handle ShadowRealm, node:vm, new.target, subclasses
JSValue idValue = callFrame->argument(0);
JSValue parentValue = callFrame->argument(1);
auto scope = DECLARE_THROW_SCOPE(vm);
if (idValue.isString()) {
idString = idValue.toString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
auto index = idString->tryGetValue()->reverseFind('/', idString->length());
if (index != WTF::notFound) {
dirname = JSC::jsSubstring(globalObject, idString, 0, index);
}
}
auto *out = Bun::JSCommonJSModule::create(vm, structure, idString, jsNull(),
dirname, SourceCode());
if (!parentValue.isUndefined()) {
out->putDirect(vm, JSC::Identifier::fromString(vm, "parent"_s), parentValue,
0);
}
out->putDirect(vm, JSC::Identifier::fromString(vm, "exports"_s),
JSC::constructEmptyObject(globalObject,
globalObject->objectPrototype(), 0),
0);
return JSValue::encode(out);
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionIsBuiltinModule,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue moduleName = callFrame->argument(0);
if (!moduleName.isString()) {
return JSValue::encode(jsBoolean(false));
}
auto moduleStr = moduleName.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, JSValue::encode(jsBoolean(false)));
return JSValue::encode(jsBoolean(Bun::isBuiltinModule(moduleStr)));
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionWrap, (JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSString *code = callFrame->argument(0).toStringOrNull(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (!code) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSString *prefix = jsString(
vm,
String(
"(function (exports, require, module, __filename, __dirname) { "_s));
JSString *suffix = jsString(vm, String("\n});"_s));
return JSValue::encode(jsString(globalObject, prefix, code, suffix));
}
extern "C" void Bun__Node__Path_joinWTF(BunString *lhs, const char *rhs,
size_t len, BunString *result);
JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (callFrame->argumentCount() < 1) {
return Bun::throwError(globalObject, scope,
Bun::ErrorCode::ERR_MISSING_ARGS,
"createRequire() requires at least one argument"_s);
}
auto val = callFrame->uncheckedArgument(0).toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (val.startsWith("file://"_s)) {
WTF::URL url(val);
if (!url.isValid()) {
throwTypeError(globalObject, scope,
makeString("createRequire() was given an invalid URL '"_s,
url.string(), "'"_s));
RELEASE_AND_RETURN(scope, JSValue::encode({}));
}
if (!url.protocolIsFile()) {
throwTypeError(globalObject, scope,
"createRequire() does not support non-file URLs"_s);
RELEASE_AND_RETURN(scope, JSValue::encode({}));
}
val = url.fileSystemPath();
}
bool trailingSlash = val.endsWith('/');
#if OS(WINDOWS)
if (val.endsWith('\\')) {
trailingSlash = true;
}
#endif
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/internal/modules/cjs/loader.js#L1603-L1620
if (trailingSlash) {
BunString lhs = Bun::toString(val);
BunString result;
Bun__Node__Path_joinWTF(&lhs, "noop.js", sizeof("noop.js") - 1, &result);
val = result.toWTFString();
if (!val.isNull()) {
ASSERT(val.impl()->refCount() == 2);
val.impl()->deref();
}
}
RETURN_IF_EXCEPTION(scope, {});
RELEASE_AND_RETURN(
scope, JSValue::encode(Bun::JSCommonJSModule::createBoundRequireFunction(
vm, globalObject, val)));
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionFindSourceMap,
(JSGlobalObject * globalObject,
CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
throwException(
globalObject, scope,
createError(globalObject,
"module.SourceMap is not yet implemented in Bun"_s));
return {};
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionSyncBuiltinExports,
(JSGlobalObject * globalObject,
CallFrame *callFrame)) {
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionSourceMap, (JSGlobalObject * globalObject,
CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
throwException(globalObject, scope,
createError(globalObject, "Not implemented"_s));
return {};
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
switch (callFrame->argumentCount()) {
case 0: {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
// not "requires" because "require" could be confusing
JSC::throwTypeError(
globalObject, scope,
"Module._resolveFilename needs 2+ arguments (a string)"_s);
scope.release();
return JSC::JSValue::encode(JSC::JSValue{});
}
default: {
JSC::JSValue moduleName = callFrame->argument(0);
JSC::JSValue fromValue = callFrame->argument(1);
if (moduleName.isUndefinedOrNull()) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
JSC::throwTypeError(globalObject, scope,
"Module._resolveFilename expects a string"_s);
scope.release();
return JSC::JSValue::encode(JSC::JSValue{});
}
if (
// fast path: it's a real CommonJS module object.
auto *cjs = jsDynamicCast<Bun::JSCommonJSModule *>(fromValue)) {
fromValue = cjs->id();
} else if
// slow path: userland code did something weird. lets let them do that
// weird thing.
(fromValue.isObject()) {
if (auto idValue = fromValue.getObject()->getIfPropertyExists(
globalObject, builtinNames(vm).filenamePublicName())) {
if (idValue.isString()) {
fromValue = idValue;
}
}
}
auto result =
Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName),
JSValue::encode(fromValue), false);
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
if (!JSC::JSValue::decode(result).isString()) {
JSC::throwException(globalObject, scope, JSC::JSValue::decode(result));
return JSC::JSValue::encode(JSC::JSValue{});
}
scope.release();
return result;
}
}
}
JSC_DEFINE_CUSTOM_GETTER(nodeModuleResolveFilename,
(JSGlobalObject * lexicalGlobalObject,
EncodedJSValue thisValue,
PropertyName propertyName)) {
auto *globalObject = defaultGlobalObject(lexicalGlobalObject);
return JSValue::encode(
globalObject->m_moduleResolveFilenameFunction.getInitializedOnMainThread(
globalObject));
}
JSC_DEFINE_CUSTOM_SETTER(setNodeModuleResolveFilename,
(JSGlobalObject * lexicalGlobalObject,
EncodedJSValue thisValue, EncodedJSValue encodedValue,
PropertyName propertyName)) {
auto *globalObject = defaultGlobalObject(lexicalGlobalObject);
auto value = JSValue::decode(encodedValue);
if (value.isCell()) {
bool isOriginal = false;
if (value.isCallable()) {
JSC::CallData callData = JSC::getCallData(value);
if (callData.type == JSC::CallData::Type::Native) {
if (callData.native.function.untaggedPtr() ==
&jsFunctionResolveFileName) {
isOriginal = true;
}
}
}
globalObject->hasOverridenModuleResolveFilenameFunction = !isOriginal;
globalObject->m_moduleResolveFilenameFunction.set(
lexicalGlobalObject->vm(), globalObject, value.asCell());
}
return true;
}
extern "C" bool ModuleLoader__isBuiltin(const char *data, size_t len);
struct Parent {
JSArray *paths;
JSString *filename;
};
Parent getParent(VM &vm, JSGlobalObject *global, JSValue maybe_parent) {
Parent value{nullptr, nullptr};
if (!maybe_parent) {
return value;
}
auto parent = maybe_parent.getObject();
if (!parent) {
return value;
}
auto scope = DECLARE_THROW_SCOPE(vm);
const auto &builtinNames = Bun::builtinNames(vm);
JSValue paths = parent->get(global, builtinNames.pathsPublicName());
RETURN_IF_EXCEPTION(scope, value);
if (paths.isCell()) {
value.paths = jsDynamicCast<JSArray *>(paths);
}
JSValue filename = parent->get(global, builtinNames.filenamePublicName());
RETURN_IF_EXCEPTION(scope, value);
if (filename.isString()) {
value.filename = filename.toString(global);
}
RELEASE_AND_RETURN(scope, value);
}
// https://github.com/nodejs/node/blob/40ef9d541ed79470977f90eb445c291b95ab75a0/lib/internal/modules/cjs/loader.js#L895
JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveLookupPaths,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
String request = callFrame->argument(0).toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
auto utf8 = request.utf8();
if (ModuleLoader__isBuiltin(utf8.data(), utf8.length())) {
return JSC::JSValue::encode(JSC::jsNull());
}
auto parent = getParent(vm, globalObject, callFrame->argument(1));
RETURN_IF_EXCEPTION(scope, {});
// Check for node modules paths.
if (request.characterAt(0) != '.' ||
(request.length() > 1 && request.characterAt(1) != '.' &&
request.characterAt(1) != '/' &&
#if OS(WINDOWS)
request.characterAt(1) != '\\'
#else
true
#endif
)) {
auto array = JSC::constructArray(
globalObject, (ArrayAllocationProfile *)nullptr, nullptr, 0);
if (parent.paths) {
auto len = parent.paths->length();
for (size_t i = 0; i < len; i++) {
auto path = parent.paths->getIndex(globalObject, i);
array->push(globalObject, path);
}
}
return JSValue::encode(array);
}
JSValue dirname;
if (parent.filename) {
EncodedJSValue encodedFilename = JSValue::encode(parent.filename);
#if OS(WINDOWS)
dirname = JSValue::decode(
Bun__Path__dirname(globalObject, true, &encodedFilename, 1));
#else
dirname = JSValue::decode(
Bun__Path__dirname(globalObject, false, &encodedFilename, 1));
#endif
} else {
dirname = jsString(vm, String("."_s));
}
JSValue values[] = {dirname};
auto array = JSC::constructArray(
globalObject, (ArrayAllocationProfile *)nullptr, values, 1);
RELEASE_AND_RETURN(scope, JSValue::encode(array));
}
extern "C" JSC::EncodedJSValue NodeModuleModule__findPath(JSGlobalObject *,
BunString, JSArray *);
JSC_DEFINE_HOST_FUNCTION(jsFunctionFindPath, (JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue request_value = callFrame->argument(0);
JSValue paths_value = callFrame->argument(1);
String request = request_value.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
BunString request_bun_str = Bun::toString(request);
JSArray *paths =
paths_value.isCell() ? jsDynamicCast<JSArray *>(paths_value) : nullptr;
return NodeModuleModule__findPath(globalObject, request_bun_str, paths);
}
// These two setters are only used if you directly hit
// `Module.prototype.require` or `module.require`. When accessing the cjs
// require argument, this is a bound version of `require`, which calls into the
// overridden one.
//
// This require function also intentionally does not have .resolve on it, nor
// does it have any of the other properties.
//
// Note: allowing require to be overridable at all is only needed for Next.js to
// work (they do Module.prototype.require = ...)
JSC_DEFINE_CUSTOM_GETTER(getterRequireFunction,
(JSC::JSGlobalObject * globalObject,
JSC::EncodedJSValue thisValue, JSC::PropertyName)) {
return JSValue::encode(globalObject->getDirect(
globalObject->vm(), WebCore::clientData(globalObject->vm())
->builtinNames()
.overridableRequirePrivateName()));
}
JSC_DEFINE_CUSTOM_SETTER(setterRequireFunction,
(JSC::JSGlobalObject * globalObject,
JSC::EncodedJSValue thisValue,
JSC::EncodedJSValue value,
JSC::PropertyName propertyName)) {
globalObject->putDirect(globalObject->vm(),
WebCore::clientData(globalObject->vm())
->builtinNames()
.overridableRequirePrivateName(),
JSValue::decode(value), 0);
return true;
}
static JSValue getModuleCacheObject(VM &vm, JSObject *moduleObject) {
return jsCast<Zig::GlobalObject *>(moduleObject->globalObject())
->lazyRequireCacheObject();
}
static JSValue getModuleDebugObject(VM &vm, JSObject *moduleObject) {
return JSC::constructEmptyObject(moduleObject->globalObject());
}
static JSValue getPathCacheObject(VM &vm, JSObject *moduleObject) {
auto *globalObject = defaultGlobalObject(moduleObject->globalObject());
return JSC::constructEmptyObject(
vm, globalObject->nullPrototypeObjectStructure());
}
static JSValue getModuleExtensionsObject(VM &vm, JSObject *moduleObject) {
auto *globalObject = defaultGlobalObject(moduleObject->globalObject());
return globalObject->requireFunctionUnbound()->getIfPropertyExists(
globalObject, Identifier::fromString(vm, "extensions"_s));
}
static JSValue getSourceMapFunction(VM &vm, JSObject *moduleObject) {
auto *globalObject = defaultGlobalObject(moduleObject->globalObject());
JSFunction *sourceMapFunction = JSFunction::create(
vm, globalObject, 1, "SourceMap"_s, jsFunctionSourceMap,
ImplementationVisibility::Public, NoIntrinsic, jsFunctionSourceMap);
return sourceMapFunction;
}
static JSValue getBuiltinModulesObject(VM &vm, JSObject *moduleObject) {
MarkedArgumentBuffer args;
args.ensureCapacity(countof(builtinModuleNames));
for (unsigned i = 0; i < countof(builtinModuleNames); ++i) {
args.append(JSC::jsOwnedString(vm, String(builtinModuleNames[i])));
}
auto *globalObject = defaultGlobalObject(moduleObject->globalObject());
return JSC::constructArray(
globalObject, static_cast<JSC::ArrayAllocationProfile *>(nullptr),
JSC::ArgList(args));
}
static JSValue getConstantsObject(VM &vm, JSObject *moduleObject) {
auto *globalObject = defaultGlobalObject(moduleObject->globalObject());
auto *compileCacheStatus = JSC::constructEmptyObject(
vm, globalObject->nullPrototypeObjectStructure());
compileCacheStatus->putDirect(vm, JSC::Identifier::fromString(vm, "FAILED"_s),
JSC::jsNumber(0));
compileCacheStatus->putDirect(
vm, JSC::Identifier::fromString(vm, "ENABLED"_s), JSC::jsNumber(1));
compileCacheStatus->putDirect(
vm, JSC::Identifier::fromString(vm, "ALREADY_ENABLED"_s),
JSC::jsNumber(2));
compileCacheStatus->putDirect(
vm, JSC::Identifier::fromString(vm, "DISABLED"_s), JSC::jsNumber(3));
auto *constantsObject = JSC::constructEmptyObject(
vm, globalObject->nullPrototypeObjectStructure());
constantsObject->putDirect(
vm, JSC::Identifier::fromString(vm, "compileCacheStatus"_s),
compileCacheStatus);
return constantsObject;
}
static JSValue getGlobalPathsObject(VM &vm, JSObject *moduleObject) {
return JSC::constructEmptyArray(
moduleObject->globalObject(),
static_cast<ArrayAllocationProfile *>(nullptr), 0);
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionInitPaths, (JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
static JSValue getModulePrototypeObject(VM &vm, JSObject *moduleObject) {
auto *globalObject = defaultGlobalObject(moduleObject->globalObject());
auto prototype =
constructEmptyObject(globalObject, globalObject->objectPrototype(), 2);
prototype->putDirectCustomAccessor(
vm, WebCore::clientData(vm)->builtinNames().requirePublicName(),
JSC::CustomGetterSetter::create(vm, getterRequireFunction,
setterRequireFunction),
0);
return prototype;
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionLoad, (JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionRunMain, (JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionPreloadModules,
(JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionSyncBuiltinESMExports,
(JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionRegister, (JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionEnableCompileCache,
(JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionGetCompileCacheDir,
(JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
static JSValue getModuleObject(VM &vm, JSObject *moduleObject) {
return moduleObject;
}
/* Source for NodeModuleModule.lut.h
@begin nodeModuleObjectTable
_cache getModuleCacheObject PropertyCallback
_debug getModuleDebugObject PropertyCallback
_extensions getModuleExtensionsObject PropertyCallback
_findPath jsFunctionFindPath Function 3
_initPaths jsFunctionInitPaths Function 0
_load jsFunctionLoad Function 1
_nodeModulePaths Resolver__nodeModulePathsForJS Function 1
_pathCache getPathCacheObject PropertyCallback
_preloadModules jsFunctionPreloadModules Function 0
_resolveFilename nodeModuleResolveFilename CustomAccessor
_resolveLookupPaths jsFunctionResolveLookupPaths Function 2
builtinModules getBuiltinModulesObject PropertyCallback
constants getConstantsObject PropertyCallback
createRequire jsFunctionNodeModuleCreateRequire Function 1
enableCompileCache jsFunctionEnableCompileCache Function 0
findSourceMap jsFunctionFindSourceMap Function 0
getCompileCacheDir jsFunctionGetCompileCacheDir Function 0
globalPaths getGlobalPathsObject PropertyCallback
isBuiltin jsFunctionIsBuiltinModule Function 1
prototype getModulePrototypeObject PropertyCallback
register jsFunctionRegister Function 1
runMain jsFunctionRunMain Function 0
SourceMap getSourceMapFunction PropertyCallback
syncBuiltinESMExports jsFunctionSyncBuiltinESMExports Function 0
wrap jsFunctionWrap Function 1
Module getModuleObject PropertyCallback
@end
*/
#include "NodeModuleModule.lut.h"
class JSModuleConstructor : public JSC::InternalFunction {
using Base = JSC::InternalFunction;
public:
DECLARE_EXPORT_INFO;
static constexpr bool needsDestruction = false;
static constexpr unsigned StructureFlags =
Base::StructureFlags | HasStaticPropertyTable;
static JSC::Structure *createStructure(JSC::VM &vm,
JSC::JSGlobalObject *globalObject,
JSC::JSValue prototype) {
ASSERT(globalObject);
return JSC::Structure::create(
vm, globalObject, prototype,
JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
template <typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace *subspaceFor(JSC::VM &vm) {
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSModuleConstructor, Base);
return &vm.internalFunctionSpace();
}
static JSModuleConstructor *create(JSC::VM &vm,
Zig::GlobalObject *globalObject) {
auto *structure =
createStructure(vm, globalObject, globalObject->functionPrototype());
auto *moduleConstructor =
new (NotNull, JSC::allocateCell<JSModuleConstructor>(vm))
JSModuleConstructor(vm, structure);
moduleConstructor->finishCreation(vm);
return moduleConstructor;
}
private:
JSModuleConstructor(JSC::VM &vm, JSC::Structure *structure)
: Base(vm, structure, jsFunctionNodeModuleModuleCall,
jsFunctionNodeModuleModuleConstructor) {}
void finishCreation(JSC::VM &vm) {
Base::finishCreation(vm, 1, "Module"_s,
PropertyAdditionMode::WithoutStructureTransition);
}
};
const JSC::ClassInfo JSModuleConstructor::s_info = {
"Module"_s, &Base::s_info, &nodeModuleObjectTable, nullptr,
CREATE_METHOD_TABLE(JSModuleConstructor)};
void addNodeModuleConstructorProperties(JSC::VM &vm,
Zig::GlobalObject *globalObject) {
globalObject->m_nodeModuleConstructor.initLater(
[](const Zig::GlobalObject::Initializer<JSObject> &init) {
JSObject *moduleConstructor = JSModuleConstructor::create(
init.vm, static_cast<Zig::GlobalObject *>(init.owner));
init.set(moduleConstructor);
});
globalObject->m_moduleResolveFilenameFunction.initLater(
[](const Zig::GlobalObject::Initializer<JSCell> &init) {
JSFunction *resolveFilenameFunction = JSFunction::create(
init.vm, init.owner, 2, "_resolveFilename"_s,
jsFunctionResolveFileName, JSC::ImplementationVisibility::Public,
JSC::NoIntrinsic, jsFunctionResolveFileName);
init.set(resolveFilenameFunction);
});
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionIsModuleResolveFilenameSlowPathEnabled,
(JSGlobalObject * globalObject,
CallFrame *callframe)) {
return JSValue::encode(
jsBoolean(defaultGlobalObject(globalObject)
->hasOverridenModuleResolveFilenameFunction));
}
} // namespace Bun
namespace Zig {
void generateNativeModule_NodeModule(JSC::JSGlobalObject *lexicalGlobalObject,
JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4> &exportNames,
JSC::MarkedArgumentBuffer &exportValues) {
Zig::GlobalObject *globalObject = defaultGlobalObject(lexicalGlobalObject);
auto &vm = globalObject->vm();
auto catchScope = DECLARE_CATCH_SCOPE(vm);
auto *constructor =
globalObject->m_nodeModuleConstructor.getInitializedOnMainThread(
globalObject);
if (constructor->hasNonReifiedStaticProperties()) {
constructor->reifyAllStaticProperties(globalObject);
if (catchScope.exception()) {
catchScope.clearException();
}
}
exportNames.reserveCapacity(Bun::countof(Bun::nodeModuleObjectTableValues) +
1);
exportValues.ensureCapacity(Bun::countof(Bun::nodeModuleObjectTableValues) +
1);
for (unsigned i = 0; i < Bun::countof(Bun::nodeModuleObjectTableValues);
++i) {
const auto &entry = Bun::nodeModuleObjectTableValues[i];
const auto &property = Identifier::fromString(vm, entry.m_key);
JSValue value = constructor->getIfPropertyExists(globalObject, property);
if (UNLIKELY(catchScope.exception())) {
value = {};
catchScope.clearException();
}
if (UNLIKELY(value.isEmpty())) {
value = JSC::jsUndefined();
}
exportNames.append(property);
exportValues.append(value);
}
exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(constructor);
}
} // namespace Zig

View File

@@ -1,6 +1,8 @@
// clang-format off
#pragma once
#include "root.h"
#include "CommonJSModuleRecord.h"
#include "ImportMetaObject.h"
#include "JavaScriptCore/ArgList.h"
@@ -10,563 +12,27 @@
#include "isBuiltinModule.h"
#include <JavaScriptCore/JSBoundFunction.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include "PathInlines.h"
#include "headers.h"
using namespace Zig;
using namespace JSC;
// This is a mix of bun's builtin module names and also the ones reported by
// node v20.4.0
static constexpr ASCIILiteral builtinModuleNames[] = {
"_http_agent"_s,
"_http_client"_s,
"_http_common"_s,
"_http_incoming"_s,
"_http_outgoing"_s,
"_http_server"_s,
"_stream_duplex"_s,
"_stream_passthrough"_s,
"_stream_readable"_s,
"_stream_transform"_s,
"_stream_wrap"_s,
"_stream_writable"_s,
"_tls_common"_s,
"_tls_wrap"_s,
"assert"_s,
"assert/strict"_s,
"async_hooks"_s,
"buffer"_s,
"bun"_s,
"bun:ffi"_s,
"bun:jsc"_s,
"bun:sqlite"_s,
"bun:test"_s,
"bun:wrap"_s,
"child_process"_s,
"cluster"_s,
"console"_s,
"constants"_s,
"crypto"_s,
"detect-libc"_s,
"dgram"_s,
"diagnostics_channel"_s,
"dns"_s,
"dns/promises"_s,
"domain"_s,
"events"_s,
"fs"_s,
"fs/promises"_s,
"http"_s,
"http2"_s,
"https"_s,
"inspector"_s,
"inspector/promises"_s,
"module"_s,
"net"_s,
"os"_s,
"path"_s,
"path/posix"_s,
"path/win32"_s,
"perf_hooks"_s,
"process"_s,
"punycode"_s,
"querystring"_s,
"readline"_s,
"readline/promises"_s,
"repl"_s,
"stream"_s,
"stream/consumers"_s,
"stream/promises"_s,
"stream/web"_s,
"string_decoder"_s,
"sys"_s,
"timers"_s,
"timers/promises"_s,
"tls"_s,
"trace_events"_s,
"tty"_s,
"undici"_s,
"url"_s,
"util"_s,
"util/types"_s,
"v8"_s,
"vm"_s,
"wasi"_s,
"worker_threads"_s,
"ws"_s,
"zlib"_s,
};
JSC_DEFINE_HOST_FUNCTION(jsFunctionDebugNoop, (JSC::JSGlobalObject * globalObject, JSC::CallFrame *callFrame)) {
return JSValue::encode(jsUndefined());
namespace Bun {
JSC_DECLARE_HOST_FUNCTION(jsFunctionIsModuleResolveFilenameSlowPathEnabled);
void addNodeModuleConstructorProperties(JSC::VM &vm, Zig::GlobalObject *globalObject);
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleModuleConstructor, (JSC::JSGlobalObject * globalObject, JSC::CallFrame *callFrame)) {
// In node, this is supposed to be the actual CommonJSModule constructor.
// We are cutting a huge corner by not doing all that work.
// This code is only to support babel.
JSC::VM &vm = globalObject->vm();
JSString *idString = JSC::jsString(vm, WTF::String("."_s));
JSString *dirname = jsEmptyString(vm);
// TODO: handle when JSGlobalObject !== Zig::GlobalObject, such as in node:vm
Structure *structure = static_cast<Zig::GlobalObject *>(globalObject)
->CommonJSModuleObjectStructure();
// TODO: handle ShadowRealm, node:vm, new.target, subclasses
JSValue idValue = callFrame->argument(0);
JSValue parentValue = callFrame->argument(1);
auto scope = DECLARE_THROW_SCOPE(vm);
if (idValue.isString()) {
idString = idValue.toString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
auto index = idString->tryGetValue()->reverseFind('/', idString->length());
if (index != WTF::notFound) {
dirname = JSC::jsSubstring(globalObject, idString, 0, index);
}
}
auto *out = Bun::JSCommonJSModule::create(vm, structure, idString, jsNull(),
dirname, SourceCode());
if (!parentValue.isUndefined()) {
out->putDirect(vm, JSC::Identifier::fromString(vm, "parent"_s), parentValue, 0);
}
out->putDirect(
vm,
JSC::Identifier::fromString(vm, "exports"_s),
JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 0),
0
);
return JSValue::encode(out);
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionIsBuiltinModule, (JSC::JSGlobalObject * globalObject, JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue moduleName = callFrame->argument(0);
if (!moduleName.isString()) {
return JSValue::encode(jsBoolean(false));
}
auto moduleStr = moduleName.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, JSValue::encode(jsBoolean(false)));
return JSValue::encode(jsBoolean(Bun::isBuiltinModule(moduleStr)));
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionWrap, (JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSString *code = callFrame->argument(0).toStringOrNull(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (!code) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
JSString *prefix = jsString(vm, String( "(function (exports, require, module, __filename, __dirname) { "_s));
JSString *suffix = jsString(vm, String("\n});"_s));
return JSValue::encode(jsString(globalObject, prefix, code, suffix));
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire,
(JSC::JSGlobalObject * globalObject,
JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (callFrame->argumentCount() < 1) {
throwTypeError(globalObject, scope,
"createRequire() requires at least one argument"_s);
RELEASE_AND_RETURN(scope, JSC::JSValue::encode({}));
}
auto val = callFrame->uncheckedArgument(0).toWTFString(globalObject);
if (val.startsWith("file://"_s)) {
WTF::URL url(val);
if (!url.isValid()) {
throwTypeError(globalObject, scope,
makeString("createRequire() was given an invalid URL '"_s,
url.string(), "'"_s));
RELEASE_AND_RETURN(scope, JSValue::encode({}));
}
if (!url.protocolIsFile()) {
throwTypeError(globalObject, scope,
"createRequire() does not support non-file URLs"_s);
RELEASE_AND_RETURN(scope, JSValue::encode({}));
}
val = url.fileSystemPath();
}
RETURN_IF_EXCEPTION(scope, {});
RELEASE_AND_RETURN(
scope, JSValue::encode(Bun::JSCommonJSModule::createBoundRequireFunction(
vm, globalObject, val)));
}
BUN_DECLARE_HOST_FUNCTION(Resolver__nodeModulePathsForJS);
JSC_DEFINE_HOST_FUNCTION(jsFunctionFindSourceMap, (JSGlobalObject * globalObject, CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
throwException(globalObject, scope, createError(globalObject, "module.SourceMap is not yet implemented in Bun"_s));
return {};
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionSyncBuiltinExports,
(JSGlobalObject * globalObject,
CallFrame *callFrame)) {
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionSourceMap, (JSGlobalObject * globalObject, CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
throwException(globalObject, scope,
createError(globalObject, "Not implemented"_s));
return {};
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName, (JSC::JSGlobalObject * globalObject, JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
switch (callFrame->argumentCount()) {
case 0: {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
// not "requires" because "require" could be confusing
JSC::throwTypeError(
globalObject, scope,
"Module._resolveFilename needs 2+ arguments (a string)"_s);
scope.release();
return JSC::JSValue::encode(JSC::JSValue{});
}
default: {
JSC::JSValue moduleName = callFrame->argument(0);
JSC::JSValue fromValue = callFrame->argument(1);
if (moduleName.isUndefinedOrNull()) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
JSC::throwTypeError(globalObject, scope,
"Module._resolveFilename expects a string"_s);
scope.release();
return JSC::JSValue::encode(JSC::JSValue{});
}
if (
// fast path: it's a real CommonJS module object.
auto *cjs = jsDynamicCast<Bun::JSCommonJSModule *>(fromValue)) {
fromValue = cjs->id();
} else if
// slow path: userland code did something weird. lets let them do that
// weird thing.
(fromValue.isObject()) {
if (auto idValue = fromValue.getObject()->getIfPropertyExists(globalObject, builtinNames(vm).filenamePublicName())) {
if (idValue.isString()) {
fromValue = idValue;
}
}
}
auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), JSValue::encode(fromValue), false);
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
if (!JSC::JSValue::decode(result).isString()) {
JSC::throwException(globalObject, scope, JSC::JSValue::decode(result));
return JSC::JSValue::encode(JSC::JSValue{});
}
scope.release();
return result;
}
}
}
template <std::size_t N, class T> consteval std::size_t countof(T (&)[N]) {
return N;
}
JSC_DEFINE_CUSTOM_GETTER(get_resolveFilename, (JSGlobalObject * globalObject,
EncodedJSValue thisValue,
PropertyName propertyName)) {
auto override = static_cast<Zig::GlobalObject *>(globalObject)
->m_nodeModuleOverriddenResolveFilename.get();
if (override) {
return JSValue::encode(override);
}
// Instead of storing the original function on the global object and have
// those extra bytes, just have it be a property alias.
JSObject *thisObject = JSValue::decode(thisValue).getObject();
if (!thisObject)
return JSValue::encode(jsUndefined());
auto &vm = globalObject->vm();
return JSValue::encode(thisObject->getDirect(
vm, Identifier::fromString(vm, "__resolveFilename"_s)));
}
JSC_DEFINE_CUSTOM_SETTER(set_resolveFilename,
(JSGlobalObject * globalObject,
EncodedJSValue thisValue, EncodedJSValue value,
PropertyName propertyName)) {
auto valueJS = JSValue::decode(value);
if (valueJS.isCell()) {
if (auto fn = jsDynamicCast<JSFunction *>(valueJS.asCell())) {
static_cast<Zig::GlobalObject *>(globalObject)
->m_nodeModuleOverriddenResolveFilename.set(globalObject->vm(),
globalObject, fn);
return true;
}
}
return false;
}
extern "C" bool ModuleLoader__isBuiltin(const char* data, size_t len);
struct Parent {
JSArray* paths;
JSString* filename;
};
Parent getParent(VM&vm, JSGlobalObject* global, JSValue maybe_parent) {
Parent value { nullptr, nullptr };
if (!maybe_parent) {
return value;
}
auto parent = maybe_parent.getObject();
if (!parent) {
return value;
}
auto scope = DECLARE_THROW_SCOPE(vm);
const auto& builtinNames = Bun::builtinNames(vm);
JSValue paths = parent->get(global, builtinNames.pathsPublicName());
RETURN_IF_EXCEPTION(scope, value);
if (paths.isCell()) {
value.paths = jsDynamicCast<JSArray*>(paths);
}
JSValue filename = parent->get(global, builtinNames.filenamePublicName());
RETURN_IF_EXCEPTION(scope, value);
if (filename.isString()) {
value.filename = filename.toString(global);
}
RELEASE_AND_RETURN(scope, value);
}
// https://github.com/nodejs/node/blob/40ef9d541ed79470977f90eb445c291b95ab75a0/lib/internal/modules/cjs/loader.js#L895
JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveLookupPaths, (JSC::JSGlobalObject * globalObject, JSC::CallFrame *callFrame)) {
auto &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
String request = callFrame->argument(0).toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
auto utf8 = request.utf8();
if(ModuleLoader__isBuiltin(utf8.data(), utf8.length())) {
return JSC::JSValue::encode(JSC::jsNull());
}
auto parent = getParent(vm, globalObject, callFrame->argument(1));
RETURN_IF_EXCEPTION(scope, {});
// Check for node modules paths.
if (
request.characterAt(0) != '.' ||
(request.length() > 1 &&
request.characterAt(1) != '.' &&
request.characterAt(1) != '/' &&
#if OS(WINDOWS)
request.characterAt(1) != '\\'
#else
true
#endif
)
) {
auto array = JSC::constructArray(globalObject, (ArrayAllocationProfile*)nullptr, nullptr, 0);
if (parent.paths) {
auto len = parent.paths->length();
for (size_t i = 0; i < len; i++) {
auto path = parent.paths->getIndex(globalObject, i);
array->push(globalObject, path);
}
}
return JSValue::encode(array);
}
JSValue dirname;
if (parent.filename) {
EncodedJSValue encodedFilename = JSValue::encode(parent.filename);
#if OS(WINDOWS)
dirname = JSValue::decode(Bun__Path__dirname(globalObject, true, &encodedFilename, 1));
#else
dirname = JSValue::decode(Bun__Path__dirname(globalObject, false, &encodedFilename, 1));
#endif
} else {
dirname = jsString(vm, String("."_s));
}
JSValue values[] = { dirname };
auto array = JSC::constructArray(
globalObject,
(ArrayAllocationProfile*)nullptr,
values,
1
);
RELEASE_AND_RETURN(scope, JSValue::encode(array));
}
extern "C" JSC::EncodedJSValue NodeModuleModule__findPath(JSGlobalObject*, BunString, JSArray*);
JSC_DEFINE_HOST_FUNCTION(jsFunctionFindPath, (JSGlobalObject * globalObject, JSC::CallFrame *callFrame)) {
JSC::VM &vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue request_value = callFrame->argument(0);
JSValue paths_value = callFrame->argument(1);
String request = request_value.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
BunString request_bun_str = Bun::toString(request);
JSArray* paths = paths_value.isCell() ? jsDynamicCast<JSArray*>(paths_value) : nullptr;
return NodeModuleModule__findPath(globalObject, request_bun_str, paths);
}
// These two setters are only used if you directly hit
// `Module.prototype.require` or `module.require`. When accessing the cjs
// require argument, this is a bound version of `require`, which calls into the
// overridden one.
//
// This require function also intentionally does not have .resolve on it, nor
// does it have any of the other properties.
//
// Note: allowing require to be overridable at all is only needed for Next.js to
// work (they do Module.prototype.require = ...)
JSC_DEFINE_CUSTOM_GETTER(getterRequireFunction,
(JSC::JSGlobalObject * globalObject,
JSC::EncodedJSValue thisValue, JSC::PropertyName)) {
return JSValue::encode(globalObject->getDirect(
globalObject->vm(), WebCore::clientData(globalObject->vm())
->builtinNames()
.overridableRequirePrivateName()));
}
JSC_DEFINE_CUSTOM_SETTER(setterRequireFunction,
(JSC::JSGlobalObject * globalObject,
JSC::EncodedJSValue thisValue,
JSC::EncodedJSValue value,
JSC::PropertyName propertyName)) {
globalObject->putDirect(globalObject->vm(),
WebCore::clientData(globalObject->vm())
->builtinNames()
.overridableRequirePrivateName(),
JSValue::decode(value), 0);
return true;
}
namespace Zig {
DEFINE_NATIVE_MODULE(NodeModule) {
// the default object here is a function, so we cant use the
// INIT_NATIVE_MODULE helper
Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject *>(lexicalGlobalObject);
JSC::VM &vm = globalObject->vm();
JSC::JSObject *defaultObject = JSC::JSFunction::create(
vm, globalObject, 0, "Module"_s, jsFunctionNodeModuleModuleConstructor,
JSC::ImplementationVisibility::Public, JSC::NoIntrinsic,
jsFunctionNodeModuleModuleConstructor);
auto put = [&](JSC::Identifier name, JSC::JSValue value) {
defaultObject->putDirect(vm, name, value);
exportNames.append(name);
exportValues.append(value);
};
auto putNativeFn = [&](JSC::Identifier name, JSC::NativeFunction ptr) {
JSC::JSFunction *value = JSC::JSFunction::create(
vm, globalObject, 1, name.string(), ptr,
JSC::ImplementationVisibility::Public, JSC::NoIntrinsic, ptr);
defaultObject->putDirect(vm, name, value);
exportNames.append(name);
exportValues.append(value);
};
exportNames.reserveCapacity(16);
exportValues.ensureCapacity(16);
exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(defaultObject);
void generateNativeModule_NodeModule(
JSC::JSGlobalObject *lexicalGlobalObject, JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4> &exportNames,
JSC::MarkedArgumentBuffer &exportValues);
put(Identifier::fromString(vm, "Module"_s), defaultObject);
// Module._extensions === require.extensions
put(
Identifier::fromString(vm, "_extensions"_s),
globalObject->requireFunctionUnbound()->get( globalObject, Identifier::fromString(vm, "extensions"_s))
);
defaultObject->putDirectCustomAccessor(
vm,
JSC::Identifier::fromString(vm, "_resolveFilename"_s),
JSC::CustomGetterSetter::create(vm, get_resolveFilename, set_resolveFilename),
JSC::PropertyAttribute::CustomAccessor | 0
);
putNativeFn(Identifier::fromString(vm, "__resolveFilename"_s), jsFunctionResolveFileName);
putNativeFn(Identifier::fromString(vm, "_resolveLookupPaths"_s), jsFunctionResolveLookupPaths);
putNativeFn(Identifier::fromString(vm, "createRequire"_s), jsFunctionNodeModuleCreateRequire);
putNativeFn(builtinNames(vm).pathsPublicName(), Resolver__nodeModulePathsForJS);
putNativeFn(Identifier::fromString(vm, "findSourceMap"_s), jsFunctionFindSourceMap);
putNativeFn(Identifier::fromString(vm, "syncBuiltinExports"_s), jsFunctionSyncBuiltinExports);
putNativeFn(Identifier::fromString(vm, "SourceMap"_s), jsFunctionSourceMap);
putNativeFn(Identifier::fromString(vm, "isBuiltin"_s), jsFunctionIsBuiltinModule);
putNativeFn(Identifier::fromString(vm, "_nodeModulePaths"_s), Resolver__nodeModulePathsForJS);
putNativeFn(Identifier::fromString(vm, "wrap"_s), jsFunctionWrap);
put(Identifier::fromString(vm, "_cache"_s), jsCast<Zig::GlobalObject *>(globalObject)->lazyRequireCacheObject());
put(Identifier::fromString(vm, "globalPaths"_s), constructEmptyArray(globalObject, nullptr, 0));
auto constants_compileCacheStatus = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
constants_compileCacheStatus->putDirect(vm, Identifier::fromString(vm, "FAILED"_s), jsNumber(0));
constants_compileCacheStatus->putDirect(vm, Identifier::fromString(vm, "ENABLED"_s), jsNumber(1));
constants_compileCacheStatus->putDirect(vm, Identifier::fromString(vm, "ALREADY_ENABLED"_s), jsNumber(2));
constants_compileCacheStatus->putDirect(vm, Identifier::fromString(vm, "DISABLED"_s), jsNumber(3));
auto constants = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
constants->putDirect(vm, Identifier::fromString(vm, "compileCacheStatus"_s), constants_compileCacheStatus);
put(Identifier::fromString(vm,"constants"_s), constants);
auto prototype = constructEmptyObject(globalObject, globalObject->objectPrototype(), 1);
prototype->putDirectCustomAccessor(
vm, WebCore::clientData(vm)->builtinNames().requirePublicName(),
JSC::CustomGetterSetter::create(
vm,
getterRequireFunction,
setterRequireFunction
),
0
);
defaultObject->putDirect(vm, vm.propertyNames->prototype, prototype);
MarkedArgumentBuffer args;
args.ensureCapacity(countof(builtinModuleNames));
for (unsigned i = 0; i < countof(builtinModuleNames); ++i) {
args.append(JSC::jsOwnedString(vm, String(builtinModuleNames[i])));
}
put(JSC::Identifier::fromString(vm, "builtinModules"_s), JSC::constructArray(globalObject, static_cast<JSC::ArrayAllocationProfile*>(nullptr), JSC::ArgList(args)));
}
} // namespace Zig

View File

@@ -24,18 +24,25 @@
// If you decide to not use INIT_NATIVE_MODULE. make sure the first property
// given is the default export
#define BUN_FOREACH_NATIVE_MODULE(macro) \
#define BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE(macro) \
macro("bun"_s, BunObject) \
macro("bun:test"_s, BunTest) \
macro("bun:jsc"_s, BunJSC) \
macro("node:buffer"_s, NodeBuffer) \
macro("node:constants"_s, NodeConstants) \
macro("node:module"_s, NodeModule) \
macro("node:process"_s, NodeProcess) \
macro("node:string_decoder"_s, NodeStringDecoder) \
macro("node:util/types"_s, NodeUtilTypes) \
macro("utf-8-validate"_s, UTF8Validate) \
macro("abort-controller"_s, AbortControllerModule) \
macro("abort-controller"_s, AbortControllerModule)
#define BUN_FOREACH_ESM_NATIVE_MODULE(macro) \
BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE(macro) \
macro("node:module"_s, NodeModule) \
macro("node:process"_s, NodeProcess)
#define BUN_FOREACH_CJS_NATIVE_MODULE(macro) \
BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE(macro)
#if ASSERT_ENABLED

View File

@@ -1104,6 +1104,21 @@ pub inline fn joinPosixT(comptime T: type, paths: []const []const T, buf: []T, b
return normalizePosixT(T, joined, buf);
}
export fn Bun__Node__Path_joinWTF(lhs: *bun.String, rhs_ptr: [*]const u8, rhs_len: usize, result: *bun.String) void {
const rhs = rhs_ptr[0..rhs_len];
var buf: [PATH_SIZE(u8)]u8 = undefined;
var buf2: [PATH_SIZE(u8)]u8 = undefined;
var slice = lhs.toUTF8(bun.default_allocator);
defer slice.deinit();
if (Environment.isWindows) {
const win = joinWindowsT(u8, &.{ slice.slice(), rhs }, &buf, &buf2);
result.* = bun.String.createUTF8(win);
} else {
const posix = joinPosixT(u8, &.{ slice.slice(), rhs }, &buf, &buf2);
result.* = bun.String.createUTF8(posix);
}
}
/// Based on Node v21.6.1 path.win32.join:
/// https://github.com/nodejs/node/blob/6ae20aa63de78294b18d5015481485b7cd8fbb60/lib/path.js#L425
pub fn joinWindowsT(comptime T: type, paths: []const []const T, buf: []T, buf2: []T) []const T {

View File

@@ -29,19 +29,14 @@ export function createInternalModuleRegistry(basedir: string) {
moduleList.push("internal-for-testing.ts");
internalRegistry.set("bun:internal-for-testing", moduleList.length - 1);
// Native Module registry
const nativeModuleH = fs.readFileSync(path.join(basedir, "../bun.js/modules/_NativeModule.h"), "utf8");
const nativeModuleDefine = nativeModuleH.match(/BUN_FOREACH_NATIVE_MODULE\(macro\)\s*\\\n((.*\\\n)*\n)/);
if (!nativeModuleDefine) {
throw new Error(
"Could not find BUN_FOREACH_NATIVE_MODULE in _NativeModule.h. Knowing native module IDs is a part of the codegen process.",
);
}
let nextNativeModuleId = 0;
const nativeModuleIds: Record<string, number> = {};
const nativeModuleEnums: Record<string, string> = {};
const nativeModuleEnumToId: Record<string, number> = {};
for (const [_, idString, enumValue] of nativeModuleDefine[0].matchAll(/macro\((.*?),(.*?)\)/g)) {
// Native Module registry
const nativeModuleH = fs.readFileSync(path.join(basedir, "../bun.js/modules/_NativeModule.h"), "utf8");
for (const [_, idString, enumValue] of nativeModuleH.matchAll(/macro\((.*?),(.*?)\)/g)) {
const processedIdString = JSON.parse(idString.trim().replace(/_s$/, ""));
const processedEnumValue = enumValue.trim();
const processedNumericId = nextNativeModuleId++;
@@ -50,6 +45,12 @@ export function createInternalModuleRegistry(basedir: string) {
nativeModuleEnumToId[processedEnumValue] = processedNumericId;
}
if (nextNativeModuleId === 0) {
throw new Error(
"Could not find BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE in _NativeModule.h. Knowing native module IDs is a part of the codegen process.",
);
}
function codegenRequireId(id: string) {
return `(__intrinsic__getInternalField(__intrinsic__internalModuleRegistry, ${id}) || __intrinsic__createInternalModuleById(${id}))`;
}

View File

@@ -135,3 +135,9 @@ export const isOperatingSystemMatch: (operatingSystem: string[]) => boolean = $n
);
export const createSocketPair: () => [number, number] = $newZigFunction("socket.zig", "jsCreateSocketPair", 0);
export const isModuleResolveFilenameSlowPathEnabled: () => boolean = $newCppFunction(
"NodeModuleModule.cpp",
"jsFunctionIsModuleResolveFilenameSlowPathEnabled",
0,
);

View File

@@ -2,10 +2,11 @@ import { spawnSync } from "bun";
import { expect, it } from "bun:test";
import { bunEnv, bunExe, ospath } from "harness";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import * as Module from "node:module";
import Module from "node:module";
import { tmpdir } from "node:os";
import { join, sep } from "node:path";
import sync from "./require-json.json";
import { isModuleResolveFilenameSlowPathEnabled } from "bun:internal-for-testing";
const { path, dir, dirname, filename } = import.meta;
@@ -127,7 +128,12 @@ it("Module._cache", () => {
});
it("Module._resolveFilename()", () => {
expect(Module._resolveFilename).toBeUndefined();
expect(isModuleResolveFilenameSlowPathEnabled()).toBe(false);
const original = Module._resolveFilename;
Module._resolveFilename = () => {};
expect(isModuleResolveFilenameSlowPathEnabled()).toBe(true);
Module._resolveFilename = original;
expect(isModuleResolveFilenameSlowPathEnabled()).toBe(false);
});
it("Module.createRequire(file://url).resolve(file://url)", () => {

View File

@@ -1,6 +1,6 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, ospath } from "harness";
import Module, { _nodeModulePaths, builtinModules, isBuiltin, wrap } from "module";
import Module, { _nodeModulePaths, builtinModules, isBuiltin, wrap, createRequire } from "module";
import path from "path";
test("builtinModules exists", () => {
@@ -23,6 +23,20 @@ test("module.globalPaths exists", () => {
expect(Array.isArray(require("module").globalPaths)).toBe(true);
});
test("createRequire trailing slash", () => {
const req = createRequire(import.meta.dir + "/");
expect(req.resolve("./node-module-module.test.js")).toBe(
ospath(path.resolve(import.meta.dir, "./node-module-module.test.js")),
);
});
test("createRequire trailing slash file url", () => {
const req = createRequire(Bun.pathToFileURL(import.meta.dir + "/"));
expect(req.resolve("./node-module-module.test.js")).toBe(
ospath(path.resolve(import.meta.dir, "./node-module-module.test.js")),
);
});
test("Module exists", () => {
expect(Module).toBeDefined();
});