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.
- package/package.json +1 -1
- package/src/ambient.d.ts +3 -0
- package/src/cli/add.ts +15 -12
- package/src/cli/block.ts +18 -16
- package/src/cli/feat.ts +31 -11
- package/src/cli/page.ts +18 -16
- package/src/cli/registry.ts +117 -20
- package/src/cli/theme.ts +7 -2
- package/src/core/assetCache.ts +124 -0
- package/src/core/backend.ts +8 -1
- package/src/core/build.ts +7 -2
- package/src/core/cache.ts +51 -23
- package/src/core/client/App.svelte +15 -26
- package/src/core/client/page.svelte.ts +15 -0
- package/src/core/client/prefetch.ts +55 -60
- package/src/core/dev.ts +11 -1
- package/src/core/env.ts +18 -1
- package/src/core/etag.ts +13 -0
- package/src/core/hooks.ts +17 -0
- package/src/core/html.ts +146 -25
- package/src/core/plugins/inspector/bun-plugin.ts +31 -18
- package/src/core/renderer.ts +114 -77
- package/src/core/routeFile.ts +36 -20
- package/src/core/scanner.ts +2 -0
- package/src/core/server.bun.ts +4 -0
- package/src/core/server.ts +294 -94
- package/src/core/staticManifest.ts +127 -14
- package/src/core/svelteCompiler.ts +96 -27
- package/src/core/types.ts +4 -0
- package/templates/default/.env.example +7 -0
- package/templates/demo/.env.example +7 -0
- package/templates/shop/.env.example +3 -0
- package/templates/shop/src/hooks.server.ts +12 -2
- package/templates/shop/src/routes/(private)/+layout.server.ts +2 -6
- package/templates/store/.env.example +3 -0
- package/templates/store/src/hooks.server.ts +12 -2
- package/templates/store/src/routes/(private)/+layout.server.ts +2 -6
package/src/core/routeFile.ts
CHANGED
|
@@ -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(`
|
|
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(`
|
|
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);
|
package/src/core/scanner.ts
CHANGED
|
@@ -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,
|
package/src/core/server.bun.ts
CHANGED
|
@@ -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;
|