@pyreon/zero 0.24.0 → 0.24.1
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/lib/server.js +42 -17
- package/package.json +10 -10
- package/src/vite-plugin.ts +65 -20
package/lib/server.js
CHANGED
|
@@ -2299,7 +2299,7 @@ function zeroPlugin(userConfig = {}) {
|
|
|
2299
2299
|
const pathname = req.url?.split("?")[0] ?? "/";
|
|
2300
2300
|
if (pathname.startsWith("/@") || pathname.startsWith("/__")) return next();
|
|
2301
2301
|
if (/\.\w+$/.test(pathname)) return next();
|
|
2302
|
-
handle404(server, routesDir, pathname, res).then((handled) => {
|
|
2302
|
+
handle404(server, routesDir, pathname, res, root, req.originalUrl ?? pathname).then((handled) => {
|
|
2303
2303
|
if (!handled) next();
|
|
2304
2304
|
}, (err) => {
|
|
2305
2305
|
console.error("[Pyreon] Error in 404 handler:", err);
|
|
@@ -2423,28 +2423,53 @@ async function dispatchApiRoute(server, req, res) {
|
|
|
2423
2423
|
return true;
|
|
2424
2424
|
}
|
|
2425
2425
|
/**
|
|
2426
|
-
*
|
|
2427
|
-
*
|
|
2428
|
-
*
|
|
2429
|
-
*
|
|
2430
|
-
*
|
|
2431
|
-
*
|
|
2432
|
-
*
|
|
2433
|
-
*
|
|
2434
|
-
*
|
|
2426
|
+
* 404 handler for unmatched URLs in dev. Three behaviours:
|
|
2427
|
+
*
|
|
2428
|
+
* 1. If the URL matches a real route pattern, return false (caller falls
|
|
2429
|
+
* through to the next middleware — Vite's SPA shell etc.).
|
|
2430
|
+
* 2. Otherwise, try `renderSsr`. Even for `mode: 'ssg'` / `mode: 'spa'`
|
|
2431
|
+
* apps (no upstream SSR middleware registered) this works in dev: the
|
|
2432
|
+
* router's `findNotFoundFallback` (PR L5 / M1.2) walks the routes
|
|
2433
|
+
* tree, finds a `notFoundComponent` (`_404.tsx` / `_not-found.tsx`)
|
|
2434
|
+
* attached to the deepest matching parent layout, builds a synthetic
|
|
2435
|
+
* chain `[...layouts, syntheticLeaf]`, and renderSsr produces 404
|
|
2436
|
+
* HTML INSIDE the layout's chrome — matching what `dist/404.html`
|
|
2437
|
+
* ships at build time.
|
|
2438
|
+
* 3. If renderSsr returns null (no `notFoundComponent` reachable from
|
|
2439
|
+
* any layout), fall back to a bare static HTML page so the user
|
|
2440
|
+
* gets SOMETHING.
|
|
2441
|
+
*
|
|
2442
|
+
* **Pre-fix this function ALWAYS emitted the bare static page in step 3**,
|
|
2443
|
+
* ignoring any user-provided `_404.tsx` / `_not-found.tsx`. For
|
|
2444
|
+
* `mode: 'ssr'` apps the upstream SSR middleware caught the 404 first
|
|
2445
|
+
* (so a `_404.tsx` worked there), but for `mode: 'ssg'` / `mode: 'spa'`
|
|
2446
|
+
* apps the SSR middleware never registered and unmatched URLs fell
|
|
2447
|
+
* through here directly — dev showed the bare fallback while the
|
|
2448
|
+
* SSG-built `dist/404.html` shipped the branded version. Production-
|
|
2449
|
+
* vs-dev drift; no warning.
|
|
2450
|
+
*
|
|
2451
|
+
* For `mode: 'ssr'` apps the upstream SSR middleware is still the
|
|
2452
|
+
* primary path (cheap when matched). renderSsr may be called twice on a
|
|
2453
|
+
* truly-unmatched URL (once by the upstream middleware, once here as
|
|
2454
|
+
* fallback). The duplicate cost is purely a no-op `resolveRoute` call
|
|
2455
|
+
* returning `matched: []` again — no extra render work.
|
|
2435
2456
|
*
|
|
2436
2457
|
* Returns true if the 404 was handled (response sent), false if the path
|
|
2437
2458
|
* actually matches a route (caller continues to next middleware).
|
|
2438
|
-
*
|
|
2439
|
-
* Pre-M1.2 a stale comment claimed `_404.tsx` "cannot be SSR-rendered because
|
|
2440
|
-
* the compiler emits _tpl() calls that require document". That was wrong — the
|
|
2441
|
-
* SSR runtime renders compiler-emitted components fine via `renderToString`
|
|
2442
|
-
* (no document needed). The static fallback exists for backward compat with
|
|
2443
|
-
* apps that don't ship `_404.tsx`, not because SSR-rendering it is impossible.
|
|
2444
2459
|
*/
|
|
2445
|
-
async function handle404(server, _routesDir, pathname, res) {
|
|
2460
|
+
async function handle404(server, _routesDir, pathname, res, root, originalUrl) {
|
|
2446
2461
|
const routes = (await server.ssrLoadModule(VIRTUAL_ROUTES_ID)).routes;
|
|
2447
2462
|
if (flattenRoutePatterns(routes).some((pattern) => matchPattern(pattern, pathname))) return false;
|
|
2463
|
+
try {
|
|
2464
|
+
const result = await renderSsr(server, root, originalUrl, pathname);
|
|
2465
|
+
if (result !== null) {
|
|
2466
|
+
res.statusCode = result.status;
|
|
2467
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
2468
|
+
res.setHeader("Content-Length", Buffer.byteLength(result.html));
|
|
2469
|
+
res.end(result.html);
|
|
2470
|
+
return true;
|
|
2471
|
+
}
|
|
2472
|
+
} catch {}
|
|
2448
2473
|
const html = await render404Page(void 0);
|
|
2449
2474
|
res.statusCode = 404;
|
|
2450
2475
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/zero",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Pyreon Zero — zero-config full-stack framework powered by Pyreon and Vite",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vit Bokisch",
|
|
@@ -173,15 +173,15 @@
|
|
|
173
173
|
"lint": "oxlint ."
|
|
174
174
|
},
|
|
175
175
|
"dependencies": {
|
|
176
|
-
"@pyreon/core": "^0.24.
|
|
177
|
-
"@pyreon/head": "^0.24.
|
|
178
|
-
"@pyreon/meta": "^0.24.
|
|
179
|
-
"@pyreon/reactivity": "^0.24.
|
|
180
|
-
"@pyreon/router": "^0.24.
|
|
181
|
-
"@pyreon/runtime-dom": "^0.24.
|
|
182
|
-
"@pyreon/runtime-server": "^0.24.
|
|
183
|
-
"@pyreon/server": "^0.24.
|
|
184
|
-
"@pyreon/vite-plugin": "^0.24.
|
|
176
|
+
"@pyreon/core": "^0.24.1",
|
|
177
|
+
"@pyreon/head": "^0.24.1",
|
|
178
|
+
"@pyreon/meta": "^0.24.1",
|
|
179
|
+
"@pyreon/reactivity": "^0.24.1",
|
|
180
|
+
"@pyreon/router": "^0.24.1",
|
|
181
|
+
"@pyreon/runtime-dom": "^0.24.1",
|
|
182
|
+
"@pyreon/runtime-server": "^0.24.1",
|
|
183
|
+
"@pyreon/server": "^0.24.1",
|
|
184
|
+
"@pyreon/vite-plugin": "^0.24.1",
|
|
185
185
|
"vite": "^8.0.0"
|
|
186
186
|
},
|
|
187
187
|
"devDependencies": {
|
package/src/vite-plugin.ts
CHANGED
|
@@ -312,7 +312,14 @@ export function zeroPlugin(userConfig: ZeroConfig = {}): Plugin[] {
|
|
|
312
312
|
return next();
|
|
313
313
|
if (/\.\w+$/.test(pathname)) return next();
|
|
314
314
|
|
|
315
|
-
handle404(
|
|
315
|
+
handle404(
|
|
316
|
+
server,
|
|
317
|
+
routesDir,
|
|
318
|
+
pathname,
|
|
319
|
+
res,
|
|
320
|
+
root,
|
|
321
|
+
req.originalUrl ?? pathname,
|
|
322
|
+
).then(
|
|
316
323
|
(handled) => {
|
|
317
324
|
if (!handled) next();
|
|
318
325
|
},
|
|
@@ -593,30 +600,47 @@ async function dispatchApiRoute(
|
|
|
593
600
|
}
|
|
594
601
|
|
|
595
602
|
/**
|
|
596
|
-
*
|
|
603
|
+
* 404 handler for unmatched URLs in dev. Three behaviours:
|
|
597
604
|
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
+
* 1. If the URL matches a real route pattern, return false (caller falls
|
|
606
|
+
* through to the next middleware — Vite's SPA shell etc.).
|
|
607
|
+
* 2. Otherwise, try `renderSsr`. Even for `mode: 'ssg'` / `mode: 'spa'`
|
|
608
|
+
* apps (no upstream SSR middleware registered) this works in dev: the
|
|
609
|
+
* router's `findNotFoundFallback` (PR L5 / M1.2) walks the routes
|
|
610
|
+
* tree, finds a `notFoundComponent` (`_404.tsx` / `_not-found.tsx`)
|
|
611
|
+
* attached to the deepest matching parent layout, builds a synthetic
|
|
612
|
+
* chain `[...layouts, syntheticLeaf]`, and renderSsr produces 404
|
|
613
|
+
* HTML INSIDE the layout's chrome — matching what `dist/404.html`
|
|
614
|
+
* ships at build time.
|
|
615
|
+
* 3. If renderSsr returns null (no `notFoundComponent` reachable from
|
|
616
|
+
* any layout), fall back to a bare static HTML page so the user
|
|
617
|
+
* gets SOMETHING.
|
|
618
|
+
*
|
|
619
|
+
* **Pre-fix this function ALWAYS emitted the bare static page in step 3**,
|
|
620
|
+
* ignoring any user-provided `_404.tsx` / `_not-found.tsx`. For
|
|
621
|
+
* `mode: 'ssr'` apps the upstream SSR middleware caught the 404 first
|
|
622
|
+
* (so a `_404.tsx` worked there), but for `mode: 'ssg'` / `mode: 'spa'`
|
|
623
|
+
* apps the SSR middleware never registered and unmatched URLs fell
|
|
624
|
+
* through here directly — dev showed the bare fallback while the
|
|
625
|
+
* SSG-built `dist/404.html` shipped the branded version. Production-
|
|
626
|
+
* vs-dev drift; no warning.
|
|
627
|
+
*
|
|
628
|
+
* For `mode: 'ssr'` apps the upstream SSR middleware is still the
|
|
629
|
+
* primary path (cheap when matched). renderSsr may be called twice on a
|
|
630
|
+
* truly-unmatched URL (once by the upstream middleware, once here as
|
|
631
|
+
* fallback). The duplicate cost is purely a no-op `resolveRoute` call
|
|
632
|
+
* returning `matched: []` again — no extra render work.
|
|
605
633
|
*
|
|
606
634
|
* Returns true if the 404 was handled (response sent), false if the path
|
|
607
635
|
* actually matches a route (caller continues to next middleware).
|
|
608
|
-
*
|
|
609
|
-
* Pre-M1.2 a stale comment claimed `_404.tsx` "cannot be SSR-rendered because
|
|
610
|
-
* the compiler emits _tpl() calls that require document". That was wrong — the
|
|
611
|
-
* SSR runtime renders compiler-emitted components fine via `renderToString`
|
|
612
|
-
* (no document needed). The static fallback exists for backward compat with
|
|
613
|
-
* apps that don't ship `_404.tsx`, not because SSR-rendering it is impossible.
|
|
614
636
|
*/
|
|
615
637
|
async function handle404(
|
|
616
638
|
server: import("vite").ViteDevServer,
|
|
617
639
|
_routesDir: string,
|
|
618
640
|
pathname: string,
|
|
619
641
|
res: import("http").ServerResponse,
|
|
642
|
+
root: string,
|
|
643
|
+
originalUrl: string,
|
|
620
644
|
): Promise<boolean> {
|
|
621
645
|
const mod = await server.ssrLoadModule(VIRTUAL_ROUTES_ID);
|
|
622
646
|
const routes = mod.routes as Array<{ path?: string; children?: unknown[] }>;
|
|
@@ -626,11 +650,32 @@ async function handle404(
|
|
|
626
650
|
return false; // Route matches — not a 404
|
|
627
651
|
}
|
|
628
652
|
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
// `
|
|
632
|
-
//
|
|
633
|
-
//
|
|
653
|
+
// Try the router-driven path: renderSsr → resolveRoute →
|
|
654
|
+
// findNotFoundFallback. Returns layout-wrapped 404 HTML + status 404 if
|
|
655
|
+
// any reachable `notFoundComponent` matches; returns null only when no
|
|
656
|
+
// `_404.tsx` / `_not-found.tsx` exists anywhere in the routes tree.
|
|
657
|
+
//
|
|
658
|
+
// Try/catch protects against ssrLoadModule failures (e.g. the user's
|
|
659
|
+
// `app.ts` has a syntax error in dev): we'd rather serve the bare
|
|
660
|
+
// fallback than crash the 404 handler. The caller's error path catches
|
|
661
|
+
// `next(err)` if renderSsr rejects in a way we can't recover from.
|
|
662
|
+
try {
|
|
663
|
+
const result = await renderSsr(server, root, originalUrl, pathname);
|
|
664
|
+
if (result !== null) {
|
|
665
|
+
res.statusCode = result.status;
|
|
666
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
667
|
+
res.setHeader("Content-Length", Buffer.byteLength(result.html));
|
|
668
|
+
res.end(result.html);
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
} catch {
|
|
672
|
+
// Fall through to bare HTML below.
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// No `notFoundComponent` reachable + renderSsr returned null — emit a
|
|
676
|
+
// minimal static page so the user gets SOMETHING. Apps that want
|
|
677
|
+
// branded 404s should add `_404.tsx` (or `_not-found.tsx`) to their
|
|
678
|
+
// routes tree.
|
|
634
679
|
const html = await render404Page(undefined);
|
|
635
680
|
|
|
636
681
|
res.statusCode = 404;
|