mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
SPAs now work by default when there's a public/index.html file
This commit is contained in:
114
src/http.zig
114
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 {
|
||||
|
||||
@@ -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 .{
|
||||
|
||||
Reference in New Issue
Block a user