mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 20:09:04 +00:00
better way to do static assets
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user