aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-02-28 00:01:21 -0800
committerGravatar GitHub <noreply@github.com> 2023-02-28 00:01:21 -0800
commitec7929b251b91745f676ce85f173516186f36c6e (patch)
tree7b1e0378f20526a1236dab54c129a0105ffdfc83
parent590219966edc9e79b34ab2c073ae34d14baa141a (diff)
downloadbun-ec7929b251b91745f676ce85f173516186f36c6e.tar.gz
bun-ec7929b251b91745f676ce85f173516186f36c6e.tar.zst
bun-ec7929b251b91745f676ce85f173516186f36c6e.zip
Implement `preload` support (like `node -r ` except in a config file) (#2231)
* Update Makefile * Introduce `preload` * Add a test * Support entry points --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--Makefile2
-rw-r--r--src/bun.js/javascript.zig77
-rw-r--r--src/bun.js/module_loader.zig1
-rw-r--r--src/bun_js.zig48
-rw-r--r--src/bundler/entry_points.zig11
-rw-r--r--src/bunfig.zig32
-rw-r--r--src/cli.zig62
-rw-r--r--src/cli/run_command.zig29
-rw-r--r--src/cli/test_command.zig1
-rw-r--r--test/bun.js/preload-test.test.js227
10 files changed, 448 insertions, 42 deletions
diff --git a/Makefile b/Makefile
index e271239a7..b2f0b3b87 100644
--- a/Makefile
+++ b/Makefile
@@ -832,7 +832,7 @@ fetch: $(IO_FILES)
.PHONY: sha
sha:
$(ZIG) build -Doptimize=ReleaseFast sha-bench-obj
- $(CXX) $(PACKAGE_DIR)/sha.o -g $(OPTIMIZATION_LEVEL) -o ./misctools/sha $(DEFAULT_LINKER_FLAGS) -lc $(MINIMUM_ARCHIVE_FILES)
+ $(CXX) $(PACKAGE_DIR)/sha.o -I$(BUN_DEPS_DIR) -g $(OPTIMIZATION_LEVEL) -o ./misctools/sha $(DEFAULT_LINKER_FLAGS) -lc $(MINIMUM_ARCHIVE_FILES)
rm -rf $(PACKAGE_DIR)/sha.o
.PHONY: fetch-debug
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;
diff --git a/test/bun.js/preload-test.test.js b/test/bun.js/preload-test.test.js
new file mode 100644
index 000000000..76603b3a0
--- /dev/null
+++ b/test/bun.js/preload-test.test.js
@@ -0,0 +1,227 @@
+import { spawnSync } from "bun";
+import { describe, expect, test } from "bun:test";
+import { mkdirSync, realpathSync } from "fs";
+import { tmpdir } from "os";
+import { join } from "path";
+import { bunEnv } from "./bunEnv";
+import { bunExe } from "./bunExe";
+const preloadModule = `
+import {plugin} from 'bun';
+
+plugin({
+ setup(build) {
+ build.onResolve({ filter: /.*\.txt$/, }, async (args) => {
+ return {
+ path: args.path,
+ namespace: 'boop'
+ }
+ });
+ build.onResolve({ namespace: "boop", filter: /.*/ }, async (args) => {
+ return {
+ path: args.path,
+ namespace: 'boop'
+ }
+ });
+ build.onLoad({ namespace: "boop", filter: /.*/ }, async (args) => {
+ return {
+ contents: '"hello world"',
+ loader: 'json'
+ }
+ });
+ }
+});
+ `;
+
+const mainModule = `
+ import hey from './hey.txt';
+
+ if (hey !== 'hello world') {
+ throw new Error('preload test failed');
+ }
+
+ console.log('Test passed');
+ process.exit(0);
+`;
+
+const bunfig = `preload = ["./preload.js"]`;
+
+describe("preload", () => {
+ test("works", async () => {
+ const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test");
+ mkdirSync(preloadDir, { recursive: true });
+ const preloadPath = join(preloadDir, "preload.js");
+ const mainPath = join(preloadDir, "main.js");
+ const bunfigPath = join(preloadDir, "bunfig.toml");
+ await Bun.write(preloadPath, preloadModule);
+ await Bun.write(mainPath, mainModule);
+ await Bun.write(bunfigPath, bunfig);
+
+ const cmds = [
+ [bunExe(), "run", mainPath],
+ [bunExe(), mainPath],
+ ];
+
+ for (let cmd of cmds) {
+ const { stderr, exitCode, stdout } = spawnSync({
+ cmd,
+ cwd: preloadDir,
+ stderr: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(exitCode).toBe(0);
+ expect(stderr.toString()).toBe("");
+ expect(stdout.toString()).toContain("Test passed");
+ }
+ });
+
+ test("works from CLI", async () => {
+ const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test4");
+ mkdirSync(preloadDir, { recursive: true });
+ const preloadPath = join(preloadDir, "preload.js");
+ const mainPath = join(preloadDir, "main.js");
+ await Bun.write(preloadPath, preloadModule);
+ await Bun.write(mainPath, mainModule);
+
+ const cmds = [
+ [bunExe(), "-r=" + preloadPath, "run", mainPath],
+ [bunExe(), "-r=" + preloadPath, mainPath],
+ ];
+
+ for (let cmd of cmds) {
+ const { stderr, exitCode, stdout } = spawnSync({
+ cmd,
+ cwd: preloadDir,
+ stderr: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(exitCode).toBe(0);
+ expect(stderr.toString()).toBe("");
+ expect(stdout.toString()).toContain("Test passed");
+ }
+ });
+
+ describe("as entry point", () => {
+ const preloadModule = `
+import {plugin} from 'bun';
+
+plugin({
+ setup(build) {
+ build.onResolve({ filter: /.*\.txt$/, }, async (args) => {
+ return {
+ path: args.path,
+ namespace: 'boop'
+ }
+ });
+ build.onResolve({ namespace: "boop", filter: /.*/ }, async (args) => {
+ return {
+ path: args.path,
+ namespace: 'boop'
+ }
+ });
+ build.onLoad({ namespace: "boop", filter: /.*/ }, async (args) => {
+ return {
+ contents: 'console.log("Test passed")',
+ loader: 'js'
+ }
+ });
+ }
+});
+ `;
+
+ test("works from CLI", async () => {
+ const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test6");
+ mkdirSync(preloadDir, { recursive: true });
+ const preloadPath = join(preloadDir, "preload.js");
+ const mainPath = join(preloadDir, "boop.txt");
+ await Bun.write(preloadPath, preloadModule);
+ await Bun.write(mainPath, "beep");
+
+ const cmds = [
+ [bunExe(), "-r=" + preloadPath, "run", mainPath],
+ [bunExe(), "-r=" + preloadPath, mainPath],
+ ];
+
+ for (let cmd of cmds) {
+ const { stderr, exitCode, stdout } = spawnSync({
+ cmd,
+ cwd: preloadDir,
+ stderr: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(stderr.toString()).toBe("");
+ expect(stdout.toString()).toContain("Test passed");
+ expect(exitCode).toBe(0);
+ }
+ });
+ });
+
+ test("throws an error when preloaded module fails to execute", async () => {
+ const preloadModule = "throw new Error('preload test failed');";
+
+ const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test3");
+ mkdirSync(preloadDir, { recursive: true });
+ const preloadPath = join(preloadDir, "preload.js");
+ const mainPath = join(preloadDir, "main.js");
+ const bunfigPath = join(preloadDir, "bunfig.toml");
+ await Bun.write(preloadPath, preloadModule);
+ await Bun.write(mainPath, mainModule);
+ await Bun.write(bunfigPath, bunfig);
+
+ const cmds = [
+ [bunExe(), "run", mainPath],
+ [bunExe(), mainPath],
+ ];
+
+ for (let cmd of cmds) {
+ const { stderr, exitCode, stdout } = spawnSync({
+ cmd,
+ cwd: preloadDir,
+ stderr: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(stderr.toString()).toContain("preload test failed");
+ expect(stdout.toString()).toBe("");
+ expect(exitCode).toBe(1);
+ }
+ });
+
+ test("throws an error when preloaded module not found", async () => {
+ const bunfig = `preload = ["./bad-file.js"]`;
+
+ const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test2");
+ mkdirSync(preloadDir, { recursive: true });
+ const preloadPath = join(preloadDir, "preload.js");
+ const mainPath = join(preloadDir, "main.js");
+ const bunfigPath = join(preloadDir, "bunfig.toml");
+ await Bun.write(preloadPath, preloadModule);
+ await Bun.write(mainPath, mainModule);
+ await Bun.write(bunfigPath, bunfig);
+
+ const cmds = [
+ [bunExe(), "run", mainPath],
+ [bunExe(), mainPath],
+ ];
+
+ for (let cmd of cmds) {
+ const { stderr, exitCode, stdout } = spawnSync({
+ cmd,
+ cwd: preloadDir,
+ stderr: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(stderr.toString()).toContain("preload not found ");
+ expect(stdout.toString()).toBe("");
+ expect(exitCode).toBe(1);
+ }
+ });
+});