diff options
author | 2022-01-30 02:04:56 -0800 | |
---|---|---|
committer | 2022-01-30 02:04:56 -0800 | |
commit | af69b47c228783825e1bae9873dda878cf5bdebf (patch) | |
tree | 8dde45c6aea79f9f8030c6ed77447783c4a50dae | |
parent | fb2c7e5f38e31e9402810b34ab8dab3d109db696 (diff) | |
download | bun-af69b47c228783825e1bae9873dda878cf5bdebf.tar.gz bun-af69b47c228783825e1bae9873dda878cf5bdebf.tar.zst bun-af69b47c228783825e1bae9873dda878cf5bdebf.zip |
[bun dev] Support HTML files in either project root or `public` folder (`static`)
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | integration/apps/bun-dev-index-html.sh | 53 | ||||
-rw-r--r-- | integration/apps/bun-dev.sh | 55 | ||||
-rw-r--r-- | src/http.zig | 89 | ||||
-rw-r--r-- | src/options.zig | 4 |
5 files changed, 181 insertions, 32 deletions
@@ -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{ |