From 1fb43d73b733e758fc9f987f30d7aff5ecd89542 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Sat, 9 Mar 2024 17:34:39 -0800 Subject: [PATCH 01/11] --print cli flag --- src/bun.js/ConsoleObject.zig | 2 +- src/bun.js/bindings/ZigGlobalObject.cpp | 31 +++++++++++++++++++++++-- src/bun.js/bindings/ZigGlobalObject.h | 5 +++- src/bun.js/bindings/bindings.cpp | 5 ++++ src/bun.js/bindings/bindings.zig | 22 ++++++++++++++++++ src/bun.js/javascript.zig | 15 ++++++++---- src/bun.js/module_loader.zig | 8 +++---- src/bun_js.zig | 23 ++++++++++++------ src/cli.zig | 15 +++++++++--- src/cli/run_command.zig | 4 ++-- 10 files changed, 106 insertions(+), 24 deletions(-) diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index b0d932494794e..fbde169bfa180 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -83,7 +83,7 @@ pub fn messageWithTypeAndLevel( //message_level: u32, level: MessageLevel, global: *JSGlobalObject, - vals: [*]JSValue, + vals: [*]const JSValue, len: usize, ) callconv(.C) void { if (comptime is_bindgen) { diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 4ea39198ab6bd..1a60a1853eaf0 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -974,7 +974,7 @@ JSC::ScriptExecutionStatus Zig::GlobalObject::scriptExecutionStatus(JSC::JSGloba } } -const JSC::GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = { +JSC::GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = { &supportsRichSourceInfo, &shouldInterruptScript, &javaScriptRuntimeFlags, @@ -990,10 +990,10 @@ const JSC::GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = { &reportUncaughtExceptionAtEventLoop, ¤tScriptExecutionOwner, &scriptExecutionStatus, + nullptr, // reportViolationForUnsafeEval nullptr, // defaultLanguage nullptr, // compileStreaming nullptr, // instantiateStreaming - nullptr, &Zig::deriveShadowRealmGlobalObject }; @@ -4554,6 +4554,33 @@ JSC::JSValue GlobalObject::moduleLoaderEvaluate(JSGlobalObject* globalObject, return result; } +extern "C" void Bun__VM__setEntryPointResult(void*, EncodedJSValue); + +JSC::JSValue GlobalObject::moduleLoaderEvaluateForEval(JSGlobalObject* globalObject, + JSModuleLoader* moduleLoader, JSValue key, + JSValue moduleRecordValue, JSValue scriptFetcher, + JSValue sentValue, JSValue resumeMode) +{ + s_globalObjectMethodTable.moduleLoaderEvaluate = &moduleLoaderEvaluate; + + if (UNLIKELY(scriptFetcher && scriptFetcher.isObject())) { + Bun__VM__setEntryPointResult(jsCast(globalObject)->bunVM(), JSValue::encode(scriptFetcher)); + return scriptFetcher; + } + + JSC::JSValue result = moduleLoader->evaluateNonVirtual(globalObject, key, moduleRecordValue, + scriptFetcher, sentValue, resumeMode); + + Bun__VM__setEntryPointResult(jsCast(globalObject)->bunVM(), JSValue::encode(result)); + + return result; +} + +void GlobalObject::setupModuleLoaderEvaluateForEval() +{ + s_globalObjectMethodTable.moduleLoaderEvaluate = &moduleLoaderEvaluateForEval; +} + GlobalObject::PromiseFunctions GlobalObject::promiseHandlerID(EncodedJSValue (*handler)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1)) { if (handler == Bun__HTTPRequestContext__onReject) { diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index dac97ca1224f0..7618a52ae60b4 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -69,7 +69,7 @@ class GlobalObject : public JSC::JSGlobalObject { public: static const JSC::ClassInfo s_info; - static const JSC::GlobalObjectMethodTable s_globalObjectMethodTable; + static JSC::GlobalObjectMethodTable s_globalObjectMethodTable; template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { @@ -169,6 +169,9 @@ class GlobalObject : public JSC::JSGlobalObject { static JSC::JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue); static JSC::JSObject* moduleLoaderCreateImportMetaProperties(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSModuleRecord*, JSC::JSValue); static JSC::JSValue moduleLoaderEvaluate(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue); + static JSC::JSValue moduleLoaderEvaluateForEval(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue); + void setupModuleLoaderEvaluateForEval(); + static ScriptExecutionStatus scriptExecutionStatus(JSGlobalObject*, JSObject*); static void promiseRejectionTracker(JSGlobalObject*, JSC::JSPromise*, JSC::JSPromiseRejectionOperation); void setConsole(void* console); diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 393df8ba00f10..75d93cd2f0860 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -5438,6 +5438,11 @@ extern "C" bool JSGlobalObject__hasException(JSC::JSGlobalObject* globalObject) return DECLARE_CATCH_SCOPE(globalObject->vm()).exception() != 0; } +extern "C" void JSGlobalObject__setupModuleLoaderEvaluateForEval(JSC::JSGlobalObject* globalObject) +{ + reinterpret_cast(globalObject)->setupModuleLoaderEvaluateForEval(); +} + CPP_DECL bool JSC__GetterSetter__isGetterNull(JSC__GetterSetter* gettersetter) { return gettersetter->isGetterNull(); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 5545c4f6c42a0..9957cef87f678 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -14,6 +14,7 @@ const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false; const ArrayBuffer = @import("../base.zig").ArrayBuffer; const JSC = @import("root").bun.JSC; const Shimmer = JSC.Shimmer; +const ConsoleObject = JSC.ConsoleObject; const FFI = @import("./FFI.zig"); const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator; const MutableString = bun.MutableString; @@ -2622,6 +2623,11 @@ pub const JSGlobalObject = extern struct { return @enumFromInt(@as(JSValue.Type, @bitCast(@intFromPtr(globalThis)))); } + extern fn JSGlobalObject__setupModuleLoaderEvaluateForEval(*JSGlobalObject) void; + pub fn setupModuleLoaderEvaluateForEval(this: *JSGlobalObject) void { + JSGlobalObject__setupModuleLoaderEvaluateForEval(this); + } + pub fn throwInvalidArguments( this: *JSGlobalObject, comptime fmt: string, @@ -4043,6 +4049,22 @@ pub const JSValue = enum(JSValueReprInt) { }); } + pub fn print( + this: JSValue, + globalObject: *JSGlobalObject, + message_type: ConsoleObject.MessageType, + message_level: ConsoleObject.MessageLevel, + ) void { + JSC.ConsoleObject.messageWithTypeAndLevel( + undefined, + message_type, + message_level, + globalObject, + &[_]JSC.JSValue{this}, + 1, + ); + } + /// Create a JSValue string from a zig format-print (fmt + args) pub fn printString(globalThis: *JSGlobalObject, comptime stack_buffer_size: usize, comptime fmt: []const u8, args: anytype) !JSValue { var stack_fallback = std.heap.stackFallback(stack_buffer_size, globalThis.allocator()); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 05d3c1a35569f..ec1d1858bb0b6 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -574,6 +574,8 @@ pub const VirtualMachine = struct { rare_data: ?*JSC.RareData = null, is_us_loop_entered: bool = false, pending_internal_promise: *JSC.JSInternalPromise = undefined, + entry_point_result: JSC.Strong = .{}, + auto_install_dependencies: bool = false, onUnhandledRejection: *const OnUnhandledRejection = defaultOnUnhandledRejection, @@ -893,8 +895,13 @@ pub const VirtualMachine = struct { return .running; } + pub fn setEntryPointResult(this: *VirtualMachine, value: JSValue) callconv(.C) void { + this.entry_point_result.set(this.global, value); + } + comptime { @export(scriptExecutionStatus, .{ .name = "Bun__VM__scriptExecutionStatus" }); + @export(setEntryPointResult, .{ .name = "Bun__VM__setEntryPointResult" }); } pub fn onExit(this: *VirtualMachine) void { @@ -1594,13 +1601,13 @@ pub const VirtualMachine = struct { break :brk options.Loader.file; }; - if (jsc_vm.module_loader.eval_script) |eval_script| { + if (jsc_vm.module_loader.eval_source) |eval_source| { if (strings.endsWithComptime(specifier, bun.pathLiteral("/[eval]"))) { - virtual_source = eval_script; + virtual_source = eval_source; loader = .tsx; } if (strings.endsWithComptime(specifier, bun.pathLiteral("/[stdin]"))) { - virtual_source = eval_script; + virtual_source = eval_source; loader = .tsx; } } @@ -1674,7 +1681,7 @@ pub const VirtualMachine = struct { ret.result = null; ret.path = result.path; return; - } else if (jsc_vm.module_loader.eval_script != null and + } else if (jsc_vm.module_loader.eval_source != null and (strings.endsWithComptime(specifier, bun.pathLiteral("/[eval]")) or strings.endsWithComptime(specifier, bun.pathLiteral("/[stdin]")))) { diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 7b5affdd84550..a176e69ca79dd 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -654,7 +654,7 @@ pub const RuntimeTranspilerStore = struct { pub const ModuleLoader = struct { transpile_source_code_arena: ?*bun.ArenaAllocator = null, - eval_script: ?*logger.Source = null, + eval_source: ?*logger.Source = null, const debug = Output.scoped(.ModuleLoader, true); @@ -2136,13 +2136,13 @@ pub const ModuleLoader = struct { // The concurrent one only handles javascript-like loaders right now. var loader: ?options.Loader = jsc_vm.bundler.options.loaders.get(path.name.ext); - if (jsc_vm.module_loader.eval_script) |eval_script| { + if (jsc_vm.module_loader.eval_source) |eval_source| { if (strings.endsWithComptime(specifier, bun.pathLiteral("/[eval]"))) { - virtual_source = eval_script; + virtual_source = eval_source; loader = .tsx; } if (strings.endsWithComptime(specifier, bun.pathLiteral("/[stdin]"))) { - virtual_source = eval_script; + virtual_source = eval_source; loader = .tsx; } } diff --git a/src/bun_js.zig b/src/bun_js.zig index d855002451743..34acffcb2ec71 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -182,12 +182,15 @@ pub const Run = struct { vm.arena = &run.arena; vm.allocator = arena.allocator(); - if (ctx.runtime_options.eval_script.len > 0) { - vm.module_loader.eval_script = ptr: { - const v = try bun.default_allocator.create(logger.Source); - v.* = logger.Source.initPathString(entry_path, ctx.runtime_options.eval_script); - break :ptr v; - }; + if (ctx.runtime_options.eval.script.len > 0) { + const script_source = try bun.default_allocator.create(logger.Source); + script_source.* = logger.Source.initPathString(entry_path, ctx.runtime_options.eval.script); + vm.module_loader.eval_source = script_source; + + if (ctx.runtime_options.eval.eval_and_print) { + b.options.dead_code_elimination = false; + vm.global.setupModuleLoaderEvaluateForEval(); + } } b.options.install = ctx.install; @@ -266,7 +269,7 @@ pub const Run = struct { vm.hot_reload = this.ctx.debug.hot_reload; vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose; - if (this.ctx.runtime_options.eval_script.len > 0) { + if (this.ctx.runtime_options.eval.script.len > 0) { Bun__ExposeNodeModuleGlobals(vm.global); } @@ -389,6 +392,12 @@ pub const Run = struct { vm.onExit(); + if (this.ctx.runtime_options.eval.eval_and_print) { + if (vm.entry_point_result.trySwap()) |result| { + result.print(vm.global, .Log, .Log); + } + } + if (!JSC.is_bindgen) JSC.napi.fixDeadCodeElimination(); Global.exit(exit_code); } diff --git a/src/cli.zig b/src/cli.zig index 94d572de5932c..185fe9e16659b 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -178,6 +178,7 @@ pub const Arguments = struct { clap.parseParam("--install Configure auto-install behavior. One of \"auto\" (default, auto-installs when no node_modules), \"fallback\" (missing packages only), \"force\" (always).") catch unreachable, clap.parseParam("-i Auto-install dependencies during execution. Equivalent to --install=fallback.") catch unreachable, clap.parseParam("-e, --eval Evaluate argument as a script") catch unreachable, + clap.parseParam("--print Evaluate argument as a script and print the result") catch unreachable, clap.parseParam("--prefer-offline Skip staleness checks for packages in the Bun runtime and resolve from disk") catch unreachable, clap.parseParam("--prefer-latest Use the latest matching versions of packages in the Bun runtime, always checking npm") catch unreachable, clap.parseParam("-p, --port Set the default port for Bun.serve") catch unreachable, @@ -576,7 +577,12 @@ pub const Arguments = struct { ctx.preloads = preloads; } - ctx.runtime_options.eval_script = args.option("--eval") orelse ""; + if (args.option("--eval")) |script| { + ctx.runtime_options.eval.script = script; + } else if (args.option("--print")) |script| { + ctx.runtime_options.eval.script = script; + ctx.runtime_options.eval.eval_and_print = true; + } ctx.runtime_options.if_present = args.flag("--if-present"); ctx.runtime_options.smol = args.flag("--smol"); if (args.option("--inspect")) |inspect_flag| { @@ -1096,7 +1102,10 @@ pub const Command = struct { smol: bool = false, debugger: Debugger = .{ .unspecified = {} }, if_present: bool = false, - eval_script: []const u8 = "", + eval: struct { + script: []const u8 = "", + eval_and_print: bool = false, + } = .{}, }; pub const Context = struct { @@ -1690,7 +1699,7 @@ pub const Command = struct { } }; - if (ctx.runtime_options.eval_script.len > 0) { + if (ctx.runtime_options.eval.script.len > 0) { const trigger = bun.pathLiteral("/[eval]"); var entry_point_buf: [bun.MAX_PATH_BYTES + trigger.len]u8 = undefined; const cwd = try std.os.getcwd(&entry_point_buf); diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 925b60da619bf..0c19686d17fef 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -1437,7 +1437,7 @@ pub const RunCommand = struct { errdefer list.deinit(); std.io.getStdIn().reader().readAllArrayList(&list, 1024 * 1024 * 1024) catch return false; - ctx.runtime_options.eval_script = list.items; + ctx.runtime_options.eval.script = list.items; const trigger = bun.pathLiteral("/[stdin]"); var entry_point_buf: [bun.MAX_PATH_BYTES + trigger.len]u8 = undefined; @@ -1528,7 +1528,7 @@ pub const RunCommand = struct { pub fn execAsIfNode(ctx: Command.Context) !void { std.debug.assert(CLI.pretend_to_be_node); - if (ctx.runtime_options.eval_script.len > 0) { + if (ctx.runtime_options.eval.script.len > 0) { const trigger = bun.pathLiteral("/[eval]"); var entry_point_buf: [bun.MAX_PATH_BYTES + trigger.len]u8 = undefined; const cwd = try std.os.getcwd(&entry_point_buf); From 50c68363becd05819b3d6b5b5f0fccd43228fe19 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Sat, 9 Mar 2024 19:22:18 -0800 Subject: [PATCH 02/11] less code elimination --- src/cli.zig | 6 ++-- src/js_parser.zig | 2 +- test/cli/run/run-eval.test.ts | 64 +++++++++++++++++++---------------- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index 185fe9e16659b..792ab0d8eff30 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -577,11 +577,11 @@ pub const Arguments = struct { ctx.preloads = preloads; } - if (args.option("--eval")) |script| { - ctx.runtime_options.eval.script = script; - } else if (args.option("--print")) |script| { + if (args.option("--print")) |script| { ctx.runtime_options.eval.script = script; ctx.runtime_options.eval.eval_and_print = true; + } else if (args.option("--eval")) |script| { + ctx.runtime_options.eval.script = script; } ctx.runtime_options.if_present = args.flag("--if-present"); ctx.runtime_options.smol = args.flag("--smol"); diff --git a/src/js_parser.zig b/src/js_parser.zig index 3fb76a183e494..bbecf05df76ea 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -21274,7 +21274,7 @@ fn NewParser_( // if this fails it means that scope pushing/popping is not balanced assert(p.current_scope == initial_scope); - if (!p.options.features.minify_syntax) { + if (!p.options.features.minify_syntax or !p.options.features.dead_code_elimination) { return; } diff --git a/test/cli/run/run-eval.test.ts b/test/cli/run/run-eval.test.ts index 7aab96f7cca94..9ed585b930d4e 100644 --- a/test/cli/run/run-eval.test.ts +++ b/test/cli/run/run-eval.test.ts @@ -4,42 +4,46 @@ import { bunEnv, bunExe } from "harness"; import { tmpdir } from "os"; import { join, sep } from "path"; -describe("bun -e", () => { - test("it works", async () => { - let { stdout } = Bun.spawnSync({ - cmd: [bunExe(), "-e", 'console.log("hello world")'], - env: bunEnv, +for (const flag of ["-e", "--print"]) { + describe(`bun ${flag}`, () => { + test("it works", async () => { + const input = flag === "--print" ? '"hello world"' : 'console.log("hello world")'; + let { stdout } = Bun.spawnSync({ + cmd: [bunExe(), flag, input], + env: bunEnv, + }); + expect(stdout.toString("utf8")).toEqual("hello world\n"); }); - expect(stdout.toString("utf8")).toEqual("hello world\n"); - }); - test("import, tsx, require in esm, import.meta", async () => { - const ref = await import("react"); - let { stdout } = Bun.spawnSync({ - cmd: [ - bunExe(), - "-e", - 'import {version} from "react"; console.log(JSON.stringify({version,file:import.meta.path,require:require("react").version})); console.log(world);', - ], - env: bunEnv, + test("import, tsx, require in esm, import.meta", async () => { + const ref = await import("react"); + const input = + flag === "--print" + ? 'import {version} from "react"; console.log(JSON.stringify({version,file:import.meta.path,require:require("react").version})); world' + : 'import {version} from "react"; console.log(JSON.stringify({version,file:import.meta.path,require:require("react").version})); console.log(world);'; + + let { stdout } = Bun.spawnSync({ + cmd: [bunExe(), flag, input], + env: bunEnv, + }); + const json = { + version: ref.version, + file: join(process.cwd(), "[eval]"), + require: ref.version, + }; + expect(stdout.toString("utf8")).toEqual(JSON.stringify(json) + "\nworld\n"); }); - const json = { - version: ref.version, - file: join(process.cwd(), "[eval]"), - require: ref.version, - }; - expect(stdout.toString("utf8")).toEqual(JSON.stringify(json) + "\nworld\n"); - }); - test("error has source map info 1", async () => { - let { stdout, stderr } = Bun.spawnSync({ - cmd: [bunExe(), "-e", '(throw new Error("hi" as 2))'], - env: bunEnv, + test("error has source map info 1", async () => { + let { stderr } = Bun.spawnSync({ + cmd: [bunExe(), flag, '(throw new Error("hi" as 2))'], + env: bunEnv, + }); + expect(stderr.toString("utf8")).toInclude('"hi" as 2'); + expect(stderr.toString("utf8")).toInclude("Unexpected throw"); }); - expect(stderr.toString("utf8")).toInclude('"hi" as 2'); - expect(stderr.toString("utf8")).toInclude("Unexpected throw"); }); -}); +} function group(run: (code: string) => ReturnType) { test("it works", async () => { From 8e8846be449bbaa712eb8997cbea692495330085 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 11 Mar 2024 02:05:59 -0700 Subject: [PATCH 03/11] handle cjs module eval results --- src/bun.js/bindings/CommonJSModuleRecord.cpp | 27 +++ src/bun.js/bindings/ZigGlobalObject.cpp | 10 +- src/bun.js/javascript.zig | 26 ++- src/bun.js/module_loader.zig | 23 ++- src/bundler.zig | 2 + src/bundler/bundle_v2.zig | 3 - src/js_ast.zig | 8 +- src/js_parser.zig | 164 +++++++++---------- src/runtime.zig | 4 + 9 files changed, 162 insertions(+), 105 deletions(-) diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index 57f4201dfe2e2..795a0b0465e79 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -92,6 +92,9 @@ static bool canPerformFastEnumeration(Structure* s) return true; } +extern "C" bool Bun__VM__specifierIsEvalEntryPoint(void*, EncodedJSValue); +extern "C" void Bun__VM__setEntryPointEvalResult(void*, EncodedJSValue); + static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObject, JSCommonJSModule* moduleObject, JSString* dirname, JSValue filename, WTF::NakedPtr& exception) { JSSourceCode* code = moduleObject->sourceCode.get(); @@ -119,6 +122,30 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj moduleObject->hasEvaluated = true; + if (Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(filename))) { + + // Using same approach as node, `arguments` in the entry point isn't defined + // https://github.com/nodejs/node/blob/592c6907bfe1922f36240e9df076be1864c3d1bd/lib/internal/process/execution.js#L92 + globalObject->putDirect(vm, Identifier::fromLatin1(vm, "exports"_s), moduleObject->exportsObject(), 0); + globalObject->putDirect(vm, Identifier::fromLatin1(vm, "require"_s), requireFunction, 0); + globalObject->putDirect(vm, Identifier::fromLatin1(vm, "module"_s), moduleObject, 0); + globalObject->putDirect(vm, Identifier::fromLatin1(vm, "__filename"_s), filename, 0); + globalObject->putDirect(vm, Identifier::fromLatin1(vm, "__dirname"_s), dirname, 0); + + JSValue result = JSC::evaluate(globalObject, code->sourceCode(), jsUndefined(), exception); + + if (UNLIKELY(exception.get() || result.isEmpty())) { + moduleObject->sourceCode.clear(); + return false; + } + + Bun__VM__setEntryPointEvalResult(globalObject->bunVM(), JSValue::encode(result)); + + moduleObject->sourceCode.clear(); + + return true; + } + // This will return 0 if there was a syntax error or an allocation failure JSValue fnValue = JSC::evaluate(globalObject, code->sourceCode(), jsUndefined(), exception); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 1a60a1853eaf0..d0b5177f6d0cb 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -4554,24 +4554,24 @@ JSC::JSValue GlobalObject::moduleLoaderEvaluate(JSGlobalObject* globalObject, return result; } -extern "C" void Bun__VM__setEntryPointResult(void*, EncodedJSValue); +extern "C" void Bun__VM__setEvalResultIfEntryPoint(void*, EncodedJSValue, EncodedJSValue); JSC::JSValue GlobalObject::moduleLoaderEvaluateForEval(JSGlobalObject* globalObject, JSModuleLoader* moduleLoader, JSValue key, JSValue moduleRecordValue, JSValue scriptFetcher, JSValue sentValue, JSValue resumeMode) { - s_globalObjectMethodTable.moduleLoaderEvaluate = &moduleLoaderEvaluate; - if (UNLIKELY(scriptFetcher && scriptFetcher.isObject())) { - Bun__VM__setEntryPointResult(jsCast(globalObject)->bunVM(), JSValue::encode(scriptFetcher)); + Bun__VM__setEvalResultIfEntryPoint(jsCast(globalObject)->bunVM(), JSValue::encode(key), JSValue::encode(scriptFetcher)); return scriptFetcher; } JSC::JSValue result = moduleLoader->evaluateNonVirtual(globalObject, key, moduleRecordValue, scriptFetcher, sentValue, resumeMode); - Bun__VM__setEntryPointResult(jsCast(globalObject)->bunVM(), JSValue::encode(result)); + // need to check each module evaluated to cover cases like these (23 should be the result): + // `import "./foo"; 23; import "./bar"` + Bun__VM__setEvalResultIfEntryPoint(jsCast(globalObject)->bunVM(), JSValue::encode(key), JSValue::encode(result)); return result; } diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index ec1d1858bb0b6..c512f2b77e3f5 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -895,13 +895,33 @@ pub const VirtualMachine = struct { return .running; } - pub fn setEntryPointResult(this: *VirtualMachine, value: JSValue) callconv(.C) void { - this.entry_point_result.set(this.global, value); + pub fn specifierIsEvalEntryPoint(this: *VirtualMachine, specifier: JSValue) callconv(.C) bool { + if (this.module_loader.eval_source) |eval_source| { + var specifier_str = specifier.toBunString(this.global); + defer specifier_str.deref(); + return specifier_str.eqlUTF8(eval_source.path.text); + } + + return false; + } + + pub fn setEvalResultIfEntryPoint(this: *VirtualMachine, specifier: JSValue, result: JSValue) callconv(.C) void { + if (!this.entry_point_result.has() and this.specifierIsEvalEntryPoint(specifier)) { + this.entry_point_result.set(this.global, result); + } + } + + pub fn setEntryPointEvalResult(this: *VirtualMachine, value: JSValue) callconv(.C) void { + if (!this.entry_point_result.has()) { + this.entry_point_result.set(this.global, value); + } } comptime { @export(scriptExecutionStatus, .{ .name = "Bun__VM__scriptExecutionStatus" }); - @export(setEntryPointResult, .{ .name = "Bun__VM__setEntryPointResult" }); + @export(setEvalResultIfEntryPoint, .{ .name = "Bun__VM__setEvalResultIfEntryPoint" }); + @export(setEntryPointEvalResult, .{ .name = "Bun__VM__setEntryPointEvalResult" }); + @export(specifierIsEvalEntryPoint, .{ .name = "Bun__VM__specifierIsEvalEntryPoint" }); } pub fn onExit(this: *VirtualMachine) void { diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index a176e69ca79dd..308a46cb19fdc 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -432,6 +432,11 @@ pub const RuntimeTranspilerStore = struct { var should_close_input_file_fd = fd == null; var input_file_fd: StoredFileDescriptorType = .zero; + + const is_main = vm.main.len == path.text.len and + vm.main_hash == hash and + strings.eqlLong(vm.main, path.text, false); + var parse_options = Bundler.ParseOptions{ .allocator = allocator, .path = path, @@ -446,12 +451,13 @@ pub const RuntimeTranspilerStore = struct { .virtual_source = null, .dont_bundle_twice = true, .allow_commonjs = true, - .inject_jest_globals = bundler.options.rewrite_jest_for_tests and - vm.main.len == path.text.len and - vm.main_hash == hash and - strings.eqlLong(vm.main, path.text, false), - .set_breakpoint_on_first_line = vm.debugger != null and vm.debugger.?.set_breakpoint_on_first_line and strings.eqlLong(vm.main, path.text, true) and setBreakPointOnFirstLine(), + .inject_jest_globals = bundler.options.rewrite_jest_for_tests and is_main, + .set_breakpoint_on_first_line = vm.debugger != null and + vm.debugger.?.set_breakpoint_on_first_line and + is_main and + setBreakPointOnFirstLine(), .runtime_transpiler_cache = if (!JSC.RuntimeTranspilerCache.is_disabled) &cache else null, + .remove_cjs_module_wrapper = is_main and vm.module_loader.eval_source != null, }; defer { @@ -1554,9 +1560,12 @@ pub const ModuleLoader = struct { .dont_bundle_twice = true, .allow_commonjs = true, .inject_jest_globals = jsc_vm.bundler.options.rewrite_jest_for_tests and is_main, - .set_breakpoint_on_first_line = is_main and jsc_vm.debugger != null and jsc_vm.debugger.?.set_breakpoint_on_first_line and setBreakPointOnFirstLine(), - + .set_breakpoint_on_first_line = is_main and + jsc_vm.debugger != null and + jsc_vm.debugger.?.set_breakpoint_on_first_line and + setBreakPointOnFirstLine(), .runtime_transpiler_cache = if (!disable_transpilying and !JSC.RuntimeTranspilerCache.is_disabled) &cache else null, + .remove_cjs_module_wrapper = is_main and jsc_vm.module_loader.eval_source != null, }; defer { if (should_close_input_file_fd and input_file_fd != bun.invalid_fd) { diff --git a/src/bundler.zig b/src/bundler.zig index 4049547dcb397..40a3d39569cbb 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1278,6 +1278,7 @@ pub const Bundler = struct { inject_jest_globals: bool = false, set_breakpoint_on_first_line: bool = false, emit_decorator_metadata: bool = false, + remove_cjs_module_wrapper: bool = false, dont_bundle_twice: bool = false, allow_commonjs: bool = false, @@ -1429,6 +1430,7 @@ pub const Bundler = struct { opts.features.minify_syntax = bundler.options.minify_syntax; opts.features.minify_identifiers = bundler.options.minify_identifiers; opts.features.dead_code_elimination = bundler.options.dead_code_elimination; + opts.features.remove_cjs_module_wrapper = this_parse.remove_cjs_module_wrapper; if (bundler.macro_context == null) { bundler.macro_context = js_ast.Macro.MacroContext.init(bundler); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 0ed79fc2402c4..eb19bccebe835 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -4514,7 +4514,6 @@ const LinkerContext = struct { stmt.loc, ), expr, - this.allocator, ); try this.graph.generateSymbolImportAndUse(source_index, 0, module_ref, 1, Index.init(source_index)); }, @@ -7522,7 +7521,6 @@ const LinkerContext = struct { }, Logger.Loc.Empty, ), - temp_allocator, ), ) catch unreachable; }, @@ -8726,7 +8724,6 @@ const LinkerContext = struct { value = value.joinWithComma( binding.assign( other, - temp_allocator, ), temp_allocator, ); diff --git a/src/js_ast.zig b/src/js_ast.zig index aa6a8ffc10852..046f0d0e8fa0f 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -397,7 +397,7 @@ pub const Binding = struct { if (b.has_spread and i == exprs.len - 1) { break :convert Expr.init(E.Spread, E.Spread{ .value = expr }, expr.loc); } else if (item.default_value) |default| { - break :convert Expr.assign(expr, default, wrapper.allocator); + break :convert Expr.assign(expr, default); } else { break :convert expr; } @@ -2676,11 +2676,11 @@ pub const Stmt = struct { pub const Batcher = bun.Batcher(Stmt); - pub fn assign(a: Expr, b: Expr, allocator: std.mem.Allocator) Stmt { + pub fn assign(a: Expr, b: Expr) Stmt { return Stmt.alloc( S.SExpr, S.SExpr{ - .value = Expr.assign(a, b, allocator), + .value = Expr.assign(a, b), }, a.loc, ); @@ -4680,7 +4680,7 @@ pub const Expr = struct { return false; } - pub fn assign(a: Expr, b: Expr, _: std.mem.Allocator) Expr { + pub fn assign(a: Expr, b: Expr) Expr { return init(E.Binary, E.Binary{ .op = .bin_assign, .left = a, diff --git a/src/js_parser.zig b/src/js_parser.zig index bbecf05df76ea..448988a5b58ca 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -17813,7 +17813,7 @@ fn NewParser_( p.to_expr_wrapper_hoisted, ); if (decl.value) |decl_value| { - value = value.joinWithComma(Expr.assign(binding, decl_value, p.allocator), p.allocator); + value = value.joinWithComma(Expr.assign(binding, decl_value), p.allocator); } else if (mode == .for_in_or_for_of) { value = value.joinWithComma(binding, p.allocator); } @@ -18591,7 +18591,6 @@ fn NewParser_( stmt.loc, ), p.visitExpr(data.value), - p.allocator, ), ) catch unreachable; p.recordUsage(p.module_ref); @@ -18667,7 +18666,7 @@ fn NewParser_( p.recordUsage((p.enclosing_namespace_arg_ref orelse unreachable)); // TODO: is it necessary to lowerAssign? why does esbuild do it _most_ of the time? stmts.append(p.s(S.SExpr{ - .value = Expr.assign(Binding.toExpr(&d.binding, p.to_expr_wrapper_namespace), val, p.allocator), + .value = Expr.assign(Binding.toExpr(&d.binding, p.to_expr_wrapper_namespace), val), }, stmt.loc)) catch unreachable; } } @@ -19050,7 +19049,6 @@ fn NewParser_( Stmt.assign( Expr.initIdentifier(decl.binding.data.b_identifier.ref, decl.binding.loc), val, - p.allocator, ), ) catch unreachable; decl.value = null; @@ -19209,11 +19207,14 @@ fn NewParser_( const enclosing_namespace_arg_ref = p.enclosing_namespace_arg_ref orelse unreachable; stmts.ensureUnusedCapacity(3) catch unreachable; stmts.appendAssumeCapacity(stmt.*); - stmts.appendAssumeCapacity(Stmt.assign(p.newExpr(E.Dot{ - .target = p.newExpr(E.Identifier{ .ref = enclosing_namespace_arg_ref }, stmt.loc), - .name = p.loadNameFromRef(data.func.name.?.ref.?), - .name_loc = data.func.name.?.loc, - }, stmt.loc), p.newExpr(E.Identifier{ .ref = data.func.name.?.ref.? }, data.func.name.?.loc), p.allocator)); + stmts.appendAssumeCapacity(Stmt.assign( + p.newExpr(E.Dot{ + .target = p.newExpr(E.Identifier{ .ref = enclosing_namespace_arg_ref }, stmt.loc), + .name = p.loadNameFromRef(data.func.name.?.ref.?), + .name_loc = data.func.name.?.loc, + }, stmt.loc), + p.newExpr(E.Identifier{ .ref = data.func.name.?.ref.? }, data.func.name.?.loc), + )); } else if (!mark_as_dead) { if (p.symbols.items[data.func.name.?.ref.?.innerIndex()].remove_overwritten_function_declaration) { return; @@ -19292,7 +19293,6 @@ fn NewParser_( E.Identifier{ .ref = data.class.class_name.?.ref.? }, data.class.class_name.?.loc, ), - p.allocator, ), ) catch unreachable; } @@ -19365,16 +19365,19 @@ fn NewParser_( enum_value.value = p.newExpr(E.Undefined{}, enum_value.loc); } // "Enum['Name'] = value" - assign_target = Expr.assign(p.newExpr(E.Index{ - .target = p.newExpr( - E.Identifier{ .ref = data.arg }, - enum_value.loc, - ), - .index = p.newExpr( - enum_value.name, - enum_value.loc, - ), - }, enum_value.loc), enum_value.value orelse unreachable, allocator); + assign_target = Expr.assign( + p.newExpr(E.Index{ + .target = p.newExpr( + E.Identifier{ .ref = data.arg }, + enum_value.loc, + ), + .index = p.newExpr( + enum_value.name, + enum_value.loc, + ), + }, enum_value.loc), + enum_value.value orelse unreachable, + ); p.recordUsage(data.arg); @@ -19393,7 +19396,6 @@ fn NewParser_( .index = assign_target, }, enum_value.loc), p.newExpr(enum_value.name, enum_value.loc), - allocator, ), ) catch unreachable; } @@ -19864,12 +19866,10 @@ fn NewParser_( name_loc, ), p.newExpr(E.Object{}, name_loc), - allocator, ), }, name_loc, ), - allocator, ); p.recordUsage(namespace); p.recordUsage(namespace); @@ -19885,7 +19885,6 @@ fn NewParser_( E.Object{}, name_loc, ), - allocator, ), }, name_loc); p.recordUsage(name_ref); @@ -20115,9 +20114,9 @@ fn NewParser_( // remove fields with decorators from class body. Move static members outside of class. if (prop.flags.contains(.is_static)) { - static_members.append(Stmt.assign(target, initializer, p.allocator)) catch unreachable; + static_members.append(Stmt.assign(target, initializer)) catch unreachable; } else { - instance_members.append(Stmt.assign(target, initializer, p.allocator)) catch unreachable; + instance_members.append(Stmt.assign(target, initializer)) catch unreachable; } continue; } @@ -20218,7 +20217,6 @@ fn NewParser_( stmts.appendAssumeCapacity(Stmt.assign( p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), p.callRuntime(stmt.loc, "__legacyDecorateClassTS", args), - p.allocator, )); p.recordUsage(class.class_name.?.ref.?); @@ -20952,7 +20950,6 @@ fn NewParser_( .name_loc = arg.binding.loc, }, arg.binding.loc), ident, - p.allocator, )) catch unreachable; // O(N) class_body.items.len += 1; @@ -21643,7 +21640,7 @@ fn NewParser_( // There may be a "=" after the type (but not after an "as" cast) if (is_typescript_enabled and p.lexer.token == .t_equals and !p.forbid_suffix_after_as_loc.eql(p.lexer.loc())) { try p.lexer.next(); - item = Expr.assign(item, try p.parseExpr(.comma), p.allocator); + item = Expr.assign(item, try p.parseExpr(.comma)); } items_list.append(item) catch unreachable; @@ -22310,65 +22307,69 @@ fn NewParser_( parts[parts.len - 1].stmts = new_stmts_list; }, - // This transforms the user's code into. - // - // (function (exports, require, module, __filename, __dirname) { - // ... - // }) - // - // which is then called in `evaluateCommonJSModuleOnce` .bun_js => { - var args = allocator.alloc(Arg, 5 + @as(usize, @intFromBool(p.has_import_meta))) catch bun.outOfMemory(); - args[0..5].* = .{ - Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }, - Arg{ .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty) }, - Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) }, - Arg{ .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty) }, - Arg{ .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty) }, - }; - if (p.has_import_meta) { - p.import_meta_ref = p.newSymbol(.other, "$Bun_import_meta") catch bun.outOfMemory(); - args[5] = Arg{ .binding = p.b(B.Identifier{ .ref = p.import_meta_ref }, logger.Loc.Empty) }; - } - - var total_stmts_count: usize = 0; - for (parts) |part| { - total_stmts_count += part.stmts.len; - } + // if remove_cjs_module_wrapper is true, `evaluateCommonJSModuleOnce` will put exports, require, module, __filename, and + // __dirname on the globalObject. + if (!p.options.features.remove_cjs_module_wrapper) { + // This transforms the user's code into. + // + // (function (exports, require, module, __filename, __dirname) { + // ... + // }) + // + // which is then called in `evaluateCommonJSModuleOnce` + var args = allocator.alloc(Arg, 5 + @as(usize, @intFromBool(p.has_import_meta))) catch bun.outOfMemory(); + args[0..5].* = .{ + Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty) }, + }; + if (p.has_import_meta) { + p.import_meta_ref = p.newSymbol(.other, "$Bun_import_meta") catch bun.outOfMemory(); + args[5] = Arg{ .binding = p.b(B.Identifier{ .ref = p.import_meta_ref }, logger.Loc.Empty) }; + } - const stmts_to_copy = allocator.alloc(Stmt, total_stmts_count) catch bun.outOfMemory(); - { - var remaining_stmts = stmts_to_copy; + var total_stmts_count: usize = 0; for (parts) |part| { - for (part.stmts, remaining_stmts[0..part.stmts.len]) |src, *dest| { - dest.* = src; + total_stmts_count += part.stmts.len; + } + + const stmts_to_copy = allocator.alloc(Stmt, total_stmts_count) catch bun.outOfMemory(); + { + var remaining_stmts = stmts_to_copy; + for (parts) |part| { + for (part.stmts, remaining_stmts[0..part.stmts.len]) |src, *dest| { + dest.* = src; + } + remaining_stmts = remaining_stmts[part.stmts.len..]; } - remaining_stmts = remaining_stmts[part.stmts.len..]; } - } - const wrapper = p.newExpr( - E.Function{ - .func = G.Fn{ - .name = null, - .open_parens_loc = logger.Loc.Empty, - .args = args, - .body = .{ .loc = logger.Loc.Empty, .stmts = stmts_to_copy }, - .flags = Flags.Function.init(.{ .is_export = false }), + const wrapper = p.newExpr( + E.Function{ + .func = G.Fn{ + .name = null, + .open_parens_loc = logger.Loc.Empty, + .args = args, + .body = .{ .loc = logger.Loc.Empty, .stmts = stmts_to_copy }, + .flags = Flags.Function.init(.{ .is_export = false }), + }, }, - }, - logger.Loc.Empty, - ); + logger.Loc.Empty, + ); - var top_level_stmts = p.allocator.alloc(Stmt, 1) catch bun.outOfMemory(); - parts[0].stmts = top_level_stmts; - top_level_stmts[0] = p.s( - S.SExpr{ - .value = wrapper, - }, - logger.Loc.Empty, - ); - parts.len = 1; + var top_level_stmts = p.allocator.alloc(Stmt, 1) catch bun.outOfMemory(); + parts[0].stmts = top_level_stmts; + top_level_stmts[0] = p.s( + S.SExpr{ + .value = wrapper, + }, + logger.Loc.Empty, + ); + parts.len = 1; + } }, .none => { @@ -22610,7 +22611,6 @@ fn NewParser_( .name = named_export.key_ptr.*, .name_loc = logger.Loc.Empty, }, logger.Loc.Empty), - allocator, ); export_properties[named_export_i] = G.Property{ @@ -22693,7 +22693,6 @@ fn NewParser_( logger.Loc.Empty, ), func, - allocator, ), }, logger.Loc.Empty, @@ -22751,7 +22750,6 @@ fn NewParser_( }, logger.Loc.Empty, ), - allocator, ), }, logger.Loc.Empty, diff --git a/src/runtime.zig b/src/runtime.zig index a1b52583a140c..3634389339949 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -267,6 +267,10 @@ pub const Runtime = struct { emit_decorator_metadata: bool = false, + /// If true and if the source is transpiled as cjs, don't wrap the module. + /// This is used for `--print` entry points so we can get the result. + remove_cjs_module_wrapper: bool = false, + runtime_transpiler_cache: ?*bun.JSC.RuntimeTranspilerCache = null, // TODO: make this a bitset of all unsupported features From 1e745a05337d8666624e66ac0610e26f912cd7e0 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 11 Mar 2024 02:33:05 -0700 Subject: [PATCH 04/11] make node -p work --- src/cli.zig | 8 +++++- .../registry/bun-install-registry.test.ts | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/cli.zig b/src/cli.zig index 792ab0d8eff30..27feff2da9982 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -541,7 +541,13 @@ pub const Arguments = struct { } if (args.option("--port")) |port_str| { - opts.port = std.fmt.parseInt(u16, port_str, 10) catch return error.InvalidPort; + if (comptime cmd == .RunAsNodeCommand) { + // TODO: prevent `node --port