aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/api/server.zig4
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp18
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h3
-rw-r--r--src/bun.js/bindings/bindings.cpp2
-rw-r--r--src/bun.js/bindings/bindings.zig8
-rw-r--r--src/bun.js/bindings/headers-cpp.h2
-rw-r--r--src/bun.js/bindings/headers.h3
-rw-r--r--src/bun.js/bindings/headers.zig1
-rw-r--r--src/bun.js/event_loop.zig7
-rw-r--r--src/bun.js/javascript.zig270
-rw-r--r--src/bun_js.zig3
-rw-r--r--src/cli.zig3
-rw-r--r--src/http.zig7
-rw-r--r--src/resolver/resolver.zig28
-rw-r--r--src/watcher.zig15
15 files changed, 340 insertions, 34 deletions
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index 23c961480..d93ca7ac6 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -2139,12 +2139,12 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
// only reload those two
if (new_config.onRequest != .zero) {
- this.config.onRequest = new_config.onRequest;
this.config.onRequest.unprotect();
+ this.config.onRequest = new_config.onRequest;
}
if (new_config.onError != .zero) {
- this.config.onError = new_config.onError;
this.config.onError.unprotect();
+ this.config.onError = new_config.onError;
}
return this.thisObject.asObjectRef();
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 5b716ef79..f9ee0117a 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -2738,6 +2738,24 @@ DEFINE_VISIT_CHILDREN(GlobalObject);
// DEFINE_VISIT_CHILDREN(Zig::GlobalObject);
+void GlobalObject::reload()
+{
+ JSModuleLoader* moduleLoader = this->moduleLoader();
+ JSC::JSMap* registry = jsCast<JSC::JSMap*>(moduleLoader->get(
+ this,
+ Identifier::fromString(this->vm(), "registry"_s)));
+
+ registry->clear(this->vm());
+ this->requireMap()->clear(this->vm());
+ this->vm().heap.collectSync();
+}
+
+extern "C" void JSC__JSGlobalObject__reload(JSC__JSGlobalObject* arg0)
+{
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg0);
+ globalObject->reload();
+}
+
JSC::Identifier GlobalObject::moduleLoaderResolve(JSGlobalObject* globalObject,
JSModuleLoader* loader, JSValue key,
JSValue referrer, JSValue origin)
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 550d4d853..4a003fc60 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -364,6 +364,9 @@ public:
BunPlugin::OnResolve onResolvePlugins[BunPluginTargetMax + 1] {};
BunPluginTarget defaultBunPluginTarget = BunPluginTargetBun;
+
+ void reload();
+
JSC::Structure* pendingVirtualModuleResultStructure() { return m_pendingVirtualModuleResultStructure.get(this); }
// When a napi module initializes on dlopen, we need to know what the value is
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index fda55c3b4..59a3c43c2 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -3095,4 +3095,4 @@ JSC__JSValue JSC__JSValue__fastGet_(JSC__JSValue JSValue0, JSC__JSGlobalObject*
return JSValue::encode(
value.getObject()->getIfPropertyExists(globalObject, builtinNameMap(globalObject, arg2)));
-} \ No newline at end of file
+}
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index d39abf337..db48a858f 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -1860,6 +1860,13 @@ pub const JSGlobalObject = extern struct {
this.vm().throwError(this, err);
}
+ pub fn reload(this: *JSC.JSGlobalObject) void {
+ this.vm().drainMicrotasks();
+ this.vm().collectAsync();
+
+ return cppFn("reload", .{this});
+ }
+
pub const BunPluginTarget = enum(u8) {
bun = 0,
node = 1,
@@ -2124,6 +2131,7 @@ pub const JSGlobalObject = extern struct {
}
pub const Extern = [_][]const u8{
+ "reload",
"bunVM",
"putCachedObject",
"getCachedObject",
diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h
index e0073666e..2dd59b457 100644
--- a/src/bun.js/bindings/headers-cpp.h
+++ b/src/bun.js/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1665017239
+//-- AUTOGENERATED FILE -- 1665034748
// clang-format off
#pragma once
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index fdcd1878b..e0c818497 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format off
-//-- AUTOGENERATED FILE -- 1665017239
+//-- AUTOGENERATED FILE -- 1665034748
#pragma once
#include <stddef.h>
@@ -411,6 +411,7 @@ CPP_DECL JSC__ObjectPrototype* JSC__JSGlobalObject__objectPrototype(JSC__JSGloba
CPP_DECL JSC__JSPromisePrototype* JSC__JSGlobalObject__promisePrototype(JSC__JSGlobalObject* arg0);
CPP_DECL JSC__JSValue JSC__JSGlobalObject__putCachedObject(JSC__JSGlobalObject* arg0, const ZigString* arg1, JSC__JSValue JSValue2);
CPP_DECL JSC__RegExpPrototype* JSC__JSGlobalObject__regExpPrototype(JSC__JSGlobalObject* arg0);
+CPP_DECL void JSC__JSGlobalObject__reload(JSC__JSGlobalObject* arg0);
CPP_DECL JSC__SetIteratorPrototype* JSC__JSGlobalObject__setIteratorPrototype(JSC__JSGlobalObject* arg0);
CPP_DECL bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* arg0, unsigned char* arg1, uint16_t arg2);
CPP_DECL JSC__StringPrototype* JSC__JSGlobalObject__stringPrototype(JSC__JSGlobalObject* arg0);
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index 7b7c2f567..ba7aa0b0f 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -214,6 +214,7 @@ pub extern fn JSC__JSGlobalObject__objectPrototype(arg0: ?*JSC__JSGlobalObject)
pub extern fn JSC__JSGlobalObject__promisePrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.JSPromisePrototype;
pub extern fn JSC__JSGlobalObject__putCachedObject(arg0: ?*JSC__JSGlobalObject, arg1: [*c]const ZigString, JSValue2: JSC__JSValue) JSC__JSValue;
pub extern fn JSC__JSGlobalObject__regExpPrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.RegExpPrototype;
+pub extern fn JSC__JSGlobalObject__reload(arg0: ?*JSC__JSGlobalObject) void;
pub extern fn JSC__JSGlobalObject__setIteratorPrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.SetIteratorPrototype;
pub extern fn JSC__JSGlobalObject__startRemoteInspector(arg0: ?*JSC__JSGlobalObject, arg1: [*c]u8, arg2: u16) bool;
pub extern fn JSC__JSGlobalObject__stringPrototype(arg0: ?*JSC__JSGlobalObject) ?*bindings.StringPrototype;
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index 0ce2394c7..462136414 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -182,6 +182,7 @@ pub const CppTask = opaque {
};
const ThreadSafeFunction = JSC.napi.ThreadSafeFunction;
const MicrotaskForDefaultGlobalObject = JSC.MicrotaskForDefaultGlobalObject;
+const HotReloadTask = JSC.HotReloader.HotReloadTask;
// const PromiseTask = JSInternalPromise.Completion.PromiseTask;
pub const Task = TaggedPointerUnion(.{
FetchTasklet,
@@ -195,6 +196,7 @@ pub const Task = TaggedPointerUnion(.{
napi_async_work,
ThreadSafeFunction,
CppTask,
+ HotReloadTask,
// PromiseTask,
// TimeoutTasklet,
});
@@ -272,6 +274,11 @@ pub const EventLoop = struct {
transform_task.*.runFromJS();
transform_task.deinit();
},
+ .HotReloadTask => {
+ var transform_task: *HotReloadTask = task.get(HotReloadTask).?;
+ transform_task.*.run();
+ transform_task.deinit();
+ },
@field(Task.Tag, typeBaseName(@typeName(AnyTask))) => {
var any: *AnyTask = task.get(AnyTask).?;
any.run();
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 020399040..c337372e5 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -305,7 +305,8 @@ pub const VirtualMachine = struct {
has_loaded_constructors: bool = false,
node_modules: ?*NodeModuleBundle = null,
bundler: Bundler,
- watcher: ?*http.Watcher = null,
+ bun_dev_watcher: ?*http.Watcher = null,
+ bun_watcher: ?*JSC.Watcher = null,
console: *ZigConsoleClient,
log: *logger.Log,
event_listeners: EventListenerMixin.Map,
@@ -379,6 +380,12 @@ pub const VirtualMachine = struct {
poller: JSC.Poller = JSC.Poller{},
us_loop_reference_count: usize = 0,
is_us_loop_entered: bool = false,
+ pending_internal_promise: *JSC.JSInternalPromise = undefined,
+ pub fn reload(this: *VirtualMachine) void {
+ Output.debug("Reloading...", .{});
+ this.global.reload();
+ this.pending_internal_promise = this.loadEntryPoint(this.main) catch @panic("Failed to reload");
+ }
pub fn io(this: *VirtualMachine) *IO {
if (this.io_ == null) {
@@ -516,6 +523,10 @@ pub const VirtualMachine = struct {
return slice;
}
+ pub fn isWatcherEnabled(this: *VirtualMachine) bool {
+ return this.bun_dev_watcher != null or this.bun_watcher != null;
+ }
+
pub fn init(
allocator: std.mem.Allocator,
_args: Api.TransformOptions,
@@ -1483,15 +1494,17 @@ pub const VirtualMachine = struct {
}
promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(std.mem.span(main_file_name)));
+ this.pending_internal_promise = promise;
} else {
promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(this.main));
+ this.pending_internal_promise = promise;
}
this.waitForPromise(promise);
this.eventLoop().autoTick();
- return promise;
+ return this.pending_internal_promise;
}
pub fn loadMacroEntryPoint(this: *VirtualMachine, entry_path: string, function_name: string, specifier: string, hash: i32) !*JSInternalPromise {
@@ -2852,7 +2865,13 @@ pub const ModuleLoader = struct {
var fd: ?StoredFileDescriptorType = null;
var package_json: ?*PackageJSON = null;
- if (jsc_vm.watcher) |watcher| {
+ if (jsc_vm.bun_dev_watcher) |watcher| {
+ if (watcher.indexOf(hash)) |index| {
+ const _fd = watcher.watchlist.items(.fd)[index];
+ fd = if (_fd > 0) _fd else null;
+ package_json = watcher.watchlist.items(.package_json)[index];
+ }
+ } else if (jsc_vm.bun_watcher) |watcher| {
if (watcher.indexOf(hash)) |index| {
const _fd = watcher.watchlist.items(.fd)[index];
fd = if (_fd > 0) _fd else null;
@@ -2979,14 +2998,30 @@ pub const ModuleLoader = struct {
return error.PrintingErrorWriteFailed;
}
- if (jsc_vm.has_loaded) {
- return jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null);
- }
-
if (comptime Environment.dump_source) {
try dumpSource(specifier, &printer);
}
+ if (jsc_vm.isWatcherEnabled()) {
+ const resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null);
+
+ if (parse_result.input_fd) |fd_| {
+ if (jsc_vm.bun_watcher != null and !is_node_override and std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
+ jsc_vm.bun_watcher.?.addFile(
+ fd_,
+ path.text,
+ hash,
+ loader,
+ 0,
+ package_json,
+ true,
+ ) catch {};
+ }
+ }
+
+ return resolved_source;
+ }
+
return ResolvedSource{
.allocator = null,
.source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())),
@@ -3363,3 +3398,224 @@ const FetchFlags = enum {
return this != .transpile;
}
};
+
+pub const Watcher = @import("../watcher.zig").NewWatcher(*HotReloader);
+
+pub const HotReloader = struct {
+ const watcher = @import("../watcher.zig");
+
+ onAccept: std.ArrayHashMapUnmanaged(Watcher.HashType, bun.BabyList(OnAcceptCallback), bun.ArrayIdentityContext, false) = .{},
+ vm: *JSC.VirtualMachine,
+
+ pub const HotReloadTask = struct {
+ reloader: *HotReloader,
+ count: u8 = 0,
+ hashes: [8]u32 = [_]u32{0} ** 8,
+ concurrent_task: JSC.ConcurrentTask = undefined,
+
+ pub fn append(this: *HotReloadTask, id: u32) void {
+ if (this.count == 8) {
+ this.enqueue();
+ var reloader = this.reloader;
+ this.* = .{
+ .reloader = reloader,
+ .count = 0,
+ };
+ }
+
+ this.hashes[this.count] = id;
+ this.count += 1;
+ }
+
+ pub fn run(this: *HotReloadTask) void {
+ this.reloader.vm.reload();
+ }
+
+ pub fn enqueue(this: *HotReloadTask) void {
+ if (this.count == 0)
+ return;
+ var that = bun.default_allocator.create(HotReloadTask) catch unreachable;
+
+ that.* = this.*;
+ this.count = 0;
+ that.concurrent_task.task = Task.init(that);
+ that.reloader.vm.eventLoop().enqueueTaskConcurrent(&that.concurrent_task);
+ }
+
+ pub fn deinit(this: *HotReloadTask) void {
+ bun.default_allocator.destroy(this);
+ }
+ };
+
+ fn NewCallback(comptime FunctionSignature: type) type {
+ return union(enum) {
+ javascript_callback: JSC.Strong,
+ zig_callback: struct {
+ ptr: *anyopaque,
+ function: FunctionSignature,
+ },
+ };
+ }
+
+ pub const OnAcceptCallback = NewCallback(fn (
+ vm: *JSC.VirtualMachine,
+ specifier: []const u8,
+ ) void);
+
+ pub fn enableHotModuleReloading(this: *VirtualMachine) void {
+ if (this.bun_watcher != null)
+ return;
+
+ var reloader = bun.default_allocator.create(HotReloader) catch @panic("OOM");
+ reloader.* = .{
+ .vm = this,
+ };
+ this.bun_watcher = JSC.Watcher.init(
+ reloader,
+ this.bundler.fs,
+ bun.default_allocator,
+ ) catch @panic("Failed to enable File Watcher");
+
+ this.bundler.resolver.watcher = Resolver.ResolveWatcher(*Watcher, onMaybeWatchDirectory).init(this.bun_watcher.?);
+
+ this.bun_watcher.?.start() catch @panic("Failed to start File Watcher");
+ }
+
+ pub fn onMaybeWatchDirectory(watch: *Watcher, file_path: string, dir_fd: StoredFileDescriptorType) void {
+ // We don't want to watch:
+ // - Directories outside the root directory
+ // - Directories inside node_modules
+ if (std.mem.indexOf(u8, file_path, "node_modules") == null and std.mem.indexOf(u8, file_path, watch.fs.top_level_dir) != null) {
+ watch.addDirectory(dir_fd, file_path, Watcher.getHash(file_path), false) catch {};
+ }
+ }
+
+ pub fn onFileUpdate(
+ this: *HotReloader,
+ events: []watcher.WatchEvent,
+ changed_files: []?[:0]u8,
+ watchlist: watcher.Watchlist,
+ ) void {
+ var slice = watchlist.slice();
+ const file_paths = slice.items(.file_path);
+ var counts = slice.items(.count);
+ const kinds = slice.items(.kind);
+ const hashes = slice.items(.hash);
+ var file_descriptors = slice.items(.fd);
+ var ctx = this.vm.bun_watcher.?;
+ defer ctx.flushEvictions();
+ defer Output.flush();
+
+ var bundler = &this.vm.bundler;
+ var fs: *Fs.FileSystem = bundler.fs;
+ var rfs: *Fs.FileSystem.RealFS = &fs.fs;
+ var resolver = &bundler.resolver;
+ var _on_file_update_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+
+ var current_task: HotReloadTask = .{
+ .reloader = this,
+ };
+ defer current_task.enqueue();
+
+ for (events) |event| {
+ const file_path = file_paths[event.index];
+ const update_count = counts[event.index] + 1;
+ counts[event.index] = update_count;
+ const kind = kinds[event.index];
+
+ // so it's consistent with the rest
+ // if we use .extname we might run into an issue with whether or not the "." is included.
+ // const path = Fs.PathName.init(file_path);
+ const id = hashes[event.index];
+
+ if (comptime Environment.isDebug) {
+ Output.prettyErrorln("[watcher] {s}: -- {}", .{ @tagName(kind), event.op });
+ }
+
+ switch (kind) {
+ .file => {
+ if (event.op.delete or event.op.rename) {
+ ctx.removeAtIndex(
+ event.index,
+ 0,
+ &.{},
+ .file,
+ );
+ }
+
+ if (comptime bun.FeatureFlags.verbose_watcher) {
+ Output.prettyErrorln("<r><d>File changed: {s}<r>", .{fs.relativeTo(file_path)});
+ }
+
+ if (event.op.write) {
+ current_task.append(id);
+ }
+ },
+ .directory => {
+ const affected = event.names(changed_files);
+ var entries_option: ?*Fs.FileSystem.RealFS.EntriesOption = null;
+ if (affected.len > 0) {
+ entries_option = rfs.entries.get(file_path);
+ }
+
+ rfs.bustEntriesCache(file_path);
+ resolver.dir_cache.remove(file_path);
+
+ if (entries_option) |dir_ent| {
+ var last_file_hash: Watcher.HashType = std.math.maxInt(Watcher.HashType);
+ for (affected) |changed_name_ptr| {
+ const changed_name: []const u8 = std.mem.span((changed_name_ptr orelse continue));
+ if (changed_name.len == 0 or changed_name[0] == '~' or changed_name[0] == '.') continue;
+
+ const loader = (bundler.options.loaders.get(Fs.PathName.init(changed_name).ext) orelse .file);
+ if (loader.isJavaScriptLikeOrJSON() or loader == .css) {
+ var path_string: bun.PathString = undefined;
+ var file_hash: Watcher.HashType = last_file_hash;
+ const abs_path: string = brk: {
+ if (dir_ent.entries.get(changed_name)) |file_ent| {
+ // reset the file descriptor
+ file_ent.entry.cache.fd = 0;
+ file_ent.entry.need_stat = true;
+ path_string = file_ent.entry.abs_path;
+ file_hash = Watcher.getHash(path_string.slice());
+ for (hashes) |hash, entry_id| {
+ if (hash == file_hash) {
+ file_descriptors[entry_id] = 0;
+ break;
+ }
+ }
+
+ break :brk path_string.slice();
+ } else {
+ var file_path_without_trailing_slash = std.mem.trimRight(u8, file_path, std.fs.path.sep_str);
+ @memcpy(&_on_file_update_path_buf, file_path_without_trailing_slash.ptr, file_path_without_trailing_slash.len);
+ _on_file_update_path_buf[file_path_without_trailing_slash.len] = std.fs.path.sep;
+
+ @memcpy(_on_file_update_path_buf[file_path_without_trailing_slash.len + 1 ..].ptr, changed_name.ptr, changed_name.len);
+ const path_slice = _on_file_update_path_buf[0 .. file_path_without_trailing_slash.len + changed_name.len + 1];
+ file_hash = Watcher.getHash(path_slice);
+ break :brk path_slice;
+ }
+ };
+
+ // skip consecutive duplicates
+ if (last_file_hash == file_hash) continue;
+ last_file_hash = file_hash;
+
+ Output.prettyErrorln("<r> <d>File change: {s}<r>", .{fs.relativeTo(abs_path)});
+ }
+ }
+ }
+
+ // if (event.op.delete or event.op.rename)
+ // ctx.watcher.removeAtIndex(event.index, hashes[event.index], parent_hashes, .directory);
+ if (comptime false) {
+ Output.prettyErrorln("<r>📁 <d>Dir change: {s}<r>", .{fs.relativeTo(file_path)});
+ } else {
+ Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{fs.relativeTo(file_path)});
+ }
+ },
+ }
+ }
+ }
+};
diff --git a/src/bun_js.zig b/src/bun_js.zig
index 65a172b52..dc65cc391 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -115,6 +115,9 @@ pub const Run = struct {
}
pub fn start(this: *Run) void {
+ if (this.ctx.debug.hot_reload) {
+ JSC.HotReloader.enableHotModuleReloading(this.vm);
+ }
var promise = this.vm.loadEntryPoint(this.entry_path) catch return;
if (promise.status(this.vm.global.vm()) == .Rejected) {
diff --git a/src/cli.zig b/src/cli.zig
index 23082e2ae..39bc8797a 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -189,6 +189,7 @@ pub const Arguments = struct {
clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: jsx, js, json, tsx, ts, css") catch unreachable,
clap.parseParam("-u, --origin <STR> Rewrite import URLs to start with --origin. Default: \"\"") catch unreachable,
clap.parseParam("-p, --port <STR> Port to serve bun's dev server on. Default: \"3000\"") catch unreachable,
+ clap.parseParam("--hot Enable auto reload in bun's JavaScript runtime") catch unreachable,
clap.parseParam("--silent Don't repeat the command for bun run") catch unreachable,
clap.parseParam("<POS>... ") catch unreachable,
};
@@ -403,6 +404,7 @@ pub const Arguments = struct {
opts.generate_node_module_bundle = cmd == .BunCommand;
opts.inject = args.options("--inject");
opts.extension_order = args.options("--extension-order");
+ ctx.debug.hot_reload = args.flag("--hot");
ctx.passthrough = args.remaining();
opts.no_summary = args.flag("--no-summary");
@@ -781,6 +783,7 @@ pub const Command = struct {
dump_limits: bool = false,
fallback_only: bool = false,
silent: bool = false,
+ hot_reload: bool = false,
// technical debt
macros: ?MacroMap = null,
diff --git a/src/http.zig b/src/http.zig
index 49303d415..fedb30a65 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -1486,7 +1486,7 @@ pub const RequestContext = struct {
const boot = vm.bundler.options.framework.?.server.path;
std.debug.assert(boot.len > 0);
errdefer vm.deinit();
- vm.watcher = handler.watcher;
+ vm.bun_dev_watcher = handler.watcher;
{
vm.bundler.configureRouter(false) catch |err| {
handler.handleJSError(.configure_router, err) catch {};
@@ -3886,10 +3886,7 @@ pub const Server = struct {
server.watcher = try Watcher.init(server, server.bundler.fs, server.allocator);
if (comptime FeatureFlags.watch_directories and !Environment.isTest) {
- server.bundler.resolver.watcher = ResolveWatcher(Watcher){
- .context = server.watcher,
- .callback = onMaybeWatchDirectory,
- };
+ server.bundler.resolver.watcher = ResolveWatcher(*Watcher, onMaybeWatchDirectory).init(server.watcher);
}
}
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 56ce3135e..2c14089ee 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -314,13 +314,26 @@ var bin_folders_lock: Mutex = Mutex.init();
var bin_folders_loaded: bool = false;
const Timer = @import("../system_timer.zig").Timer;
-pub fn ResolveWatcher(comptime Context: type) type {
- return struct {
- context: *Context,
- callback: fn (*Context, dir_path: string, dir_fd: StoredFileDescriptorType) void = undefined,
- pub fn watch(this: @This(), dir_path: string, fd: StoredFileDescriptorType) void {
- return this.callback(this.context, dir_path, fd);
+pub const AnyResolveWatcher = struct {
+ context: *anyopaque,
+ callback: fn (*anyopaque, dir_path: string, dir_fd: StoredFileDescriptorType) void = undefined,
+
+ pub fn watch(this: @This(), dir_path: string, fd: StoredFileDescriptorType) void {
+ return this.callback(this.context, dir_path, fd);
+ }
+};
+
+pub fn ResolveWatcher(comptime Context: type, comptime onWatch: anytype) type {
+ return struct {
+ pub fn init(context: Context) AnyResolveWatcher {
+ return AnyResolveWatcher{
+ .context = context,
+ .callback = watch,
+ };
+ }
+ pub fn watch(this: *anyopaque, dir_path: string, fd: StoredFileDescriptorType) void {
+ onWatch(bun.cast(Context, this), dir_path, fd);
}
};
}
@@ -341,7 +354,7 @@ pub const Resolver = struct {
debug_logs: ?DebugLogs = null,
elapsed: u64 = 0, // tracing
- watcher: ?ResolveWatcher(HTTPWatcher) = null,
+ watcher: ?AnyResolveWatcher = null,
caches: CacheSet,
@@ -2538,7 +2551,6 @@ pub const Resolver = struct {
if (comptime FeatureFlags.watch_directories) {
// For existent directories which don't find a match
// Start watching it automatically,
- // onStartWatchingDirectory fn decides whether to actually watch.
if (r.watcher) |watcher| {
watcher.watch(entries.dir, entries.fd);
}
diff --git a/src/watcher.zig b/src/watcher.zig
index 138edca44..0663303bc 100644
--- a/src/watcher.zig
+++ b/src/watcher.zig
@@ -287,10 +287,6 @@ pub const WatchEvent = struct {
pub const Watchlist = std.MultiArrayList(WatchItem);
-// This implementation only works on macOS, for now.
-// The Internet seems to suggest basically always using FSEvents instead of kqueue
-// It seems like the main concern is max open file descriptors
-// Since we adjust the ulimit already, I think we can avoid that.
pub fn NewWatcher(comptime ContextType: type) type {
return struct {
const Watcher = @This();
@@ -425,23 +421,24 @@ pub fn NewWatcher(comptime ContextType: type) type {
std.debug.assert(DarwinWatcher.fd > 0);
const KEvent = std.c.Kevent;
- var changelist_array: [1]KEvent = std.mem.zeroes([1]KEvent);
+ var changelist_array: [128]KEvent = std.mem.zeroes([128]KEvent);
var changelist = &changelist_array;
while (true) {
defer Output.flush();
- _ = std.os.system.kevent(
+ const count_ = std.os.system.kevent(
DarwinWatcher.fd,
@as([*]KEvent, changelist),
0,
@as([*]KEvent, changelist),
- 1,
+ 128,
null,
);
- var watchevents = this.watch_events[0..1];
- for (changelist) |event, i| {
+ var changes = changelist[0..@intCast(usize, @maximum(0, count_))];
+ var watchevents = this.watch_events[0..changes.len];
+ for (changes) |event, i| {
watchevents[i].fromKEvent(event);
}