bosia 0.6.18 → 0.6.20

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.6.18",
3
+ "version": "0.6.20",
4
4
  "type": "module",
5
5
  "description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
6
6
  "keywords": [
package/src/core/build.ts CHANGED
@@ -159,7 +159,7 @@ const clientPromise = Bun.build({
159
159
  target: "browser",
160
160
  conditions: ["svelte"],
161
161
  splitting: true,
162
- naming: { chunk: "[name]-[hash].[ext]" },
162
+ naming: { entry: "[name]-[hash].[ext]", chunk: "[name]-[hash].[ext]" },
163
163
  minify: isProduction,
164
164
  sourcemap: isProduction ? "none" : "linked",
165
165
  define: {
@@ -222,10 +222,19 @@ if (!isProduction && svelteMapCache.size > 0) {
222
222
  // 6. Collect output files for dist/manifest.json
223
223
  const jsFiles: string[] = [];
224
224
  const cssFiles: string[] = [];
225
+ // 0.6.19 hashed the entry filename; the old `f === "hydrate.js"` exact-match
226
+ // no longer hits, and the fallback `startsWith("hydrate")` picks the first
227
+ // hydrate-* chunk by array order — which can be a small leaf module (e.g.
228
+ // `src/lib/version.ts`) that sorts before the real entry. Use Bun's
229
+ // `output.kind === "entry-point"` instead so we pin the actual entry.
230
+ let clientEntry: string | null = null;
225
231
  for (const output of clientResult.outputs) {
226
232
  const rel = relative(`${OUT_DIR}/client`, output.path);
227
233
  if (output.path.endsWith(".js")) jsFiles.push(rel);
228
234
  if (output.path.endsWith(".css")) cssFiles.push(rel);
235
+ if ((output as { kind?: string }).kind === "entry-point" && output.path.endsWith(".js")) {
236
+ clientEntry = rel;
237
+ }
229
238
  }
230
239
 
231
240
  // Entry is always "index.js" due to naming: { entry: "index.[ext]" }
@@ -241,6 +250,7 @@ const distManifest = {
241
250
  js: jsFiles,
242
251
  css: cssFiles,
243
252
  entry:
253
+ clientEntry ??
244
254
  jsFiles.find((f) => f === "hydrate.js") ??
245
255
  jsFiles.find((f) => f.startsWith("hydrate")) ??
246
256
  "hydrate.js",
@@ -136,6 +136,45 @@ main().catch((err) => {
136
136
  console.error("[bosia] hydration failed", err);
137
137
  });
138
138
 
139
+ // ─── Stale-Build Recovery ─────────────────────────────────
140
+ // After a deploy, browsers with the old HTML/entry cached may try to import
141
+ // hashed chunks that no longer exist on the server. The dynamic import()
142
+ // rejects and the router's promise chain has no .catch, so the rejection
143
+ // surfaces here as an unhandledrejection. Trigger a single full reload with a
144
+ // cache-busting query so the browser can't serve the old HTML from disk.
145
+ // A 10s sessionStorage guard prevents an infinite reload loop if the new
146
+ // build is also broken.
147
+ if (typeof window !== "undefined" && process.env.NODE_ENV === "production") {
148
+ const KEY = "bosia:reload-attempt";
149
+ const STALE_CHUNK =
150
+ /Failed to fetch dynamically imported module|Importing a module script failed|error loading dynamically imported module|Loading chunk|ChunkLoadError/i;
151
+
152
+ try {
153
+ const last = Number(sessionStorage.getItem(KEY) ?? 0);
154
+ if (last && Date.now() - last < 10_000) {
155
+ sessionStorage.removeItem(KEY);
156
+ console.error("[bosia] reload guard hit — new build may also be broken.");
157
+ } else {
158
+ window.addEventListener("unhandledrejection", (e) => {
159
+ const msg = String((e.reason as { message?: unknown })?.message ?? e.reason ?? "");
160
+ if (!STALE_CHUNK.test(msg)) return;
161
+ e.preventDefault();
162
+ try {
163
+ sessionStorage.setItem(KEY, String(Date.now()));
164
+ } catch {
165
+ // Storage may be unavailable (private mode) — best effort.
166
+ }
167
+ const sep = window.location.search ? "&" : "?";
168
+ window.location.replace(
169
+ `${window.location.pathname}${window.location.search}${sep}_v=${Date.now()}${window.location.hash}`,
170
+ );
171
+ });
172
+ }
173
+ } catch {
174
+ // sessionStorage may throw in restricted contexts — give up silently.
175
+ }
176
+ }
177
+
139
178
  // ─── Hot Reload (dev only) ────────────────────────────────
140
179
 
141
180
  if (process.env.NODE_ENV !== "production") {
package/src/core/html.ts CHANGED
@@ -395,6 +395,7 @@ const STATIC_EXTS = new Set([
395
395
  ".ttf",
396
396
  ".xml",
397
397
  ".txt",
398
+ ".webmanifest",
398
399
  ]);
399
400
 
400
401
  export function isStaticPath(path: string): boolean {