From dbd04816668ddce0e9d0c00f57e574f262ed740e Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Fri, 4 Apr 2025 14:50:30 -0700 Subject: [PATCH] wip --- src/bun.js/api/BunObject.zig | 84 ++++++++++++------------- src/bun.js/bindings/BunObject.cpp | 21 +++---- src/bun.js/bindings/BunObject.h | 2 - src/bun.js/bindings/BunProcess.cpp | 15 ++--- src/bun.js/bindings/JSGlobalObject.zig | 65 +++++++++++++++++++ src/bun.js/bindings/ZigGlobalObject.cpp | 10 ++- src/bun.js/bindings/ZigGlobalObject.h | 3 + test/js/node/process/process.test.js | 23 ++++++- 8 files changed, 156 insertions(+), 67 deletions(-) diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 5105068633..08dd83143b 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -62,7 +62,6 @@ pub const BunObject = struct { pub const enableANSIColors = toJSGetter(Bun.enableANSIColors); pub const hash = toJSGetter(Bun.getHashObject); pub const inspect = toJSGetter(Bun.getInspect); - pub const main = toJSGetter(Bun.getMain); pub const origin = toJSGetter(Bun.getOrigin); pub const semver = toJSGetter(Bun.getSemver); pub const stderr = toJSGetter(Bun.getStderr); @@ -120,7 +119,6 @@ pub const BunObject = struct { @export(&BunObject.enableANSIColors, .{ .name = getterName("enableANSIColors") }); @export(&BunObject.hash, .{ .name = getterName("hash") }); @export(&BunObject.inspect, .{ .name = getterName("inspect") }); - @export(&BunObject.main, .{ .name = getterName("main") }); @export(&BunObject.origin, .{ .name = getterName("origin") }); @export(&BunObject.stderr, .{ .name = getterName("stderr") }); @export(&BunObject.stdin, .{ .name = getterName("stdin") }); @@ -564,55 +562,55 @@ pub fn enableANSIColors(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.J return JSValue.jsBoolean(Output.enable_ansi_colors); } -pub fn getMain(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue { - const vm = globalThis.bunVM(); +// pub fn getMain(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue { +// const vm = globalThis.bunVM(); - // Attempt to use the resolved filesystem path - // This makes `eval('require.main === module')` work when the main module is a symlink. - // This behavior differs slightly from Node. Node sets the `id` to `.` when the main module is a symlink. - use_resolved_path: { - if (vm.main_resolved_path.isEmpty()) { - // If it's from eval, don't try to resolve it. - if (strings.hasSuffixComptime(vm.main, "[eval]")) { - break :use_resolved_path; - } - if (strings.hasSuffixComptime(vm.main, "[stdin]")) { - break :use_resolved_path; - } +// // Attempt to use the resolved filesystem path +// // This makes `eval('require.main === module')` work when the main module is a symlink. +// // This behavior differs slightly from Node. Node sets the `id` to `.` when the main module is a symlink. +// use_resolved_path: { +// if (vm.main_resolved_path.isEmpty()) { +// // If it's from eval, don't try to resolve it. +// if (strings.hasSuffixComptime(vm.main, "[eval]")) { +// break :use_resolved_path; +// } +// if (strings.hasSuffixComptime(vm.main, "[stdin]")) { +// break :use_resolved_path; +// } - const fd = bun.sys.openatA( - if (comptime Environment.isWindows) bun.invalid_fd else bun.FD.cwd(), - vm.main, +// const fd = bun.sys.openatA( +// if (comptime Environment.isWindows) bun.invalid_fd else bun.FD.cwd(), +// vm.main, - // Open with the minimum permissions necessary for resolving the file path. - if (comptime Environment.isLinux) bun.O.PATH else bun.O.RDONLY, +// // Open with the minimum permissions necessary for resolving the file path. +// if (comptime Environment.isLinux) bun.O.PATH else bun.O.RDONLY, - 0, - ).unwrap() catch break :use_resolved_path; +// 0, +// ).unwrap() catch break :use_resolved_path; - defer _ = bun.sys.close(fd); - if (comptime Environment.isWindows) { - var wpath: bun.WPathBuffer = undefined; - const fdpath = bun.getFdPathW(fd, &wpath) catch break :use_resolved_path; - vm.main_resolved_path = bun.String.createUTF16(fdpath); - } else { - var path: bun.PathBuffer = undefined; - const fdpath = bun.getFdPath(fd, &path) catch break :use_resolved_path; +// defer _ = bun.sys.close(fd); +// if (comptime Environment.isWindows) { +// var wpath: bun.WPathBuffer = undefined; +// const fdpath = bun.getFdPathW(fd, &wpath) catch break :use_resolved_path; +// vm.main_resolved_path = bun.String.createUTF16(fdpath); +// } else { +// var path: bun.PathBuffer = undefined; +// const fdpath = bun.getFdPath(fd, &path) catch break :use_resolved_path; - // Bun.main === otherId will be compared many times, so let's try to create an atom string if we can. - if (bun.String.tryCreateAtom(fdpath)) |atom| { - vm.main_resolved_path = atom; - } else { - vm.main_resolved_path = bun.String.createUTF8(fdpath); - } - } - } +// // Bun.main === otherId will be compared many times, so let's try to create an atom string if we can. +// if (bun.String.tryCreateAtom(fdpath)) |atom| { +// vm.main_resolved_path = atom; +// } else { +// vm.main_resolved_path = bun.String.createUTF8(fdpath); +// } +// } +// } - return vm.main_resolved_path.toJS(globalThis); - } +// return vm.main_resolved_path.toJS(globalThis); +// } - return ZigString.init(vm.main).toJS(globalThis); -} +// return ZigString.init(vm.main).toJS(globalThis); +// } pub fn getArgv(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue { return JSC.Node.Process.getArgv(globalThis); diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index 67c7286645..17646085d0 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -79,10 +79,9 @@ namespace Bun { extern "C" bool has_bun_garbage_collector_flag_enabled; -JSValue getMain(JSC::VM& vm, JSC::JSObject* object) +static JSValue BunObject__mainModule(JSC::VM& vm, JSC::JSObject* bunObject) { - // See: BunObject.zig - return BunObject_getter_wrap_main(vm, object); + return jsCast(bunObject->globalObject())->mainModule(); } static JSValue BunObject_getter_wrap_ArrayBufferSink(VM& vm, JSObject* bunObject) @@ -705,8 +704,8 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj @begin bunObjectTable $ constructBunShell DontDelete|PropertyCallback ArrayBufferSink BunObject_getter_wrap_ArrayBufferSink DontDelete|PropertyCallback - Cookie constructCookieObject DontDelete|ReadOnly|PropertyCallback - CookieMap constructCookieMapObject DontDelete|ReadOnly|PropertyCallback + Cookie constructCookieObject DontDelete|ReadOnly|PropertyCallback + CookieMap constructCookieMapObject DontDelete|ReadOnly|PropertyCallback CryptoHasher BunObject_getter_wrap_CryptoHasher DontDelete|PropertyCallback FFI BunObject_getter_wrap_FFI DontDelete|PropertyCallback FileSystemRouter BunObject_getter_wrap_FileSystemRouter DontDelete|PropertyCallback @@ -734,14 +733,14 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj color BunObject_callback_color DontDelete|Function 2 deepEquals functionBunDeepEquals DontDelete|Function 2 deepMatch functionBunDeepMatch DontDelete|Function 2 - deflateSync BunObject_callback_deflateSync DontDelete|Function 1 + deflateSync BunObject_callback_deflateSync DontDelete|Function 1 dns constructDNSObject ReadOnly|DontDelete|PropertyCallback enableANSIColors BunObject_getter_wrap_enableANSIColors DontDelete|PropertyCallback env constructEnvObject ReadOnly|DontDelete|PropertyCallback escapeHTML functionBunEscapeHTML DontDelete|Function 2 - fetch constructBunFetchObject ReadOnly|DontDelete|PropertyCallback - file BunObject_callback_file DontDelete|Function 1 - fileURLToPath functionFileURLToPath DontDelete|Function 1 + fetch constructBunFetchObject ReadOnly|DontDelete|PropertyCallback + file BunObject_callback_file DontDelete|Function 1 + fileURLToPath functionFileURLToPath DontDelete|Function 1 gc Generated::BunObject::jsGc DontDelete|Function 1 generateHeapSnapshot functionGenerateHeapSnapshot DontDelete|Function 1 gunzipSync BunObject_callback_gunzipSync DontDelete|Function 1 @@ -753,8 +752,8 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj isMainThread constructIsMainThread ReadOnly|DontDelete|PropertyCallback jest BunObject_callback_jest DontEnum|DontDelete|Function 1 listen BunObject_callback_listen DontDelete|Function 1 - udpSocket BunObject_callback_udpSocket DontDelete|Function 1 - main BunObject_getter_wrap_main DontDelete|PropertyCallback + udpSocket BunObject_callback_udpSocket DontDelete|Function 1 + main BunObject__mainModule DontDelete|PropertyCallback mmap BunObject_callback_mmap DontDelete|Function 1 nanoseconds functionBunNanoseconds DontDelete|Function 0 openInEditor BunObject_callback_openInEditor DontDelete|Function 1 diff --git a/src/bun.js/bindings/BunObject.h b/src/bun.js/bindings/BunObject.h index d41a53bd7d..e42d23c7f1 100644 --- a/src/bun.js/bindings/BunObject.h +++ b/src/bun.js/bindings/BunObject.h @@ -14,8 +14,6 @@ JSC_DECLARE_HOST_FUNCTION(functionFileURLToPath); JSC::JSValue constructBunFetchObject(VM& vm, JSObject* bunObject); JSC::JSObject* createBunObject(VM& vm, JSObject* globalObject); -/// Get the name/path of the main module, if there is one. Returns a string or undefined. -JSC::JSValue getMain(JSC::VM& vm, JSC::JSObject* object); JSC::JSObject* BunShell(JSGlobalObject* globalObject); JSC::JSValue ShellError(JSGlobalObject* globalObject); diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 30fcdc522d..1c0b949b4c 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -2,7 +2,6 @@ #include "napi.h" #include "BunProcess.h" -#include "BunObject.h" #include "headers-handwritten.h" #include #include @@ -3528,13 +3527,15 @@ JSC_DEFINE_CUSTOM_GETTER(processMainModule, (JSC::JSGlobalObject * lexicalGlobal } auto* global = reinterpret_cast(lexicalGlobalObject); - auto* requireMap = global->requireMap(); - ASSERT(requireMap); - auto mainValue = Bun::getMain(vm, global); - if (UNLIKELY(!mainValue.isCell())) return JSValue::encode(mainValue); - ASSERT(mainValue.isString()); - auto mainModule = requireMap->get(global, mainValue); + auto* requireCache = global->lazyRequireCacheObject(); + auto* mm = global->mainModule(); + if (UNLIKELY(mm->length() == 0)) { + thisObject->putDirect(vm, mainIdent, jsUndefined(), 0); + return JSValue::encode(jsUndefined()); + } + auto prop = PropertyName(JSC::Identifier::fromString(vm, WTFMove(mm->getString(global)))); + auto mainModule = requireCache->getIfPropertyExists(global, prop); thisObject->putDirect(vm, mainIdent, mainModule, 0); return JSValue::encode(mainModule); } diff --git a/src/bun.js/bindings/JSGlobalObject.zig b/src/bun.js/bindings/JSGlobalObject.zig index 9c6efd280a..0de68dba0b 100644 --- a/src/bun.js/bindings/JSGlobalObject.zig +++ b/src/bun.js/bindings/JSGlobalObject.zig @@ -761,6 +761,70 @@ pub const JSGlobalObject = opaque { return Zig__GlobalObject__resetModuleRegistryMap(global, map); } + /// Name of main module. This is usually a path, but can be something else + /// when Bun is not run on a file (e.g. `[eval]` for `bun --eval `). + /// Always returns a `JSC::JSString*`. + /// + /// You usually don't need to call this directly since this is lazily + /// initialized and cached on the global object. + fn determineMainModule(globalThis: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { + const bun_vm = globalThis.bunVM(); + + // Attempt to use the resolved filesystem path + // This makes `eval('require.main === module')` work when the main module is a symlink. + // This behavior differs slightly from Node. Node sets the `id` to `.` when the main module is a symlink. + use_resolved_path: { + if (bun_vm.main_resolved_path.isEmpty()) { + // If it's from eval, don't try to resolve it. + if (strings.hasSuffixComptime(bun_vm.main, "[eval]")) { + @branchHint(.unlikely); + break :use_resolved_path; + } + if (strings.hasSuffixComptime(bun_vm.main, "[stdin]")) { + @branchHint(.unlikely); + break :use_resolved_path; + } + if (bun_vm.main.len == 0) { + @branchHint(.cold); + return bun.String.empty.toJS(globalThis); + } + + const fd = bun.sys.openatA( + if (comptime bun.Environment.isWindows) bun.invalid_fd else bun.FD.cwd(), + bun_vm.main, + + // Open with the minimum permissions necessary for resolving the file path. + if (comptime bun.Environment.isLinux) bun.O.PATH else bun.O.RDONLY, + + 0, + ).unwrap() catch break :use_resolved_path; + + defer _ = bun.sys.close(fd); + if (comptime bun.Environment.isWindows) { + var wpath: bun.WPathBuffer = undefined; + const fdpath = bun.getFdPathW(fd, &wpath) catch break :use_resolved_path; + bun_vm.main_resolved_path = bun.String.createUTF16(fdpath); + } else { + var path: bun.PathBuffer = undefined; + const fdpath = bun.getFdPath(fd, &path) catch break :use_resolved_path; + + // Bun.main === otherId will be compared many times, so let's try to create an atom string if we can. + if (bun.String.tryCreateAtom(fdpath)) |atom| { + bun_vm.main_resolved_path = atom; + } else { + bun_vm.main_resolved_path = bun.String.createUTF8(fdpath); + } + } + } + + return bun_vm.main_resolved_path.toJS(globalThis); + } + + // NOTE: we cannot assume `main` is ascii. + var main_module = bun.String.init(bun_vm.main); + return main_module.transferToJS(globalThis); + } + pub fn resolve(res: *ErrorableString, global: *JSGlobalObject, specifier: *bun.String, source: *bun.String, query: *ZigString) callconv(.C) void { JSC.markBinding(@src()); return JSC.VirtualMachine.resolve(res, global, specifier.*, source.*, query, true) catch { @@ -782,6 +846,7 @@ pub const JSGlobalObject = opaque { pub const Extern = [_][]const u8{ "create", "getModuleRegistryMap", "resetModuleRegistryMap" }; comptime { + @export(&determineMainModule, .{ .name = "Zig__GlobalObject__determineMainModule" }); @export(&resolve, .{ .name = "Zig__GlobalObject__resolve" }); @export(&reportUncaughtException, .{ .name = "Zig__GlobalObject__reportUncaughtException" }); @export(&onCrash, .{ .name = "Zig__GlobalObject__onCrash" }); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 4f50452f4a..19a6f26730 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -218,7 +218,8 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers; constexpr size_t DEFAULT_ERROR_STACK_TRACE_LIMIT = 10; -// #include +// defined in JSGlobalObject.zig +extern "C" JSC::EncodedJSValue Zig__GlobalObject__determineMainModule(JSC::JSGlobalObject* globalObject); Structure* createMemoryFootprintStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject); @@ -3338,6 +3339,13 @@ void GlobalObject::finishCreation(VM& vm) InternalModuleRegistry::createStructure(init.vm, init.owner))); }); + m_mainModule.initLater( + [](const JSC::LazyProperty::Initializer& init) { + JSC::JSValue mainModule = JSValue::decode(Zig__GlobalObject__determineMainModule(init.owner)); + ASSERT(mainModule.isString()); + init.set(jsCast(mainModule)); + }); + m_processBindingBuffer.initLater( [](const JSC::LazyProperty::Initializer& init) { init.set( diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 60e3940bc6..45f0d31e7c 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -263,6 +263,8 @@ public: JSObject* requireFunctionUnbound() const { return m_requireFunctionUnbound.getInitializedOnMainThread(this); } JSObject* requireResolveFunctionUnbound() const { return m_requireResolveFunctionUnbound.getInitializedOnMainThread(this); } Bun::InternalModuleRegistry* internalModuleRegistry() const { return m_internalModuleRegistry.getInitializedOnMainThread(this); } + /// Name of entrypoint module. Usually a path. + JSString* mainModule() const { return m_mainModule.getInitializedOnMainThread(this); } JSObject* processBindingBuffer() const { return m_processBindingBuffer.getInitializedOnMainThread(this); } JSObject* processBindingConstants() const { return m_processBindingConstants.getInitializedOnMainThread(this); } @@ -612,6 +614,7 @@ public: LazyProperty m_requireFunctionUnbound; LazyProperty m_requireResolveFunctionUnbound; LazyProperty m_internalModuleRegistry; + LazyProperty m_mainModule; // string | undefined. may be null. LazyProperty m_processBindingBuffer; LazyProperty m_processBindingConstants; LazyProperty m_processBindingFs; diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 3e8765fc46..3d539ad48e 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -1,8 +1,8 @@ import { spawnSync, which } from "bun"; -import { describe, expect, it } from "bun:test"; +import { describe, it, expect } from "bun:test"; import { existsSync, readFileSync, writeFileSync } from "fs"; import { bunEnv, bunExe, isWindows, tmpdirSync } from "harness"; -import path, { basename, join, resolve } from "path"; +import { basename, join, resolve } from "path"; import { familySync } from "detect-libc"; expect.extend({ @@ -1100,7 +1100,7 @@ it("process.memoryUsage.arrayBuffers", () => { expect(process.memoryUsage().arrayBuffers).toBeGreaterThanOrEqual(initial + 16 * 1024 * 1024); }); -describe("process.mainModule", () => { +describe.only("process.mainModule", () => { it("is undefined when run via REPL", async () => { await Bun.$`${bunExe()} -e 'assert(process.mainModule === undefined)'`.nothrow(); }); @@ -1118,4 +1118,21 @@ describe("process.mainModule", () => { expect(process.mainModule).toBe("foo"); process.mainModule = prev; }); + + describe("When accessed from a file", () => { + it("is a NodeJS.Module object", () => { + expect(process.mainModule).toBeObject(); + expect(process.mainModule).toMatchObject({ + children: expect.arrayContaining([expect.any(Object)]), + exports: { foo: "bar" }, + filename: __filename, + id: expect.any(String), + isPreloading: expect.any(Boolean), + loaded: true, + path: __dirname, + paths: expect.arrayContaining([expect.any(String)]), + require: expect.any(Function), + }); + }); + }); // when accessed from a file });