aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-10 01:37:23 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-10 01:37:23 -0800
commit2e2521c6381104e7697b9c44918bf8fb313ccb3e (patch)
tree1292eaba2f344a002ffb2fda50131a91732eb18b
parentbcdd2cf220de171bd48d0293015283ab2d104a91 (diff)
downloadbun-2e2521c6381104e7697b9c44918bf8fb313ccb3e.tar.gz
bun-2e2521c6381104e7697b9c44918bf8fb313ccb3e.tar.zst
bun-2e2521c6381104e7697b9c44918bf8fb313ccb3e.zip
[bun dev] Implement `hash:` namespace for `file` loader to improve browser cache invalidation
This appends a hash to URLs and import paths In `bun dev`, this means: `/foo.woff2` => `/hash:/foo.woff2` `bun dev` simply ignores this.
-rw-r--r--src/fs.zig27
-rw-r--r--src/http.zig23
-rw-r--r--src/linker.zig42
3 files changed, 70 insertions, 22 deletions
diff --git a/src/fs.zig b/src/fs.zig
index f11651ae7..7218f7987 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -662,14 +662,26 @@ pub const FileSystem = struct {
mtime: i128 = 0,
mode: std.fs.File.Mode = 0,
- threadlocal var hash_bytes: [32]u8 = undefined;
threadlocal var hash_name_buf: [1024]u8 = undefined;
pub fn hashName(
this: *const ModKey,
basename: string,
) !string {
+ return try std.fmt.bufPrint(
+ &hash_name_buf,
+ "{s}-{x}",
+ .{
+ basename,
+ this.hash(),
+ },
+ );
+ }
+ pub fn hash(
+ this: *const ModKey,
+ ) u64 {
+ var hash_bytes: [32]u8 = undefined;
// We shouldn't just read the contents of the ModKey into memory
// The hash should be deterministic across computers and operating systems.
// inode is non-deterministic across volumes within the same compuiter
@@ -679,15 +691,10 @@ pub const FileSystem = struct {
std.mem.writeIntNative(@TypeOf(this.size), hash_bytes_remain[0..@sizeOf(@TypeOf(this.size))], this.size);
hash_bytes_remain = hash_bytes_remain[@sizeOf(@TypeOf(this.size))..];
std.mem.writeIntNative(@TypeOf(this.mtime), hash_bytes_remain[0..@sizeOf(@TypeOf(this.mtime))], this.mtime);
-
- return try std.fmt.bufPrint(
- &hash_name_buf,
- "{s}-{x}",
- .{
- basename,
- @truncate(u32, std.hash.Wyhash.hash(1, &hash_bytes)),
- },
- );
+ hash_bytes_remain = hash_bytes_remain[@sizeOf(@TypeOf(this.mtime))..];
+ std.debug.assert(hash_bytes_remain.len == 8);
+ hash_bytes_remain[0..8].* = @bitCast([8]u8, @as(u64, 0));
+ return std.hash.Wyhash.hash(0, &hash_bytes);
}
pub fn generate(_: *RealFS, _: string, file: std.fs.File) anyerror!ModKey {
diff --git a/src/http.zig b/src/http.zig
index 62a07cfc0..89ed1fe97 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -2636,9 +2636,26 @@ pub const RequestContext = struct {
return true;
}
- if (ctx.url.path.len > "blob:".len and strings.eqlComptimeIgnoreLen(ctx.url.path[0.."blob:".len], "blob:")) {
- try ctx.handleBlobURL(server);
- return true;
+ if (ctx.url.path.len > "blob:".len) {
+ if (strings.eqlComptimeIgnoreLen(ctx.url.path[0.."blob:".len], "blob:")) {
+ try ctx.handleBlobURL(server);
+ return true;
+ }
+
+ // From HTTP, we serve files with a hash modkey
+ // The format is
+ // hash:${hash}/${ORIGINAL_PATH}
+ // hash:abcdefg123/app/foo/my-file.jpeg
+ // The hash exists for browser cache invalidation
+ if (strings.eqlComptimeIgnoreLen(ctx.url.path[0.."hash:".len], "hash:")) {
+ var current = ctx.url.path;
+ current = current["hash:".len..];
+ if (strings.indexOfChar(current, '/')) |i| {
+ current = current[i + 1 ..];
+ ctx.url.path = current;
+ return false;
+ }
+ }
}
const isMaybePrefix = ctx.url.path.len > "bun:".len;
diff --git a/src/linker.zig b/src/linker.zig
index b108ccce7..1ddcc1eef 100644
--- a/src/linker.zig
+++ b/src/linker.zig
@@ -92,6 +92,20 @@ pub const Linker = struct {
};
}
+ pub fn getModKey(
+ this: *ThisLinker,
+ file_path: Fs.Path,
+ fd: ?FileDescriptorType,
+ ) !Fs.FileSystem.RealFS.ModKey {
+ var file: std.fs.File = if (fd) |_fd| std.fs.File{ .handle = _fd } else try std.fs.openFileAbsolute(file_path.text, .{ .read = true });
+ Fs.FileSystem.setMaxFd(file.handle);
+ const modkey = try Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, file_path.text, file);
+
+ if (fd == null)
+ file.close();
+ return modkey;
+ }
+
pub fn getHashedFilename(
this: *ThisLinker,
file_path: Fs.Path,
@@ -104,20 +118,15 @@ pub const Linker = struct {
return hashed_result.value_ptr.*;
}
}
- var file: std.fs.File = if (fd) |_fd| std.fs.File{ .handle = _fd } else try std.fs.openFileAbsolute(file_path.text, .{ .read = true });
- Fs.FileSystem.setMaxFd(file.handle);
- var modkey = try Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, file_path.text, file);
- const hash_name = try modkey.hashName(file_path.name.base);
+
+ const modkey = try this.getModKey(file_path, fd);
+ const hash_name = modkey.hashName(file_path.text);
if (Bundler.isCacheEnabled) {
var hashed = std.hash.Wyhash.hash(0, file_path.text);
try this.hashed_filenames.put(hashed, try this.allocator.dupe(u8, hash_name));
}
- if (this.fs.fs.needToCloseFiles() and fd == null) {
- file.close();
- }
-
return hash_name;
}
@@ -651,6 +660,21 @@ pub const Linker = struct {
if (use_hashed_name) {
var basepath = Fs.Path.init(source_path);
+
+ if (linker.options.serve) {
+ var hash_buf: [64]u8 = undefined;
+ const modkey = try linker.getModKey(basepath, null);
+
+ return Fs.Path.init(try origin.joinAlloc(
+ linker.allocator,
+ std.fmt.bufPrint(&hash_buf, "hash:{x}/", .{modkey.hash()}) catch unreachable,
+ dirname,
+ basename,
+ absolute_pathname.ext,
+ source_path,
+ ));
+ }
+
basename = try linker.getHashedFilename(basepath, null);
}
@@ -690,7 +714,7 @@ pub const Linker = struct {
import_record.path = try linker.generateImportPath(
source_dir,
if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform.isNotBun()) path.pretty else path.text,
- Bundler.isCacheEnabled and loader == .file,
+ loader == .file,
path.namespace,
origin,
import_path_format,