diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/javascript.zig | 77 | ||||
-rw-r--r-- | src/bun.js/module_loader.zig | 1 | ||||
-rw-r--r-- | src/bun_js.zig | 48 | ||||
-rw-r--r-- | src/bundler/entry_points.zig | 11 | ||||
-rw-r--r-- | src/bunfig.zig | 32 | ||||
-rw-r--r-- | src/cli.zig | 62 | ||||
-rw-r--r-- | src/cli/run_command.zig | 29 | ||||
-rw-r--r-- | src/cli/test_command.zig | 1 |
8 files changed, 220 insertions, 41 deletions
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index efa42deb5..445f30233 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -361,6 +361,7 @@ pub const VirtualMachine = struct { timer: Bun.Timer = Bun.Timer{}, uws_event_loop: ?*uws.Loop = null, pending_unref_counter: i32 = 0, + preload: []const string = &[_][]const u8{}, /// hide bun:wrap from stack traces /// bun:wrap is very noisy @@ -990,8 +991,7 @@ pub const VirtualMachine = struct { )) { .success => |r| r, .failure => |e| e, - .pending => unreachable, - .not_found => if (!retry_on_not_found) + .pending, .not_found => if (!retry_on_not_found) error.ModuleNotFound else { retry_on_not_found = false; @@ -1441,7 +1441,12 @@ pub const VirtualMachine = struct { pub fn reloadEntryPoint(this: *VirtualMachine, entry_path: []const u8) !*JSInternalPromise { this.main = entry_path; - try this.entry_point.generate(this.bun_watcher != null, Fs.PathName.init(entry_path), main_file_name); + try this.entry_point.generate( + this.allocator, + this.bun_watcher != null, + Fs.PathName.init(entry_path), + main_file_name, + ); this.eventLoop().ensureWaker(); var promise: *JSInternalPromise = undefined; @@ -1460,6 +1465,72 @@ pub const VirtualMachine = struct { return promise; } + for (this.preload) |preload| { + var result = switch (this.bundler.resolver.resolveAndAutoInstall( + this.bundler.fs.top_level_dir, + normalizeSource(preload), + .stmt, + .read_only, + )) { + .success => |r| r, + .failure => |e| { + this.log.addErrorFmt( + null, + logger.Loc.Empty, + this.allocator, + "{s} resolving preload {any}", + .{ + @errorName(e), + js_printer.formatJSONString(preload), + }, + ) catch unreachable; + return e; + }, + .pending, .not_found => { + this.log.addErrorFmt( + null, + logger.Loc.Empty, + this.allocator, + "preload not found {any}", + .{ + js_printer.formatJSONString(preload), + }, + ) catch unreachable; + return error.ModuleNotFound; + }, + }; + promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(result.path().?.text)); + this.pending_internal_promise = promise; + + // pending_internal_promise can change if hot module reloading is enabled + if (this.bun_watcher != null) { + this.eventLoop().performGC(); + switch (this.pending_internal_promise.status(this.global.vm())) { + JSC.JSPromise.Status.Pending => { + while (this.pending_internal_promise.status(this.global.vm()) == .Pending) { + this.eventLoop().tick(); + + if (this.pending_internal_promise.status(this.global.vm()) == .Pending) { + this.eventLoop().autoTick(); + } + } + }, + else => {}, + } + } else { + this.eventLoop().performGC(); + this.waitForPromise(JSC.AnyPromise{ + .Internal = promise, + }); + } + + if (promise.status(this.global.vm()) == .Rejected) + return promise; + } + + // only load preloads once + this.preload.len = 0; + promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.static(main_file_name)); this.pending_internal_promise = promise; } else { diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 0a7fe2fb6..adfa88cb1 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -1585,6 +1585,7 @@ pub const ModuleLoader = struct { opts.features.dynamic_require = true; opts.can_import_from_bundle = bundler.options.node_modules_bundle != null; opts.features.hot_module_reloading = false; + opts.features.top_level_await = true; opts.features.react_fast_refresh = false; opts.filepath_hash_for_hmr = 0; opts.warn_about_unbundled_modules = false; diff --git a/src/bun_js.zig b/src/bun_js.zig index 2b3e514e6..30ed20a26 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -44,7 +44,8 @@ pub const Run = struct { arena: Arena = undefined, any_unhandled: bool = false, - pub fn boot(ctx: Command.Context, file: std.fs.File, entry_path: string) !void { + pub fn boot(ctx_: Command.Context, file: std.fs.File, entry_path: string) !void { + var ctx = ctx_; JSC.markBinding(@src()); @import("bun.js/javascript_core_c_api.zig").JSCInitialize(); @@ -52,6 +53,10 @@ pub const Run = struct { js_ast.Stmt.Data.Store.create(default_allocator); var arena = try Arena.init(); + if (!ctx.debug.loaded_bunfig) { + try bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", &ctx, .RunCommand); + } + run = .{ .vm = try VirtualMachine.init(arena.allocator(), ctx.args, null, ctx.log, null), .file = file, @@ -59,9 +64,10 @@ pub const Run = struct { .ctx = ctx, .entry_path = entry_path, }; + var vm = run.vm; var b = &vm.bundler; - + vm.preload = ctx.preloads; vm.argv = ctx.passthrough; vm.arena = &run.arena; vm.allocator = arena.allocator(); @@ -144,23 +150,35 @@ pub const Run = struct { if (this.ctx.debug.hot_reload) { JSC.HotReloader.enableHotModuleReloading(vm); } - var promise = vm.loadEntryPoint(this.entry_path) catch return; - - if (promise.status(vm.global.vm()) == .Rejected) { - vm.runErrorHandler(promise.result(vm.global.vm()), null); - Global.exit(1); - } + if (vm.loadEntryPoint(this.entry_path)) |promise| { + if (promise.status(vm.global.vm()) == .Rejected) { + vm.runErrorHandler(promise.result(vm.global.vm()), null); + Global.exit(1); + } - _ = promise.result(vm.global.vm()); + _ = promise.result(vm.global.vm()); - if (vm.log.msgs.items.len > 0) { - if (Output.enable_ansi_colors) { - vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; + if (vm.log.msgs.items.len > 0) { + if (Output.enable_ansi_colors) { + vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; + } else { + vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {}; + } + Output.prettyErrorln("\n", .{}); + Output.flush(); + } + } else |err| { + if (vm.log.msgs.items.len > 0) { + if (Output.enable_ansi_colors) { + vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; + } else { + vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {}; + } + Output.flush(); } else { - vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {}; + Output.prettyErrorln("Error occurred loading entry point: {s}", .{@errorName(err)}); } - Output.prettyErrorln("\n", .{}); - Output.flush(); + Global.exit(1); } // don't run the GC if we don't actually need to diff --git a/src/bundler/entry_points.zig b/src/bundler/entry_points.zig index a501bcddb..a4068d2af 100644 --- a/src/bundler/entry_points.zig +++ b/src/bundler/entry_points.zig @@ -157,12 +157,11 @@ pub const ClientEntryPoint = struct { }; pub const ServerEntryPoint = struct { - code_buffer: [bun.MAX_PATH_BYTES * 2 + 500]u8 = undefined, - output_code_buffer: [bun.MAX_PATH_BYTES * 8 + 500]u8 = undefined, source: logger.Source = undefined, pub fn generate( entry: *ServerEntryPoint, + allocator: std.mem.Allocator, is_hot_reload_enabled: bool, original_path: Fs.PathName, name: string, @@ -182,8 +181,8 @@ pub const ServerEntryPoint = struct { const code = brk: { if (is_hot_reload_enabled) { - break :brk try std.fmt.bufPrint( - &entry.code_buffer, + break :brk try std.fmt.allocPrint( + allocator, \\//Auto-generated file \\var cjsSymbol = Symbol.for("CommonJS"); \\var hmrSymbol = Symbol.for("BunServerHMR"); @@ -225,8 +224,8 @@ pub const ServerEntryPoint = struct { }, ); } - break :brk try std.fmt.bufPrint( - &entry.code_buffer, + break :brk try std.fmt.allocPrint( + allocator, \\//Auto-generated file \\var cjsSymbol = Symbol.for("CommonJS"); \\import * as start from '{s}{s}'; diff --git a/src/bunfig.zig b/src/bunfig.zig index 4a341ef20..c6ecb7c10 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -169,6 +169,22 @@ pub const Bunfig = struct { } } } + + if (json.get("preload")) |expr| { + if (expr.asArray()) |array_| { + var array = array_; + var preloads = try std.ArrayList(string).initCapacity(allocator, array.array.items.len); + errdefer preloads.deinit(); + while (array.next()) |item| { + try this.expect(item, .e_string); + if (item.data.e_string.len() > 0) + preloads.appendAssumeCapacity(try item.data.e_string.string(allocator)); + } + this.ctx.preloads = preloads.items; + } else if (expr.data != .e_null) { + try this.addError(expr.loc, "Expected preload to be an array"); + } + } } if (comptime cmd == .DevCommand or cmd == .AutoCommand) { @@ -196,6 +212,22 @@ pub const Bunfig = struct { if (test_.get("root")) |root| { this.ctx.debug.test_directory = root.asString(this.allocator) orelse ""; } + + if (test_.get("preload")) |expr| { + if (expr.asArray()) |array_| { + var array = array_; + var preloads = try std.ArrayList(string).initCapacity(allocator, array.array.items.len); + errdefer preloads.deinit(); + while (array.next()) |item| { + try this.expect(item, .e_string); + if (item.data.e_string.len() > 0) + preloads.appendAssumeCapacity(try item.data.e_string.string(allocator)); + } + this.ctx.preloads = preloads.items; + } else if (expr.data != .e_null) { + try this.addError(expr.loc, "Expected preload to be an array"); + } + } } } diff --git a/src/cli.zig b/src/cli.zig index 33569c33e..e05bd113d 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -177,6 +177,7 @@ pub const Arguments = struct { clap.parseParam("--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable, clap.parseParam("--jsx-production Use jsx instead of jsxDEV (default) for the automatic runtime") catch unreachable, clap.parseParam("--jsx-runtime <STR> \"automatic\" (default) or \"classic\"") catch unreachable, + clap.parseParam("-r, --preload <STR>... Import a module before other modules are loaded") catch unreachable, clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable, clap.parseParam("--no-summary Don't print a summary (when generating .bun") catch unreachable, clap.parseParam("-v, --version Print version and exit") catch unreachable, @@ -222,14 +223,16 @@ pub const Arguments = struct { Global.exit(0); } - fn loadConfigPath(allocator: std.mem.Allocator, auto_loaded: bool, config_path: [:0]const u8, ctx: *Command.Context, comptime cmd: Command.Tag) !void { - var config_file = std.fs.openFileAbsoluteZ(config_path, .{ .mode = .read_only }) catch |err| { - if (auto_loaded) return; - Output.prettyErrorln("<r><red>error<r>: {s} opening config \"{s}\"", .{ - @errorName(err), - config_path, - }); - Global.exit(1); + pub fn loadConfigPath(allocator: std.mem.Allocator, auto_loaded: bool, config_path: [:0]const u8, ctx: *Command.Context, comptime cmd: Command.Tag) !void { + var config_file = std.fs.File{ + .handle = std.os.openZ(config_path, std.os.O.RDONLY, 0) catch |err| { + if (auto_loaded) return; + Output.prettyErrorln("<r><red>error<r>: {s} opening config \"{s}\"", .{ + @errorName(err), + config_path, + }); + Global.exit(1); + }, }; defer config_file.close(); var contents = config_file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { @@ -263,12 +266,15 @@ pub const Arguments = struct { return null; } - pub fn loadConfig(allocator: std.mem.Allocator, user_config_path_: ?string, ctx: *Command.Context, comptime cmd: Command.Tag) !void { var config_buf: [bun.MAX_PATH_BYTES]u8 = undefined; if (comptime cmd.readGlobalConfig()) { - if (getHomeConfigPath(&config_buf)) |path| { - try loadConfigPath(allocator, true, path, ctx, comptime cmd); + if (!ctx.has_loaded_global_config) { + ctx.has_loaded_global_config = true; + + if (getHomeConfigPath(&config_buf)) |path| { + try loadConfigPath(allocator, true, path, ctx, comptime cmd); + } } } @@ -290,7 +296,7 @@ pub const Arguments = struct { if (config_path_.len == 0) { return; } - + defer ctx.debug.loaded_bunfig = true; var config_path: [:0]u8 = undefined; if (config_path_[0] == '/') { @memcpy(&config_buf, config_path_.ptr, config_path_.len); @@ -421,6 +427,18 @@ pub const Arguments = struct { opts.no_summary = args.flag("--no-summary"); opts.disable_hmr = args.flag("--disable-hmr"); + if (cmd != .DevCommand) { + const preloads = args.options("--preload"); + if (ctx.preloads.len > 0 and preloads.len > 0) { + var all = std.ArrayList(string).initCapacity(ctx.allocator, ctx.preloads.len + preloads.len) catch unreachable; + all.appendSliceAssumeCapacity(ctx.preloads); + all.appendSliceAssumeCapacity(preloads); + ctx.preloads = all.items; + } else if (preloads.len > 0) { + ctx.preloads = preloads; + } + } + ctx.debug.silent = args.flag("--silent"); if (opts.port != null and opts.origin == null) { opts.origin = try std.fmt.allocPrint(allocator, "http://localhost:{d}/", .{opts.port.?}); @@ -831,6 +849,7 @@ pub const Command = struct { global_cache: options.GlobalCache = .auto, offline_mode_setting: ?Bunfig.OfflineMode = null, run_in_bun: bool = false, + loaded_bunfig: bool = false, // technical debt macros: ?MacroMap = null, @@ -851,6 +870,9 @@ pub const Command = struct { debug: DebugOptions = DebugOptions{}, + preloads: []const string = &[_]string{}, + has_loaded_global_config: bool = false, + const _ctx = Command.Context{ .args = std.mem.zeroes(Api.TransformOptions), .log = undefined, @@ -1237,6 +1259,15 @@ pub const Command = struct { break :brk options.Loader.js; } + if (extension.len > 0) { + if (!ctx.debug.loaded_bunfig) { + try bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", &ctx, .RunCommand); + } + + if (ctx.preloads.len > 0) + break :brk options.Loader.js; + } + break :brk null; }; @@ -1288,7 +1319,7 @@ pub const Command = struct { } } - fn maybeOpenWithBunJS(ctx: *const Command.Context) bool { + fn maybeOpenWithBunJS(ctx: *Command.Context) bool { if (ctx.args.entry_points.len == 0) return false; @@ -1337,6 +1368,11 @@ pub const Command = struct { // the case where this doesn't work is if the script name on disk doesn't end with a known JS-like file extension var absolute_script_path = bun.getFdPath(file.handle, &script_name_buf) catch return false; + + if (!ctx.debug.loaded_bunfig) { + bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", ctx, .RunCommand) catch {}; + } + BunJS.Run.boot( ctx.*, file, diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 8c5b98b0f..80840f7c9 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -836,7 +836,8 @@ pub const RunCommand = struct { return shell_out; } - pub fn exec(ctx: Command.Context, comptime bin_dirs_only: bool, comptime log_errors: bool) !bool { + pub fn exec(ctx_: Command.Context, comptime bin_dirs_only: bool, comptime log_errors: bool) !bool { + var ctx = ctx_; // Step 1. Figure out what we're trying to run var positionals = ctx.positionals; if (positionals.len > 0 and strings.eqlComptime(positionals[0], "run") or strings.eqlComptime(positionals[0], "r")) { @@ -855,12 +856,20 @@ pub const RunCommand = struct { if (log_errors or force_using_bun) { if (script_name_to_search.len > 0) { possibly_open_with_bun_js: { + const ext = std.fs.path.extension(script_name_to_search); + var has_loader = false; if (!force_using_bun) { - if (options.defaultLoaders.get(std.fs.path.extension(script_name_to_search))) |load| { + if (options.defaultLoaders.get(ext)) |load| { + has_loader = true; if (!load.canBeRunByBun()) break :possibly_open_with_bun_js; + // if there are preloads, allow weirdo file extensions } else { - break :possibly_open_with_bun_js; + // you can have package.json scripts with file extensions in the name + // eg "foo.zip" + // in those cases, we don't know + if (ext.len == 0 or strings.containsChar(script_name_to_search, ':')) + break :possibly_open_with_bun_js; } } @@ -888,8 +897,20 @@ pub const RunCommand = struct { const file = file_ catch break :possibly_open_with_bun_js; - // ignore the shebang if they explicitly passed `--bun` if (!force_using_bun) { + // Due to preload, we don't know if they intend to run + // this as a script or as a regular file + // once we know it's a file, check if they have any preloads + if (ext.len > 0 and !has_loader) { + if (!ctx.debug.loaded_bunfig) { + try bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", &ctx, .RunCommand); + } + + if (ctx.preloads.len == 0) + break :possibly_open_with_bun_js; + } + + // ignore the shebang if they explicitly passed `--bun` // "White space after #! is optional." var shebang_buf: [64]u8 = undefined; const shebang_size = file.pread(&shebang_buf, 0) catch |err| { diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index fbdf73ba3..a7b7b237c 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -388,6 +388,7 @@ pub const TestCommand = struct { js_ast.Stmt.Data.Store.create(default_allocator); var vm = try JSC.VirtualMachine.init(ctx.allocator, ctx.args, null, ctx.log, env_loader); vm.argv = ctx.passthrough; + vm.preload = ctx.preloads; try vm.bundler.configureDefines(); vm.bundler.options.rewrite_jest_for_tests = true; |