mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
[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.
This commit is contained in:
27
src/fs.zig
27
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 {
|
||||
|
||||
23
src/http.zig
23
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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user