better way to do static assets

This commit is contained in:
Zack Radisic
2025-09-30 20:55:16 -07:00
parent 294368565b
commit b3fe9c0cd3
5 changed files with 123 additions and 16 deletions

View File

@@ -21,6 +21,8 @@ routes: []Route = &[_]Route{},
build_output_dir: []const u8 = "dist",
/// Router types with their server entrypoints
router_types: []RouterType = &[_]RouterType{},
/// Static assets
assets: [][]const u8 = &[_][]const u8{},
/// All memory allocated with bun.default_allocator here
router: bun.ptr.Owned(*FrameworkRouter),
@@ -130,6 +132,23 @@ fn initFromJSON(self: *Manifest, source: *const logger.Source, log: *logger.Log)
}
}
if (json_obj.get("assets")) |assets_prop| {
if (assets_prop.data == .e_array) {
const items = assets_prop.data.e_array.items.slice();
self.assets = try allocator.alloc([]const u8, items.len);
for (items, self.assets) |*in, *out| {
if (in.data != .e_string) {
// All style array elements must be strings
try log.addError(&json_source, Loc.Empty, "\"assets\" must be an array of strings");
return error.InvalidManifest;
}
const style_str = try in.data.e_string.string(allocator);
out.* = style_str;
}
}
}
// Parse router_types array (optional)
if (json_obj.get("router_types")) |router_types_prop| {
if (router_types_prop.data == .e_array) {
@@ -165,18 +184,18 @@ fn initFromJSON(self: *Manifest, source: *const logger.Source, log: *logger.Log)
}
}
// Parse entries array
const entries_prop = json_obj.get("entries") orelse {
try log.addError(&json_source, json_expr.loc, "manifest.json must have an 'entries' field");
// Parse routes array
const routes_prop = json_obj.get("routes") orelse {
try log.addError(&json_source, json_expr.loc, "manifest.json must have an 'routes' field");
return error.InvalidManifest;
};
if (entries_prop.data != .e_array) {
try log.addError(&json_source, entries_prop.loc, "manifest.json entries must be an array");
if (routes_prop.data != .e_array) {
try log.addError(&json_source, routes_prop.loc, "manifest.json routes must be an array");
return error.InvalidManifest;
}
const entries = entries_prop.data.e_array.items.slice();
const entries = routes_prop.data.e_array.items.slice();
// Group entries by route_index
var route_map = std.AutoHashMap(u32, std.ArrayList(RawManifestEntry)).init(temp_allocator);

View File

@@ -231,10 +231,7 @@ pub fn ProductionServerMethods(protocol_enum: bun.api.server.Protocol, developme
pub fn setBakeManifestRoutes(server: *Server, app: *Server.App, manifest: *bun.bake.Manifest) void {
// Add route handler for /_bun/* static chunk files
// FIXME: this is being done dynamically. Either put the _bun/*
// files in the manifest or read the directory and make the routes
// up front
app.get("/_bun/*", *Server, server, bakeStaticChunkRequestHandler);
setStaticRoutes(server, app, manifest);
// First, we need to serve the client entrypoint files
// These are shared across all SSG routes of the same type
@@ -275,6 +272,64 @@ pub fn ProductionServerMethods(protocol_enum: bun.api.server.Protocol, developme
}
}
pub fn setStaticRoutes(server: *Server, app: *Server.App, manifest: *bun.bake.Manifest) void {
const assets = manifest.assets;
const pathbuf = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(pathbuf);
for (assets) |asset_path| {
bun.assert(bun.strings.hasPrefixComptime(asset_path, "/_bun/"));
const file = bun.strings.trimPrefixComptime(u8, asset_path, "/_bun/");
const file_path_copy = bun.default_allocator.dupe(u8, bun.path.joinStringBuf(
pathbuf,
&[_][]const u8{ manifest.build_output_dir, file },
.auto,
)) catch |e| bun.handleOom(e);
// Determine MIME type based on file extension
const mime_type = if (std.mem.endsWith(u8, asset_path, ".js"))
bun.http.MimeType.javascript
else if (std.mem.endsWith(u8, asset_path, ".css"))
bun.http.MimeType.css
else if (std.mem.endsWith(u8, asset_path, ".map"))
bun.http.MimeType.json
else
bun.http.MimeType.other;
// Create a file blob for the static chunk
const store = jsc.WebCore.Blob.Store.initFile(
.{ .path = .{ .string = bun.PathString.init(file_path_copy) } },
mime_type,
bun.default_allocator,
) catch |e| bun.handleOom(e);
const blob = jsc.WebCore.Blob{
.size = jsc.WebCore.Blob.max_size,
.store = store,
.content_type = mime_type.value,
.globalThis = server.globalThis,
};
// Create a file route and serve it
const any_server = AnyServer.from(server);
const file_route = FileRoute.initFromBlob(blob, .{
.server = any_server,
.status_code = 200,
});
ServerConfig.applyStaticRoute(
any_server,
Server.ssl_enabled,
app,
*FileRoute,
file_route,
asset_path,
.{ .method = bun.http.Method.Set.init(.{ .GET = true }) },
);
}
}
pub fn bakeStaticChunkRequestHandler(server: *ThisServer, req: *uws.Request, resp: *App.Response) void {
const manifest = server.bake_prod.get().?.manifest;

View File

@@ -758,11 +758,30 @@ pub fn buildWithVm(ctx: bun.cli.Command.Context, cwd: []const u8, vm: *VirtualMa
// styles: string[][]
route_style_references,
);
const path_buffer = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(path_buffer);
render_promise.setHandled(vm.jsc_vm);
vm.waitForPromise(.{ .normal = render_promise });
switch (render_promise.unwrap(vm.jsc_vm, .mark_handled)) {
.pending => unreachable,
.fulfilled => |manifest_value| {
// Add assets field to manifest with all client-side files in _bun directory
// First, count how many client assets we have
const asset_count: usize = bundled_outputs.len;
// Create assets array and directly add filenames
const assets_js = try JSValue.createEmptyArray(global, asset_count);
for (bundled_outputs, 0..) |file, asset_index| {
const str = bun.path.joinStringBuf(path_buffer, [_][]const u8{ "/", file.dest_path }, .posix);
bun.assert(bun.strings.hasPrefixComptime(str, "/_bun/"));
var bunstr = bun.String.init(str);
try assets_js.putIndex(global, @intCast(asset_index), bunstr.transferToJS(global));
}
_ = manifest_value.put(global, "assets", assets_js);
// Write manifest to file
const manifest_path = try std.fs.path.join(allocator, &.{ root_dir_path, "manifest.json" });
defer allocator.free(manifest_path);

View File

@@ -850,6 +850,9 @@ pub fn fromJS(
var log = bun.logger.Log.init(bun.default_allocator);
defer log.deinit();
var arena = std.heap.ArenaAllocator.init(bun.default_allocator);
errdefer arena.deinit();
var types = try std.ArrayListUnmanaged(bun.bake.FrameworkRouter.Type).initCapacity(
bun.default_allocator,
args.bake.?.framework.file_system_router_types.len,
@@ -865,7 +868,7 @@ pub fn fromJS(
const entry = transpiler.resolver.readDirInfoIgnoreError(joined_root) orelse
continue;
try types.append(bun.default_allocator, .{
types.appendAssumeCapacity(.{
.abs_root = bun.strings.withoutTrailingSlash(entry.abs_path),
.prefix = fsr.prefix,
.ignore_underscores = fsr.ignore_underscores,
@@ -883,14 +886,22 @@ pub fn fromJS(
});
}
var router: ?bun.ptr.Owned(*bun.bake.FrameworkRouter) = bun.ptr.Owned(*bun.bake.FrameworkRouter).alloc(try bun.bake.FrameworkRouter.initEmpty(root, types.items, bun.default_allocator)) catch bun.outOfMemory();
var router: ?bun.ptr.Owned(*bun.bake.FrameworkRouter) = bun.ptr.Owned(*bun.bake.FrameworkRouter).alloc(try bun.bake.FrameworkRouter.initEmpty(root, types: {
const ret = types.items;
types = .{};
break :types ret;
}, bun.default_allocator)) catch bun.outOfMemory();
errdefer if (router) |*r| {
r.get().deinit(bun.default_allocator);
r.deinitShallow();
};
var manifest = bun.bake.Manifest{
.arena = std.heap.ArenaAllocator.init(bun.default_allocator),
.arena = arena: {
const ret = arena;
arena = std.heap.ArenaAllocator.init(bun.default_allocator);
break :arena ret;
},
.router = bun.take(&router).?,
};
errdefer manifest.deinit();

View File

@@ -169,8 +169,10 @@ export async function renderRoutesForProdStatic(
type Manifest = {
version: string;
entries: ManifestEntry[];
routes: ManifestEntry[];
router_types: Array<{ server_entrypoint: string | null }>;
server_runtime?: string;
assets: Array<string>;
};
let entries: ManifestEntry[] = [];
@@ -279,7 +281,7 @@ export async function renderRoutesForProdStatic(
);
// Build the router_types array (make server_entrypoint relative to _bun folder)
const routerTypes = [];
const routerTypes: Array<{ server_entrypoint: string | null }> = [];
for (let i = 0; i < routerTypeServerEntrypoints.length; i++) {
const serverEntrypoint = routerTypeServerEntrypoints[i];
if (serverEntrypoint) {
@@ -298,8 +300,9 @@ export async function renderRoutesForProdStatic(
const manifest: Manifest = {
version: "0.0.1",
entries: entries,
routes: entries,
router_types: routerTypes,
assets: [],
};
return manifest;