diff options
author | 2021-09-14 01:28:04 -0700 | |
---|---|---|
committer | 2021-09-14 01:28:04 -0700 | |
commit | bdd5502aefcdc388ebbfa563131417314acd32d1 (patch) | |
tree | ad2ed525534e70a3b31f0ee9904fcc716dc8d7b8 | |
parent | 2f301e39d78ab5f70b6c5468f8ab21e3a201e315 (diff) | |
download | bun-bdd5502aefcdc388ebbfa563131417314acd32d1.tar.gz bun-bdd5502aefcdc388ebbfa563131417314acd32d1.tar.zst bun-bdd5502aefcdc388ebbfa563131417314acd32d1.zip |
SPAs now work by default when there's a public/index.html file
-rw-r--r-- | src/http.zig | 114 | ||||
-rw-r--r-- | src/options.zig | 25 |
2 files changed, 118 insertions, 21 deletions
diff --git a/src/http.zig b/src/http.zig index 2abdf849e..a38a50c46 100644 --- a/src/http.zig +++ b/src/http.zig @@ -317,14 +317,20 @@ pub const RequestContext = struct { // std.mem.copy(u8, &tmp_buildfile_buf, relative_unrooted_path); // std.mem.copy(u8, tmp_buildfile_buf[relative_unrooted_path.len..], "/" // Search for /index.html - if (public_dir.openFile("index.html", .{})) |file| { + if (this.bundler.options.routes.single_page_app_routing and + this.bundler.options.routes.single_page_app_fd != 0) + { + this.sendSinglePageHTML() catch {}; + return null; + } else if (public_dir.openFile("index.html", .{})) |file| { var index_path = "index.html".*; relative_unrooted_path = &(index_path); _file = file; extension = "html"; } else |err| {} + // Okay is it actually a full path? - } else { + } else if (extension.len > 0) { if (public_dir.openFile(relative_unrooted_path, .{})) |file| { _file = file; } else |err| {} @@ -630,6 +636,35 @@ pub const RequestContext = struct { ); } + pub fn sendSinglePageHTML(ctx: *RequestContext) !void { + ctx.appendHeader("Content-Type", MimeType.html.value); + ctx.appendHeader("Cache-Control", "no-cache"); + + defer ctx.done(); + + std.debug.assert(ctx.bundler.options.routes.single_page_app_fd > 0); + const file = std.fs.File{ .handle = ctx.bundler.options.routes.single_page_app_fd }; + const stats = file.stat() catch |err| { + Output.prettyErrorln("<r><red>Error {s}<r> reading index.html", .{@errorName(err)}); + ctx.writeStatus(500) catch {}; + return; + }; + + const content_length = stats.size; + try ctx.writeStatus(200); + try ctx.prepareToSendBody(content_length, false); + + _ = try std.os.sendfile( + ctx.conn.client.socket.fd, + ctx.bundler.options.routes.single_page_app_fd, + 0, + content_length, + &[_]std.os.iovec_const{}, + &[_]std.os.iovec_const{}, + 0, + ); + } + pub const WatchBuilder = struct { watcher: *Watcher, bundler: *Bundler, @@ -2501,13 +2536,30 @@ pub const Server = struct { // However, we want to optimize for easy to copy paste // Nobody should get weird CORS errors when you go to the printed url. if (std.mem.readIntNative(u32, &addr.ipv4.host.octets) == 0 or std.mem.readIntNative(u128, &addr.ipv6.host.octets) == 0) { - Output.prettyError(" Bun!!<r>\n\n\n<d> Link:<r> <b><cyan>http://localhost:{d}<r>\n\n\n", .{ - addr.ipv4.port, - }); + if (server.bundler.options.routes.single_page_app_routing) { + Output.prettyError( + " Bun!!<r>\n\n\n<d> Link:<r> <b><cyan>http://localhost:{d}<r>\n <d>./{s}/index.html<r> \n\n\n", + .{ + addr.ipv4.port, + resolve_path.relative(server.bundler.fs.top_level_dir, server.bundler.options.routes.static_dir), + }, + ); + } else { + Output.prettyError(" Bun!!<r>\n\n\n<d> Link:<r> <b><cyan>http://localhost:{d}<r>\n\n\n", .{ + addr.ipv4.port, + }); + } } else { - Output.prettyError(" Bun!!<r>\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n\n\n", .{ - addr, - }); + if (server.bundler.options.routes.single_page_app_routing) { + Output.prettyError(" Bun!!<r>\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n <d>./{s}/index.html<r> \n\n\n", .{ + addr, + resolve_path.relative(server.bundler.fs.top_level_dir, server.bundler.options.routes.static_dir), + }); + } else { + Output.prettyError(" Bun!!<r>\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n\n\n", .{ + addr, + }); + } } Output.flush(); @@ -2698,6 +2750,8 @@ pub const Server = struct { return; }; } + + finished = finished or req_ctx.has_called_done; } }, else => {}, @@ -2720,19 +2774,21 @@ pub const Server = struct { finished = true; } } else { - if (!finished) { - req_ctx.handleRequest() catch |err| { - switch (err) { - error.ModuleNotFound => { - req_ctx.sendNotFound() catch {}; - }, - else => { - Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - did_print = true; - }, - } - }; - finished = true; + request_handler: { + if (!finished) { + req_ctx.handleRequest() catch |err| { + switch (err) { + error.ModuleNotFound => { + break :request_handler; + }, + else => { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + did_print = true; + }, + } + }; + finished = true; + } } } @@ -2745,8 +2801,24 @@ pub const Server = struct { did_print = true; }; } + + finished = finished or req_ctx.has_called_done; } } + + if (comptime features.public_folder != .none) { + if (!finished and (req_ctx.bundler.options.routes.single_page_app_routing and req_ctx.url.extname.len == 0)) { + req_ctx.sendSinglePageHTML() catch |err| { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + did_print = true; + }; + finished = true; + } + } + + if (!finished) { + req_ctx.sendNotFound() catch {}; + } } pub fn initWatcher(server: *Server) !void { diff --git a/src/options.zig b/src/options.zig index 240ceeaaf..5035f4c93 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1153,6 +1153,29 @@ pub const BundleOptions = struct { }; opts.routes.static_dir_enabled = opts.routes.static_dir_handle != null; } + + if (opts.routes.static_dir_enabled and (opts.framework == null or !opts.framework.?.server.isEnabled()) and !opts.routes.routes_enabled) { + const dir = opts.routes.static_dir_handle.?; + var index_html_file = dir.openFile("index.html", .{ .read = true }) catch |err| brk: { + switch (err) { + error.FileNotFound => {}, + else => { + Output.prettyErrorln( + "{s} when trying to open {s}/index.html. single page app routing is disabled.", + .{ @errorName(err), opts.routes.static_dir }, + ); + }, + } + opts.routes.single_page_app_routing = false; + break :brk null; + }; + + if (index_html_file) |index_dot_html| { + opts.routes.single_page_app_routing = true; + opts.routes.single_page_app_fd = index_dot_html.handle; + } + } + // Windows has weird locking rules for file access. // so it's a bad idea to keep a file handle open for a long time on Windows. if (isWindows and opts.routes.static_dir_handle != null) { @@ -1763,6 +1786,8 @@ pub const RouteConfig = struct { static_dir: string = "", static_dir_handle: ?std.fs.Dir = null, static_dir_enabled: bool = false, + single_page_app_routing: bool = false, + single_page_app_fd: StoredFileDescriptorType = 0, pub fn toAPI(this: *const RouteConfig) Api.LoadedRouteConfig { return .{ |