diff options
author | 2022-02-10 01:37:23 -0800 | |
---|---|---|
committer | 2022-02-10 01:37:23 -0800 | |
commit | 2e2521c6381104e7697b9c44918bf8fb313ccb3e (patch) | |
tree | 1292eaba2f344a002ffb2fda50131a91732eb18b | |
parent | bcdd2cf220de171bd48d0293015283ab2d104a91 (diff) | |
download | bun-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.zig | 27 | ||||
-rw-r--r-- | src/http.zig | 23 | ||||
-rw-r--r-- | src/linker.zig | 42 |
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, |