diff options
author | 2023-08-25 21:08:41 -0700 | |
---|---|---|
committer | 2023-08-25 21:08:41 -0700 | |
commit | d98a93c3181426ea0565193303d3e63d3796231c (patch) | |
tree | 71a8d96bc4b75d902643ec877c03070cca8fcf45 /src/bun.js/api | |
parent | f70bb2497b2406e89afec3ee8a36a3b10ef66334 (diff) | |
download | bun-d98a93c3181426ea0565193303d3e63d3796231c.tar.gz bun-d98a93c3181426ea0565193303d3e63d3796231c.tar.zst bun-d98a93c3181426ea0565193303d3e63d3796231c.zip |
Automatically hot reload Bun.serve() (#4344)
* Automatically hot reload Bun.serve()
* Update doc
* Update example
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js/api')
-rw-r--r-- | src/bun.js/api/bun.zig | 60 | ||||
-rw-r--r-- | src/bun.js/api/server.classes.ts | 4 | ||||
-rw-r--r-- | src/bun.js/api/server.zig | 108 |
3 files changed, 151 insertions, 21 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index a9d7ed970..71b065993 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -2481,7 +2481,7 @@ pub fn serve( callframe: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { const arguments = callframe.arguments(2).slice(); - const config: JSC.API.ServerConfig = brk: { + var config: JSC.API.ServerConfig = brk: { var exception_ = [1]JSC.JSValueRef{null}; var exception = &exception_; @@ -2497,6 +2497,40 @@ pub fn serve( var exception_value: *JSC.JSValue = undefined; + if (config.allow_hot) { + if (globalObject.bunVM().hotMap()) |hot| { + if (config.id.len == 0) { + config.id = config.computeID(globalObject.allocator()); + } + + if (hot.getEntry(config.id)) |entry| { + switch (entry.tag()) { + @field(@TypeOf(entry.tag()), @typeName(JSC.API.HTTPServer)) => { + var server: *JSC.API.HTTPServer = entry.as(JSC.API.HTTPServer); + server.onReloadFromZig(&config, globalObject); + return server.thisObject; + }, + @field(@TypeOf(entry.tag()), @typeName(JSC.API.DebugHTTPServer)) => { + var server: *JSC.API.DebugHTTPServer = entry.as(JSC.API.DebugHTTPServer); + server.onReloadFromZig(&config, globalObject); + return server.thisObject; + }, + @field(@TypeOf(entry.tag()), @typeName(JSC.API.DebugHTTPSServer)) => { + var server: *JSC.API.DebugHTTPSServer = entry.as(JSC.API.DebugHTTPSServer); + server.onReloadFromZig(&config, globalObject); + return server.thisObject; + }, + @field(@TypeOf(entry.tag()), @typeName(JSC.API.HTTPSServer)) => { + var server: *JSC.API.HTTPSServer = entry.as(JSC.API.HTTPSServer); + server.onReloadFromZig(&config, globalObject); + return server.thisObject; + }, + else => {}, + } + } + } + } + // Listen happens on the next tick! // This is so we can return a Server object if (config.ssl_config != null) { @@ -2515,6 +2549,12 @@ pub fn serve( obj.protect(); server.thisObject = obj; + + if (config.allow_hot) { + if (globalObject.bunVM().hotMap()) |hot| { + hot.insert(config.id, server); + } + } return obj; } else { var server = JSC.API.HTTPSServer.init(config, globalObject.ptr()); @@ -2530,6 +2570,12 @@ pub fn serve( const obj = server.toJS(globalObject); obj.protect(); server.thisObject = obj; + + if (config.allow_hot) { + if (globalObject.bunVM().hotMap()) |hot| { + hot.insert(config.id, server); + } + } return obj; } } else { @@ -2547,6 +2593,12 @@ pub fn serve( const obj = server.toJS(globalObject); obj.protect(); server.thisObject = obj; + + if (config.allow_hot) { + if (globalObject.bunVM().hotMap()) |hot| { + hot.insert(config.id, server); + } + } return obj; } else { var server = JSC.API.HTTPServer.init(config, globalObject.ptr()); @@ -2563,6 +2615,12 @@ pub fn serve( obj.protect(); server.thisObject = obj; + + if (config.allow_hot) { + if (globalObject.bunVM().hotMap()) |hot| { + hot.insert(config.id, server); + } + } return obj; } } diff --git a/src/bun.js/api/server.classes.ts b/src/bun.js/api/server.classes.ts index 7db8a3444..544f37ce6 100644 --- a/src/bun.js/api/server.classes.ts +++ b/src/bun.js/api/server.classes.ts @@ -27,6 +27,10 @@ function generate(name) { port: { getter: "getPort", }, + id: { + getter: "getId", + cache: true, + }, pendingRequests: { getter: "getPendingRequests", }, diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 81a50a5a7..01f06ebf2 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -158,6 +158,36 @@ pub const ServerConfig = struct { inspector: bool = false, reuse_port: bool = false, + id: []const u8 = "", + allow_hot: bool = true, + + pub fn computeID(this: *const ServerConfig, allocator: std.mem.Allocator) []const u8 { + var arraylist = std.ArrayList(u8).init(allocator); + var writer = arraylist.writer(); + + writer.writeAll("[http]-") catch {}; + switch (this.address) { + .tcp => { + if (this.address.tcp.hostname) |host| { + writer.print("tcp:{s}:{d}", .{ + bun.sliceTo(host, 0), + this.address.tcp.port, + }) catch {}; + } else { + writer.print("tcp:localhost:{d}", .{ + this.address.tcp.port, + }) catch {}; + } + }, + .unix => { + writer.print("unix:{s}", .{ + bun.sliceTo(this.address.unix, 0), + }) catch {}; + }, + } + + return arraylist.items; + } pub const SSLConfig = struct { server_name: [*c]const u8 = null, @@ -794,6 +824,23 @@ pub const ServerConfig = struct { } } + if (arg.get(global, "id")) |id| { + if (id.isUndefinedOrNull()) { + args.allow_hot = false; + } else { + const id_str = id.toSlice( + global, + bun.default_allocator, + ); + + if (id_str.len > 0) { + args.id = (id_str.cloneIfNeeded(bun.default_allocator) catch unreachable).slice(); + } else { + args.allow_hot = false; + } + } + } + if (arg.get(global, "development")) |dev| { args.development = dev.coerce(bool, global); args.reuse_port = !args.development; @@ -4867,26 +4914,8 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp return JSC.jsBoolean(true); } - pub fn onReload( - this: *ThisServer, - globalThis: *JSC.JSGlobalObject, - callframe: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(1).slice(); - if (arguments.len < 1) { - globalThis.throwNotEnoughArguments("reload", 1, 0); - return .zero; - } - - var args_slice = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments); - defer args_slice.deinit(); - var exception_ref = [_]JSC.C.JSValueRef{null}; - var exception: JSC.C.ExceptionRef = &exception_ref; - var new_config = ServerConfig.fromJS(globalThis, &args_slice, exception); - if (exception.* != null) { - globalThis.throwValue(exception_ref[0].?.value()); - return .zero; - } + pub fn onReloadFromZig(this: *ThisServer, new_config: *ServerConfig, globalThis: *JSC.JSGlobalObject) void { + httplog("onReload", .{}); // only reload those two if (this.config.onRequest != new_config.onRequest) { @@ -4915,6 +4944,30 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp this.config.websocket = ws.*; } // we don't remove it } + } + + pub fn onReload( + this: *ThisServer, + globalThis: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments = callframe.arguments(1).slice(); + if (arguments.len < 1) { + globalThis.throwNotEnoughArguments("reload", 1, 0); + return .zero; + } + + var args_slice = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments); + defer args_slice.deinit(); + var exception_ref = [_]JSC.C.JSValueRef{null}; + var exception: JSC.C.ExceptionRef = &exception_ref; + var new_config = ServerConfig.fromJS(globalThis, &args_slice, exception); + if (exception.* != null) { + globalThis.throwValue(exception_ref[0].?.value()); + return .zero; + } + + this.onReloadFromZig(&new_config, globalThis); return this.thisObject; } @@ -5066,6 +5119,15 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp return JSC.JSValue.jsNumber(listener.getLocalPort()); } + pub fn getId( + this: *ThisServer, + globalThis: *JSC.JSGlobalObject, + ) callconv(.C) JSC.JSValue { + var str = bun.String.create(this.config.id); + defer str.deref(); + return str.toJS(globalThis); + } + pub fn getPendingRequests( this: *ThisServer, _: *JSC.JSGlobalObject, @@ -5170,6 +5232,12 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp } pub fn stop(this: *ThisServer, abrupt: bool) void { + if (this.config.allow_hot and this.config.id.len > 0) { + if (this.globalThis.bunVM().hotMap()) |hot| { + hot.remove(this.config.id); + } + } + this.stopListening(abrupt); this.deinitIfWeCan(); } |