aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile12
-rw-r--r--integration/apps/bun-dev-index-html.sh53
-rw-r--r--integration/apps/bun-dev.sh55
-rw-r--r--src/http.zig89
-rw-r--r--src/options.zig4
5 files changed, 181 insertions, 32 deletions
diff --git a/Makefile b/Makefile
index 54ee02c89..195f5fcf7 100644
--- a/Makefile
+++ b/Makefile
@@ -637,7 +637,15 @@ mkdir-dev:
test-install:
cd integration/scripts && $(NPM_CLIENT) install
-test-all: test-install test-with-hmr test-no-hmr test-create-next test-create-react test-bun-run test-bun-install
+test-bun-dev:
+ BUN_BIN=$(RELEASE_BUN) bash integration/apps/bun-dev.sh
+ BUN_BIN=$(RELEASE_BUN) bash integration/apps/bun-dev-index-html.sh
+
+test-dev-bun-dev:
+ BUN_BIN=$(DEBUG_BUN) bash integration/apps/bun-dev.sh
+ BUN_BIN=$(DEBUG_BUN) bash integration/apps/bun-dev-index-html.sh
+
+test-all: test-install test-with-hmr test-no-hmr test-create-next test-create-react test-bun-run test-bun-install test-bun-dev
copy-test-node-modules:
rm -rf integration/snippets/package-json-exports/node_modules || echo "";
@@ -684,7 +692,7 @@ test-dev-no-hmr: copy-test-node-modules
test-dev-bun-run:
cd integration/apps && BUN_BIN=$(DEBUG_BUN) bash bun-run-check.sh
-test-dev-all: test-dev-with-hmr test-dev-no-hmr test-dev-create-next test-dev-create-react test-dev-bun-run test-dev-bun-install
+test-dev-all: test-dev-with-hmr test-dev-no-hmr test-dev-create-next test-dev-create-react test-dev-bun-run test-dev-bun-install test-dev-bun-dev
test-dev-bunjs:
test-dev: test-dev-with-hmr
diff --git a/integration/apps/bun-dev-index-html.sh b/integration/apps/bun-dev-index-html.sh
new file mode 100644
index 000000000..44322eeec
--- /dev/null
+++ b/integration/apps/bun-dev-index-html.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+set -euo pipefail
+
+killall -9 $(basename $BUN_BIN) || echo ""
+
+dir=$(mktemp -d --suffix=bun-dev-check)
+
+index_content="<html><body>index.html</body></html>"
+bacon_content="<html><body>bacon.html</body></html>"
+js_content="console.log('hi')"
+
+echo $index_content >"$dir/index.html"
+echo $js_content >"$dir/index.js"
+echo $bacon_content >"$dir/bacon.html"
+
+cd $dir
+
+$BUN_BIN --port 8087 &
+sleep 0.005
+
+if [ "$(curl --fail http://localhost:8087/)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/index)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/index)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/index.html)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/index.html)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/foo/foo)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/index.html)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/bacon)" != "$bacon_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/bacon)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/bacon.html)" != "$bacon_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/bacon.html)'"
+ exit 1
+fi
+
+killall -9 $(basename $BUN_BIN) || echo ""
+echo "✅ bun dev index html check passed."
diff --git a/integration/apps/bun-dev.sh b/integration/apps/bun-dev.sh
new file mode 100644
index 000000000..806b38aac
--- /dev/null
+++ b/integration/apps/bun-dev.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+set -euo pipefail
+
+killall -9 $(basename $BUN_BIN) || echo ""
+
+dir=$(mktemp -d --suffix=bun-dev-check)
+
+index_content="<html><body>index.html</body></html>"
+bacon_content="<html><body>bacon.html</body></html>"
+js_content="console.log('hi')"
+
+mkdir -p $dir/public
+
+echo $index_content >"$dir/public/index.html"
+echo $js_content >"$dir/index.js"
+echo $bacon_content >"$dir/public/bacon.html"
+
+cd $dir
+
+$BUN_BIN --port 8087 &
+sleep 0.005
+
+if [ "$(curl --fail http://localhost:8087/)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/index)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/index)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/index.html)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/index.html)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/foo/foo)" != "$index_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/index.html)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/bacon)" != "$bacon_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/bacon)'"
+ exit 1
+fi
+
+if [ "$(curl --fail http://localhost:8087/bacon.html)" != "$bacon_content" ]; then
+ echo "ERR: Expected '$index_content', got '$(curl --fail http://localhost:8087/bacon.html)'"
+ exit 1
+fi
+
+killall -9 $(basename $BUN_BIN) || echo ""
+echo "✅ bun dev index html check passed."
diff --git a/src/http.zig b/src/http.zig
index f05fdb161..ebcb1acf0 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -426,7 +426,7 @@ pub const RequestContext = struct {
try this.writeBodyBuf(bb.items);
}
- fn matchPublicFolder(this: *RequestContext) ?bundler.ServeResult {
+ fn matchPublicFolder(this: *RequestContext, comptime extensionless: bool) ?bundler.ServeResult {
if (!this.bundler.options.routes.static_dir_enabled) return null;
const relative_path = this.url.path;
var extension = this.url.extname;
@@ -462,7 +462,7 @@ pub const RequestContext = struct {
} else |_| {}
// Okay is it actually a full path?
- } else if (extension.len > 0) {
+ } else if (extension.len > 0 and (!extensionless or strings.eqlComptime(extension, "html"))) {
if (public_dir.openFile(relative_unrooted_path, .{})) |file| {
_file = file;
} else |_| {}
@@ -528,9 +528,18 @@ pub const RequestContext = struct {
var output_file = OutputFile.initFile(file.*, absolute_path, stat.size);
output_file.value.copy.close_handle_on_complete = true;
output_file.value.copy.autowatch = false;
+
+ // if it wasn't a symlink, we never got the absolute path
+ // so it could still be missing a file extension
+ var ext = std.fs.path.extension(absolute_path);
+ if (ext.len > 0) ext = ext[1..];
+
+ // even if it was an absolute path, the file extension could just be a dot, like "foo."
+ if (ext.len == 0) ext = extension;
+
return bundler.ServeResult{
.file = output_file,
- .mime_type = MimeType.byExtension(std.fs.path.extension(absolute_path)[1..]),
+ .mime_type = MimeType.byExtension(ext),
};
}
@@ -798,13 +807,17 @@ pub const RequestContext = struct {
}
pub fn sendSinglePageHTML(ctx: *RequestContext) !void {
+ 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 };
+ return try sendHTMLFile(ctx, file);
+ }
+
+ pub fn sendHTMLFile(ctx: *RequestContext, file: std.fs.File) !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 {};
@@ -3040,17 +3053,33 @@ pub const Server = struct {
const start_time = Global.getStartTime();
const now = std.time.nanoTimestamp();
Output.printStartEnd(start_time, now);
+
+ const display_path: string = brk: {
+ if (server.bundler.options.routes.single_page_app_routing) {
+ const lhs = std.mem.trimRight(u8, server.bundler.fs.top_level_dir, std.fs.path.sep_str);
+ const rhs = std.mem.trimRight(u8, server.bundler.options.routes.static_dir, std.fs.path.sep_str);
+
+ if (strings.eql(lhs, rhs)) {
+ break :brk ".";
+ }
+
+ break :brk resolve_path.relative(lhs, rhs);
+ }
+
+ break :brk "";
+ };
+
// This is technically imprecise.
// 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) {
if (server.bundler.options.routes.single_page_app_routing) {
Output.prettyError(
- " bun!! <d>v{s}<r>\n\n\n Link:<r> <b><cyan>http://localhost:{d}<r>\n <d>./{s}/index.html<r> \n\n\n",
+ " bun!! <d>v{s}<r>\n\n\n Link:<r> <b><cyan>http://localhost:{d}<r>\n <d>{s}/index.html<r> \n\n\n",
.{
Global.package_json_version,
addr.ipv4.port,
- resolve_path.relative(server.bundler.fs.top_level_dir, server.bundler.options.routes.static_dir),
+ display_path,
},
);
} else {
@@ -3061,10 +3090,10 @@ pub const Server = struct {
}
} else {
if (server.bundler.options.routes.single_page_app_routing) {
- Output.prettyError(" bun!! <d>v{s}<r>\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n <d>./{s}/index.html<r> \n\n\n", .{
+ Output.prettyError(" bun!! <d>v{s}<r>\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n <d>{s}/index.html<r> \n\n\n", .{
Global.package_json_version,
addr,
- resolve_path.relative(server.bundler.fs.top_level_dir, server.bundler.options.routes.static_dir),
+ display_path,
});
} else {
Output.prettyError(" bun!! <d>v{s}\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n\n\n", .{
@@ -3266,32 +3295,34 @@ pub const Server = struct {
if (!finished) {
switch (comptime features.public_folder) {
- .first => {
- if (!finished) {
- if (req_ctx.matchPublicFolder()) |result| {
- finished = true;
- req_ctx.renderServeResult(result) catch |err| {
+ .none => {
+ if (comptime features.single_page_app_routing) {
+ if (req_ctx.url.isRoot(server.bundler.options.routes.asset_prefix_path)) {
+ req_ctx.sendSinglePageHTML() catch |err| {
Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
did_print = true;
- return;
};
+ finished = true;
}
-
- finished = finished or req_ctx.has_called_done;
}
},
- .none => {
- if (comptime features.single_page_app_routing) {
- if (req_ctx.url.isRoot(server.bundler.options.routes.asset_prefix_path)) {
- req_ctx.sendSinglePageHTML() catch |err| {
+ else => {
+ // Check if this is a route to an HTML file in the public folder.
+ // Note: the public folder may actually just be the root folder
+ // In this case, we only check if the pathname has no extension
+ if (!finished) {
+ if (req_ctx.matchPublicFolder(true)) |result| {
+ finished = true;
+ req_ctx.renderServeResult(result) catch |err| {
Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
did_print = true;
+ return;
};
- finished = true;
}
+
+ finished = finished or req_ctx.has_called_done;
}
},
- else => {},
}
}
@@ -3331,7 +3362,7 @@ pub const Server = struct {
if (comptime features.public_folder == .last) {
if (!finished) {
- if (req_ctx.matchPublicFolder()) |result| {
+ if (req_ctx.matchPublicFolder(false)) |result| {
finished = true;
req_ctx.renderServeResult(result) catch |err| {
Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
@@ -3345,10 +3376,12 @@ pub const Server = struct {
if (comptime features.single_page_app_routing or 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;
- };
+ if (!finished) {
+ req_ctx.sendSinglePageHTML() catch |err| {
+ Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
+ did_print = true;
+ };
+ }
finished = true;
}
}
diff --git a/src/options.zig b/src/options.zig
index 41b84c9b4..393c1e128 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -1393,7 +1393,7 @@ pub const BundleOptions = struct {
opts.resolve_mode = .lazy;
var dir_to_use: string = opts.routes.static_dir;
- const static_dir_set = !opts.routes.static_dir_enabled or dir_to_use.len > 0;
+ const static_dir_set = opts.routes.static_dir_enabled or dir_to_use.len == 0;
var disabled_static = false;
var chosen_dir = dir_to_use;
@@ -2133,7 +2133,7 @@ pub const RouteConfig = struct {
}
pub const DefaultDir = "pages";
- pub const DefaultStaticDir = "public";
+ pub const DefaultStaticDir: string = "public";
pub const DefaultExtensions = [_]string{ "tsx", "ts", "mjs", "jsx", "js" };
pub inline fn zero() RouteConfig {
return RouteConfig{