bosia 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,7 +48,7 @@ function sortRoutes<T extends { pattern: string }>(routes: T[]): T[] {
48
48
  }
49
49
 
50
50
  export function generateRoutesFile(manifest: RouteManifest): void {
51
- const lines: string[] = ["// AUTO-GENERATED by bosia build — do not edit\n"];
51
+ const lines: string[] = ["// AUTO-GENERATED by bosia build — do not edit\n", ONCE_HELPER];
52
52
 
53
53
  const pages = sortRoutes(manifest.pages);
54
54
  const apis = sortRoutes(manifest.apis);
@@ -56,6 +56,7 @@ export function generateRoutesFile(manifest: RouteManifest): void {
56
56
  // clientRoutes
57
57
  lines.push("export const clientRoutes: Array<{");
58
58
  lines.push(" pattern: string;");
59
+ lines.push(" id: string;");
59
60
  lines.push(" page: () => Promise<any>;");
60
61
  lines.push(" layouts: (() => Promise<any>)[];");
61
62
  lines.push(" errorPages: { loader: () => Promise<any>; depth: number }[];");
@@ -92,6 +93,7 @@ export function generateRoutesFile(manifest: RouteManifest): void {
92
93
  .join(", ");
93
94
  lines.push(" {");
94
95
  lines.push(` pattern: ${JSON.stringify(clientPattern(r.pattern))},`);
96
+ lines.push(` id: ${JSON.stringify(r.id)},`);
95
97
  lines.push(` page: () => import(${JSON.stringify(toImportPath(r.page))}),`);
96
98
  lines.push(` layouts: [${layoutImports}],`);
97
99
  lines.push(` errorPages: [${errorPageImports}],`);
@@ -111,6 +113,7 @@ export function generateRoutesFile(manifest: RouteManifest): void {
111
113
  // serverRoutes
112
114
  lines.push("export const serverRoutes: Array<{");
113
115
  lines.push(" pattern: string;");
116
+ lines.push(" id: string;");
114
117
  lines.push(" pageModule: () => Promise<any>;");
115
118
  lines.push(" layoutModules: (() => Promise<any>)[];");
116
119
  lines.push(" pageServer: (() => Promise<any>) | null;");
@@ -120,28 +123,19 @@ export function generateRoutesFile(manifest: RouteManifest): void {
120
123
  lines.push(" cache: boolean | null;");
121
124
  lines.push("}> = [");
122
125
  for (const r of pages) {
123
- const layoutImports = r.layouts
124
- .map((l) => `() => import(${JSON.stringify(toImportPath(l))})`)
125
- .join(", ");
126
+ const layoutImports = r.layouts.map((l) => serverImport(l)).join(", ");
126
127
  const layoutServerImports = r.layoutServers
127
- .map(
128
- (ls) =>
129
- `{ loader: () => import(${JSON.stringify(toImportPath(ls.path))}), depth: ${ls.depth} }`,
130
- )
128
+ .map((ls) => `{ loader: ${serverImport(ls.path)}, depth: ${ls.depth} }`)
131
129
  .join(", ");
132
130
  const errorPageImports = r.errorPages
133
- .map(
134
- (ep) =>
135
- `{ loader: () => import(${JSON.stringify(toImportPath(ep.path))}), depth: ${ep.depth} }`,
136
- )
131
+ .map((ep) => `{ loader: ${serverImport(ep.path)}, depth: ${ep.depth} }`)
137
132
  .join(", ");
138
133
  lines.push(" {");
139
134
  lines.push(` pattern: ${JSON.stringify(r.pattern)},`);
140
- lines.push(` pageModule: () => import(${JSON.stringify(toImportPath(r.page))}),`);
135
+ lines.push(` id: ${JSON.stringify(r.id)},`);
136
+ lines.push(` pageModule: ${serverImport(r.page)},`);
141
137
  lines.push(` layoutModules: [${layoutImports}],`);
142
- lines.push(
143
- ` pageServer: ${r.pageServer ? `() => import(${JSON.stringify(toImportPath(r.pageServer))})` : "null"},`,
144
- );
138
+ lines.push(` pageServer: ${r.pageServer ? serverImport(r.pageServer) : "null"},`);
145
139
  lines.push(` layoutServers: [${layoutServerImports}],`);
146
140
  lines.push(` errorPages: [${errorPageImports}],`);
147
141
  lines.push(` trailingSlash: ${JSON.stringify(r.trailingSlash)},`);
@@ -153,12 +147,14 @@ export function generateRoutesFile(manifest: RouteManifest): void {
153
147
  // apiRoutes
154
148
  lines.push("export const apiRoutes: Array<{");
155
149
  lines.push(" pattern: string;");
150
+ lines.push(" id: string;");
156
151
  lines.push(" module: () => Promise<any>;");
157
152
  lines.push("}> = [");
158
153
  for (const r of apis) {
159
154
  lines.push(" {");
160
155
  lines.push(` pattern: ${JSON.stringify(r.pattern)},`);
161
- lines.push(` module: () => import(${JSON.stringify(toImportPath(r.server))}),`);
156
+ lines.push(` id: ${JSON.stringify(r.id)},`);
157
+ lines.push(` module: ${serverImport(r.server)},`);
162
158
  lines.push(" },");
163
159
  }
164
160
  lines.push("];\n");
@@ -166,9 +162,7 @@ export function generateRoutesFile(manifest: RouteManifest): void {
166
162
  // errorPage
167
163
  const ep = manifest.errorPage;
168
164
  lines.push(
169
- `export const errorPage: (() => Promise<any>) | null = ${
170
- ep ? `() => import(${JSON.stringify(toImportPath(ep))})` : "null"
171
- };\n`,
165
+ `export const errorPage: (() => Promise<any>) | null = ${ep ? serverImport(ep) : "null"};\n`,
172
166
  );
173
167
 
174
168
  mkdirSync(".bosia", { recursive: true });
@@ -197,6 +191,7 @@ function generateClientRoutesFile(
197
191
 
198
192
  lines.push("export const clientRoutes: Array<{");
199
193
  lines.push(" pattern: string;");
194
+ lines.push(" id: string;");
200
195
  lines.push(" page: () => Promise<any>;");
201
196
  lines.push(" layouts: (() => Promise<any>)[];");
202
197
  lines.push(" errorPages: { loader: () => Promise<any>; depth: number }[];");
@@ -227,6 +222,7 @@ function generateClientRoutesFile(
227
222
  .join(", ");
228
223
  lines.push(" {");
229
224
  lines.push(` pattern: ${JSON.stringify(clientPattern(r.pattern))},`);
225
+ lines.push(` id: ${JSON.stringify(r.id)},`);
230
226
  lines.push(` page: () => import(${JSON.stringify(toImportPath(r.page))}),`);
231
227
  lines.push(` layouts: [${layoutImports}],`);
232
228
  lines.push(` errorPages: [${errorPageImports}],`);
@@ -253,6 +249,26 @@ function generateClientRoutesFile(
253
249
  writeFileSync(".bosia/routes.client.ts", lines.join("\n"));
254
250
  }
255
251
 
252
+ // The renderer calls these thunks several times per request (metadata, load,
253
+ // render), and a repeated `import()` still resolves the specifier each time even
254
+ // though the module is cached — ~8% of CPU on an uncached SSR request. Each thunk
255
+ // keeps its first promise instead. A rejected import is dropped, so the next
256
+ // request retries it rather than replaying the failure forever.
257
+ const ONCE_HELPER = `function once<T>(load: () => Promise<T>): () => Promise<T> {
258
+ let p: Promise<T> | undefined;
259
+ return () =>
260
+ (p ??= load().catch((err) => {
261
+ p = undefined;
262
+ throw err;
263
+ }));
264
+ }
265
+ `;
266
+
267
+ /** A memoized server-side import thunk for `src/routes/<routePath>`. */
268
+ function serverImport(routePath: string): string {
269
+ return `once(() => import(${JSON.stringify(toImportPath(routePath))}))`;
270
+ }
271
+
256
272
  // Import path from .bosia/routes.ts to src/routes/<routePath>
257
273
  function toImportPath(routePath: string): string {
258
274
  return "../src/routes/" + toPosix(routePath);
@@ -142,6 +142,7 @@ export function scanRoutes(): RouteManifest {
142
142
  if (items.some((i) => i.isFile() && i.name === "+server.ts")) {
143
143
  apis.push({
144
144
  pattern: toUrlPath(urlSegments),
145
+ id: `/${dir}`,
145
146
  server: rel("+server.ts"),
146
147
  });
147
148
  }
@@ -158,6 +159,7 @@ export function scanRoutes(): RouteManifest {
158
159
  const pageFile = rel("+page.svelte");
159
160
  pages.push({
160
161
  pattern: toUrlPath(urlSegments),
162
+ id: `/${dir}`,
161
163
  page: pageFile,
162
164
  layouts: [...currentLayouts],
163
165
  pageServer: pageServerFile,
@@ -11,6 +11,7 @@ import { isDev } from "./html.ts";
11
11
  import { OUT_DIR } from "./paths.ts";
12
12
  import { pidsOnPort } from "./port.ts";
13
13
  import { CACHE_ENABLED, CACHE_KEYS } from "./cache.ts";
14
+ import { logAssetCache } from "./assetCache.ts";
14
15
 
15
16
  // ─── User Hooks ──────────────────────────────────────────
16
17
  // Production prefers the pre-bundled `${OUT_DIR}/hooks.server.js` emitted by the
@@ -44,6 +45,9 @@ if (hooksPath) {
44
45
 
45
46
  const app = await createApp({ handle });
46
47
 
48
+ // Dev serves files straight from disk (no manifest), so the cache never runs there.
49
+ if (!isDev) logAssetCache();
50
+
47
51
  // ─── Listen ──────────────────────────────────────────────
48
52
 
49
53
  const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : isDev ? 9001 : 9000;