vinext 0.0.38 → 0.0.39
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/README.md +33 -20
- package/dist/build/nitro-route-rules.d.ts +50 -0
- package/dist/build/nitro-route-rules.js +81 -0
- package/dist/build/nitro-route-rules.js.map +1 -0
- package/dist/build/precompress.d.ts +17 -0
- package/dist/build/precompress.js +102 -0
- package/dist/build/precompress.js.map +1 -0
- package/dist/build/prerender.d.ts +27 -22
- package/dist/build/prerender.js +17 -17
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/report.d.ts +3 -4
- package/dist/build/report.js.map +1 -1
- package/dist/build/run-prerender.d.ts +3 -4
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/build/standalone.d.ts +32 -0
- package/dist/build/standalone.js +199 -0
- package/dist/build/standalone.js.map +1 -0
- package/dist/build/static-export.d.ts +17 -29
- package/dist/build/static-export.js.map +1 -1
- package/dist/check.d.ts +4 -4
- package/dist/check.js +1 -1
- package/dist/check.js.map +1 -1
- package/dist/cli.js +31 -4
- package/dist/cli.js.map +1 -1
- package/dist/client/instrumentation-client.d.ts +2 -2
- package/dist/client/instrumentation-client.js.map +1 -1
- package/dist/client/vinext-next-data.d.ts +5 -8
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/kv-cache-handler.d.ts +5 -3
- package/dist/cloudflare/kv-cache-handler.js +1 -1
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/cloudflare/tpr.d.ts +35 -27
- package/dist/cloudflare/tpr.js +36 -12
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/config-matchers.d.ts +2 -2
- package/dist/config/config-matchers.js +1 -1
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/dotenv.d.ts +4 -4
- package/dist/config/dotenv.js.map +1 -1
- package/dist/config/next-config.d.ts +40 -61
- package/dist/config/next-config.js +5 -4
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts +25 -41
- package/dist/deploy.js +1 -1
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +6 -10
- package/dist/entries/app-rsc-entry.js +4 -6
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +1 -3
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.d.ts +23 -33
- package/dist/index.js +165 -84
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +14 -26
- package/dist/init.js +8 -2
- package/dist/init.js.map +1 -1
- package/dist/plugins/client-reference-dedup.js.map +1 -1
- package/dist/plugins/fix-use-server-closure-collision.js.map +1 -1
- package/dist/plugins/fonts.d.ts +18 -1
- package/dist/plugins/fonts.js +107 -8
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/optimize-imports.d.ts +2 -2
- package/dist/plugins/optimize-imports.js +4 -4
- package/dist/plugins/optimize-imports.js.map +1 -1
- package/dist/plugins/server-externals-manifest.d.ts +27 -0
- package/dist/plugins/server-externals-manifest.js +76 -0
- package/dist/plugins/server-externals-manifest.js.map +1 -0
- package/dist/routing/app-router.d.ts +29 -55
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +2 -2
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.d.ts +6 -11
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-trie.d.ts +2 -2
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-browser-entry.js +270 -39
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-stream.d.ts +6 -6
- package/dist/server/app-browser-stream.js.map +1 -1
- package/dist/server/app-page-boundary-render.d.ts +8 -8
- package/dist/server/app-page-boundary-render.js +2 -2
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.d.ts +13 -11
- package/dist/server/app-page-boundary.js +1 -1
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +10 -10
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +10 -10
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +2 -2
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-page-render.d.ts +4 -4
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +12 -12
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.d.ts +18 -18
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +18 -18
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-cache.d.ts +2 -2
- package/dist/server/app-route-handler-cache.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +6 -6
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-policy.d.ts +8 -8
- package/dist/server/app-route-handler-policy.js.map +1 -1
- package/dist/server/app-route-handler-response.d.ts +6 -6
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-route-handler-runtime.d.ts +4 -4
- package/dist/server/app-route-handler-runtime.js.map +1 -1
- package/dist/server/app-ssr-entry.d.ts +4 -4
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +2 -2
- package/dist/server/app-ssr-stream.js +1 -3
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/dev-module-runner.d.ts +2 -2
- package/dist/server/dev-module-runner.js.map +1 -1
- package/dist/server/dev-server.js +5 -7
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/image-optimization.d.ts +7 -12
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/instrumentation.d.ts +8 -12
- package/dist/server/instrumentation.js +1 -1
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/isr-cache.d.ts +2 -2
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/metadata-routes.d.ts +14 -19
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware.d.ts +9 -17
- package/dist/server/middleware.js +1 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/pages-api-route.d.ts +6 -6
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-i18n.d.ts +4 -4
- package/dist/server/pages-i18n.js.map +1 -1
- package/dist/server/pages-node-compat.d.ts +10 -10
- package/dist/server/pages-node-compat.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +22 -22
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +8 -8
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.d.ts +20 -15
- package/dist/server/prod-server.js +170 -53
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/server/static-file-cache.d.ts +57 -0
- package/dist/server/static-file-cache.js +219 -0
- package/dist/server/static-file-cache.js.map +1 -0
- package/dist/shims/app.d.ts +2 -2
- package/dist/shims/cache-runtime.d.ts +6 -9
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +28 -31
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/config.d.ts +2 -2
- package/dist/shims/config.js.map +1 -1
- package/dist/shims/dynamic.d.ts +2 -2
- package/dist/shims/dynamic.js +5 -7
- package/dist/shims/dynamic.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +7 -7
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/error.d.ts +2 -2
- package/dist/shims/error.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +4 -4
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/font-google-base.d.ts +4 -4
- package/dist/shims/font-google-base.js.map +1 -1
- package/dist/shims/font-local.d.ts +6 -6
- package/dist/shims/font-local.js.map +1 -1
- package/dist/shims/form.d.ts +4 -8
- package/dist/shims/form.js +4 -6
- package/dist/shims/form.js.map +1 -1
- package/dist/shims/head-state.d.ts +2 -2
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/head.d.ts +2 -2
- package/dist/shims/head.js +18 -20
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +4 -4
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/i18n-context.d.ts +2 -2
- package/dist/shims/i18n-context.js.map +1 -1
- package/dist/shims/i18n-state.d.ts +2 -2
- package/dist/shims/i18n-state.js.map +1 -1
- package/dist/shims/image-config.d.ts +2 -2
- package/dist/shims/image-config.js.map +1 -1
- package/dist/shims/image.d.ts +5 -6
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/app-router-context.d.ts +6 -6
- package/dist/shims/internal/app-router-context.js.map +1 -1
- package/dist/shims/internal/utils.d.ts +2 -2
- package/dist/shims/internal/utils.js.map +1 -1
- package/dist/shims/layout-segment-context.d.ts +12 -5
- package/dist/shims/layout-segment-context.js +9 -4
- package/dist/shims/layout-segment-context.js.map +1 -1
- package/dist/shims/legacy-image.d.ts +5 -8
- package/dist/shims/legacy-image.js.map +1 -1
- package/dist/shims/link.d.ts +21 -31
- package/dist/shims/link.js +4 -58
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +23 -31
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation-state.d.ts +2 -2
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.d.ts +102 -17
- package/dist/shims/navigation.js +359 -113
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/request-context.d.ts +2 -2
- package/dist/shims/request-context.js.map +1 -1
- package/dist/shims/router-state.d.ts +4 -4
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts +28 -47
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.d.ts +16 -31
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.d.ts +10 -10
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/unified-request-context.d.ts +3 -5
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/shims/web-vitals.d.ts +2 -2
- package/dist/shims/web-vitals.js.map +1 -1
- package/dist/utils/lazy-chunks.d.ts +34 -0
- package/dist/utils/lazy-chunks.js +50 -0
- package/dist/utils/lazy-chunks.js.map +1 -0
- package/dist/utils/vinext-root.d.ts +24 -0
- package/dist/utils/vinext-root.js +31 -0
- package/dist/utils/vinext-root.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,16 @@ Options: `-p / --port <port>`, `-H / --hostname <host>`, `--turbopack` (accepted
|
|
|
74
74
|
|
|
75
75
|
`vinext init` options: `--port <port>` (default: 3001), `--skip-check`, `--force`.
|
|
76
76
|
|
|
77
|
+
If your `next.config.*` sets `output: "standalone"`, `vinext build` emits a self-hosting bundle at `dist/standalone/`. Start it with:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
node dist/standalone/server.js
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Environment variables: `PORT` (default `3000`), `HOST` (default `0.0.0.0`).
|
|
84
|
+
|
|
85
|
+
> **Note:** Next.js standalone uses `HOSTNAME` for the bind address, but vinext uses `HOST` to avoid collision with the system-set `HOSTNAME` variable on Linux. Update your deployment config accordingly.
|
|
86
|
+
|
|
77
87
|
### Starting a new vinext project
|
|
78
88
|
|
|
79
89
|
Run `npm create next-app@latest` to create a new Next.js project, and then follow these instructions to migrate it to vinext.
|
|
@@ -94,7 +104,7 @@ This will:
|
|
|
94
104
|
2. Install `vite`, `@vitejs/plugin-react`, and App Router-only deps (`@vitejs/plugin-rsc`, `react-server-dom-webpack`) as devDependencies
|
|
95
105
|
3. Rename CJS config files (e.g. `postcss.config.js` -> `.cjs`) to avoid ESM conflicts
|
|
96
106
|
4. Add `"type": "module"` to `package.json`
|
|
97
|
-
5. Add `dev:vinext` and `
|
|
107
|
+
5. Add `dev:vinext`, `build:vinext`, and `start:vinext` scripts to `package.json`
|
|
98
108
|
6. Generate a minimal `vite.config.ts`
|
|
99
109
|
|
|
100
110
|
The migration is non-destructive -- your existing Next.js setup continues to work alongside vinext. It does not modify `next.config`, `tsconfig.json`, or any source files, and it does not remove Next.js dependencies.
|
|
@@ -103,6 +113,8 @@ vinext targets Vite 8, which defaults to Rolldown, Oxc, Lightning CSS, and a new
|
|
|
103
113
|
|
|
104
114
|
```bash
|
|
105
115
|
npm run dev:vinext # Start the vinext dev server (port 3001)
|
|
116
|
+
npm run build:vinext # Build production output with vinext
|
|
117
|
+
npm run start:vinext # Start vinext production server
|
|
106
118
|
npm run dev # Still runs Next.js as before
|
|
107
119
|
```
|
|
108
120
|
|
|
@@ -458,25 +470,26 @@ Every `next/*` import is shimmed to a Vite-compatible implementation.
|
|
|
458
470
|
|
|
459
471
|
### Server features
|
|
460
472
|
|
|
461
|
-
| Feature
|
|
462
|
-
|
|
|
463
|
-
| SSR (Pages Router)
|
|
464
|
-
| SSR (App Router)
|
|
465
|
-
| `getStaticProps`
|
|
466
|
-
| `getStaticPaths`
|
|
467
|
-
| `getServerSideProps`
|
|
468
|
-
| ISR
|
|
469
|
-
| Server Actions (`"use server"`)
|
|
470
|
-
| React Server Components
|
|
471
|
-
| Streaming SSR
|
|
472
|
-
| Metadata API
|
|
473
|
-
| `generateStaticParams`
|
|
474
|
-
| Metadata file routes
|
|
475
|
-
| Static export (`output: 'export'`)
|
|
476
|
-
| `
|
|
477
|
-
| `
|
|
478
|
-
| `
|
|
479
|
-
|
|
|
473
|
+
| Feature | | Notes |
|
|
474
|
+
| ------------------------------------------ | --- | ------------------------------------------------------------------------------------------- |
|
|
475
|
+
| SSR (Pages Router) | ✅ | Streaming, `_app`/`_document`, `__NEXT_DATA__`, hydration |
|
|
476
|
+
| SSR (App Router) | ✅ | RSC pipeline, nested layouts, streaming, nav context for client components |
|
|
477
|
+
| `getStaticProps` | ✅ | Props, redirect, notFound, revalidate |
|
|
478
|
+
| `getStaticPaths` | ✅ | `fallback: false`, `true`, `"blocking"` |
|
|
479
|
+
| `getServerSideProps` | ✅ | Full context including locale |
|
|
480
|
+
| ISR | ✅ | Stale-while-revalidate, pluggable `CacheHandler`, background regeneration |
|
|
481
|
+
| Server Actions (`"use server"`) | ✅ | Action execution, FormData, re-render after mutation, `redirect()` in actions |
|
|
482
|
+
| React Server Components | ✅ | Via `@vitejs/plugin-rsc`. `"use client"` boundaries work correctly |
|
|
483
|
+
| Streaming SSR | ✅ | Both routers |
|
|
484
|
+
| Metadata API | ✅ | `metadata`, `generateMetadata`, `viewport`, `generateViewport`, title templates |
|
|
485
|
+
| `generateStaticParams` | ✅ | With `dynamicParams` enforcement |
|
|
486
|
+
| Metadata file routes | ✅ | sitemap.xml, robots.txt, manifest, favicon, OG images (static + dynamic) |
|
|
487
|
+
| Static export (`output: 'export'`) | ✅ | Generates static HTML/JSON for all routes |
|
|
488
|
+
| Standalone output (`output: 'standalone'`) | ✅ | Generates `dist/standalone` with `server.js`, build artifacts, and runtime deps |
|
|
489
|
+
| `connection()` | ✅ | Forces dynamic rendering |
|
|
490
|
+
| `"use cache"` directive | ✅ | File-level and function-level. `cacheLife()` profiles, `cacheTag()`, stale-while-revalidate |
|
|
491
|
+
| `instrumentation.ts` | ✅ | `register()` and `onRequestError()` callbacks |
|
|
492
|
+
| Route segment config | 🟡 | `revalidate`, `dynamic`, `dynamicParams`. `runtime` and `preferredRegion` are ignored |
|
|
480
493
|
|
|
481
494
|
### Configuration
|
|
482
495
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { RouteRow } from "./report.js";
|
|
2
|
+
|
|
3
|
+
//#region src/build/nitro-route-rules.d.ts
|
|
4
|
+
type NitroRouteRuleConfig = Record<string, unknown> & {
|
|
5
|
+
swr?: boolean | number;
|
|
6
|
+
cache?: unknown;
|
|
7
|
+
static?: boolean;
|
|
8
|
+
isr?: boolean | number;
|
|
9
|
+
prerender?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type NitroRouteRules = Record<string, {
|
|
12
|
+
swr: number;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.
|
|
16
|
+
*
|
|
17
|
+
* Note: this duplicates the filesystem scanning that `printBuildReport` also performs.
|
|
18
|
+
* The `nitro.setup` hook runs during Nitro initialization (before the build), while
|
|
19
|
+
* `printBuildReport` runs after the build, so sharing results is non-trivial. This is
|
|
20
|
+
* a future optimization target.
|
|
21
|
+
*
|
|
22
|
+
* Unlike `printBuildReport`, this path does not receive `prerenderResult`, so routes
|
|
23
|
+
* classified as `unknown` by static analysis (which `printBuildReport` might upgrade
|
|
24
|
+
* to `static` via speculative prerender) are skipped here.
|
|
25
|
+
*/
|
|
26
|
+
declare function collectNitroRouteRules(options: {
|
|
27
|
+
appDir?: string | null;
|
|
28
|
+
pagesDir?: string | null;
|
|
29
|
+
pageExtensions: string[];
|
|
30
|
+
}): Promise<NitroRouteRules>;
|
|
31
|
+
declare function generateNitroRouteRules(rows: RouteRow[]): NitroRouteRules;
|
|
32
|
+
/**
|
|
33
|
+
* Converts vinext's internal `:param` route syntax to Nitro's rou3
|
|
34
|
+
* pattern format. Nitro uses `rou3` for routeRules matching, which
|
|
35
|
+
* supports `*` (single-segment) and `**` (multi-segment) wildcards.
|
|
36
|
+
*
|
|
37
|
+
* /blog/:slug -> /blog/* (single segment)
|
|
38
|
+
* /docs/:slug+ -> /docs/** (one or more segments — catch-all)
|
|
39
|
+
* /docs/:slug* -> /docs/** (zero or more segments — optional catch-all)
|
|
40
|
+
* /about -> /about (unchanged)
|
|
41
|
+
* /:a/:b produces `/*`/`/*` (consecutive single-segment params)
|
|
42
|
+
*/
|
|
43
|
+
declare function convertToNitroPattern(pattern: string): string;
|
|
44
|
+
declare function mergeNitroRouteRules(existingRouteRules: Record<string, NitroRouteRuleConfig> | undefined, generatedRouteRules: NitroRouteRules): {
|
|
45
|
+
routeRules: Record<string, NitroRouteRuleConfig>;
|
|
46
|
+
skippedRoutes: string[];
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { NitroRouteRuleConfig, NitroRouteRules, collectNitroRouteRules, convertToNitroPattern, generateNitroRouteRules, mergeNitroRouteRules };
|
|
50
|
+
//# sourceMappingURL=nitro-route-rules.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { apiRouter, pagesRouter } from "../routing/pages-router.js";
|
|
2
|
+
import { appRouter } from "../routing/app-router.js";
|
|
3
|
+
import { buildReportRows } from "./report.js";
|
|
4
|
+
//#region src/build/nitro-route-rules.ts
|
|
5
|
+
/**
|
|
6
|
+
* Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.
|
|
7
|
+
*
|
|
8
|
+
* Note: this duplicates the filesystem scanning that `printBuildReport` also performs.
|
|
9
|
+
* The `nitro.setup` hook runs during Nitro initialization (before the build), while
|
|
10
|
+
* `printBuildReport` runs after the build, so sharing results is non-trivial. This is
|
|
11
|
+
* a future optimization target.
|
|
12
|
+
*
|
|
13
|
+
* Unlike `printBuildReport`, this path does not receive `prerenderResult`, so routes
|
|
14
|
+
* classified as `unknown` by static analysis (which `printBuildReport` might upgrade
|
|
15
|
+
* to `static` via speculative prerender) are skipped here.
|
|
16
|
+
*/
|
|
17
|
+
async function collectNitroRouteRules(options) {
|
|
18
|
+
const { appDir, pageExtensions, pagesDir } = options;
|
|
19
|
+
let appRoutes = [];
|
|
20
|
+
let pageRoutes = [];
|
|
21
|
+
let apiRoutes = [];
|
|
22
|
+
if (appDir) appRoutes = await appRouter(appDir, pageExtensions);
|
|
23
|
+
if (pagesDir) {
|
|
24
|
+
const [pages, apis] = await Promise.all([pagesRouter(pagesDir, pageExtensions), apiRouter(pagesDir, pageExtensions)]);
|
|
25
|
+
pageRoutes = pages;
|
|
26
|
+
apiRoutes = apis;
|
|
27
|
+
}
|
|
28
|
+
return generateNitroRouteRules(buildReportRows({
|
|
29
|
+
appRoutes,
|
|
30
|
+
pageRoutes,
|
|
31
|
+
apiRoutes
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
function generateNitroRouteRules(rows) {
|
|
35
|
+
const rules = {};
|
|
36
|
+
for (const row of rows) if (row.type === "isr" && typeof row.revalidate === "number" && Number.isFinite(row.revalidate) && row.revalidate > 0) rules[convertToNitroPattern(row.pattern)] = { swr: row.revalidate };
|
|
37
|
+
return rules;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Converts vinext's internal `:param` route syntax to Nitro's rou3
|
|
41
|
+
* pattern format. Nitro uses `rou3` for routeRules matching, which
|
|
42
|
+
* supports `*` (single-segment) and `**` (multi-segment) wildcards.
|
|
43
|
+
*
|
|
44
|
+
* /blog/:slug -> /blog/* (single segment)
|
|
45
|
+
* /docs/:slug+ -> /docs/** (one or more segments — catch-all)
|
|
46
|
+
* /docs/:slug* -> /docs/** (zero or more segments — optional catch-all)
|
|
47
|
+
* /about -> /about (unchanged)
|
|
48
|
+
* /:a/:b produces `/*`/`/*` (consecutive single-segment params)
|
|
49
|
+
*/
|
|
50
|
+
function convertToNitroPattern(pattern) {
|
|
51
|
+
return pattern.split("/").map((segment) => {
|
|
52
|
+
if (segment.startsWith(":")) return segment.endsWith("+") || segment.endsWith("*") ? "**" : "*";
|
|
53
|
+
return segment;
|
|
54
|
+
}).join("/");
|
|
55
|
+
}
|
|
56
|
+
function mergeNitroRouteRules(existingRouteRules, generatedRouteRules) {
|
|
57
|
+
const routeRules = { ...existingRouteRules };
|
|
58
|
+
const skippedRoutes = [];
|
|
59
|
+
for (const [route, generatedRule] of Object.entries(generatedRouteRules)) {
|
|
60
|
+
const existingRule = routeRules[route];
|
|
61
|
+
if (existingRule && hasUserDefinedCacheRule(existingRule)) {
|
|
62
|
+
skippedRoutes.push(route);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
routeRules[route] = {
|
|
66
|
+
...existingRule,
|
|
67
|
+
...generatedRule
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
routeRules,
|
|
72
|
+
skippedRoutes
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function hasUserDefinedCacheRule(rule) {
|
|
76
|
+
return rule.swr !== void 0 || rule.cache !== void 0 || rule.static !== void 0 || rule.isr !== void 0 || rule.prerender !== void 0;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { collectNitroRouteRules, convertToNitroPattern, generateNitroRouteRules, mergeNitroRouteRules };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=nitro-route-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nitro-route-rules.js","names":[],"sources":["../../src/build/nitro-route-rules.ts"],"sourcesContent":["import { appRouter, type AppRoute } from \"../routing/app-router.js\";\nimport { apiRouter, pagesRouter, type Route } from \"../routing/pages-router.js\";\nimport { buildReportRows, type RouteRow } from \"./report.js\";\n\n// Mirrors Nitro's NitroRouteConfig — hand-rolled because nitropack is not a direct dependency.\nexport type NitroRouteRuleConfig = Record<string, unknown> & {\n swr?: boolean | number;\n cache?: unknown;\n static?: boolean;\n isr?: boolean | number;\n prerender?: boolean;\n};\n\nexport type NitroRouteRules = Record<string, { swr: number }>;\n\n/**\n * Scans the filesystem for route files and generates Nitro `routeRules` for ISR routes.\n *\n * Note: this duplicates the filesystem scanning that `printBuildReport` also performs.\n * The `nitro.setup` hook runs during Nitro initialization (before the build), while\n * `printBuildReport` runs after the build, so sharing results is non-trivial. This is\n * a future optimization target.\n *\n * Unlike `printBuildReport`, this path does not receive `prerenderResult`, so routes\n * classified as `unknown` by static analysis (which `printBuildReport` might upgrade\n * to `static` via speculative prerender) are skipped here.\n */\nexport async function collectNitroRouteRules(options: {\n appDir?: string | null;\n pagesDir?: string | null;\n pageExtensions: string[];\n}): Promise<NitroRouteRules> {\n const { appDir, pageExtensions, pagesDir } = options;\n\n let appRoutes: AppRoute[] = [];\n let pageRoutes: Route[] = [];\n let apiRoutes: Route[] = [];\n\n if (appDir) {\n appRoutes = await appRouter(appDir, pageExtensions);\n }\n\n if (pagesDir) {\n const [pages, apis] = await Promise.all([\n pagesRouter(pagesDir, pageExtensions),\n apiRouter(pagesDir, pageExtensions),\n ]);\n pageRoutes = pages;\n apiRoutes = apis;\n }\n\n return generateNitroRouteRules(buildReportRows({ appRoutes, pageRoutes, apiRoutes }));\n}\n\nexport function generateNitroRouteRules(rows: RouteRow[]): NitroRouteRules {\n const rules: NitroRouteRules = {};\n\n for (const row of rows) {\n if (\n row.type === \"isr\" &&\n typeof row.revalidate === \"number\" &&\n Number.isFinite(row.revalidate) &&\n row.revalidate > 0\n ) {\n rules[convertToNitroPattern(row.pattern)] = { swr: row.revalidate };\n }\n }\n\n return rules;\n}\n\n/**\n * Converts vinext's internal `:param` route syntax to Nitro's rou3\n * pattern format. Nitro uses `rou3` for routeRules matching, which\n * supports `*` (single-segment) and `**` (multi-segment) wildcards.\n *\n * /blog/:slug -> /blog/* (single segment)\n * /docs/:slug+ -> /docs/** (one or more segments — catch-all)\n * /docs/:slug* -> /docs/** (zero or more segments — optional catch-all)\n * /about -> /about (unchanged)\n * /:a/:b produces `/*`/`/*` (consecutive single-segment params)\n */\nexport function convertToNitroPattern(pattern: string): string {\n return pattern\n .split(\"/\")\n .map((segment) => {\n if (segment.startsWith(\":\")) {\n // Catch-all (:param+) and optional catch-all (:param*) match multiple segments → **\n // Single dynamic param (:param) matches one segment → *\n return segment.endsWith(\"+\") || segment.endsWith(\"*\") ? \"**\" : \"*\";\n }\n return segment;\n })\n .join(\"/\");\n}\n\nexport function mergeNitroRouteRules(\n existingRouteRules: Record<string, NitroRouteRuleConfig> | undefined,\n generatedRouteRules: NitroRouteRules,\n): {\n routeRules: Record<string, NitroRouteRuleConfig>;\n skippedRoutes: string[];\n} {\n const routeRules = { ...existingRouteRules };\n const skippedRoutes: string[] = [];\n\n for (const [route, generatedRule] of Object.entries(generatedRouteRules)) {\n const existingRule = routeRules[route];\n\n if (existingRule && hasUserDefinedCacheRule(existingRule)) {\n skippedRoutes.push(route);\n continue;\n }\n\n routeRules[route] = {\n ...existingRule,\n ...generatedRule,\n };\n }\n\n return { routeRules, skippedRoutes };\n}\n\nfunction hasUserDefinedCacheRule(rule: NitroRouteRuleConfig): boolean {\n return (\n rule.swr !== undefined ||\n rule.cache !== undefined ||\n rule.static !== undefined ||\n rule.isr !== undefined ||\n rule.prerender !== undefined\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2BA,eAAsB,uBAAuB,SAIhB;CAC3B,MAAM,EAAE,QAAQ,gBAAgB,aAAa;CAE7C,IAAI,YAAwB,EAAE;CAC9B,IAAI,aAAsB,EAAE;CAC5B,IAAI,YAAqB,EAAE;AAE3B,KAAI,OACF,aAAY,MAAM,UAAU,QAAQ,eAAe;AAGrD,KAAI,UAAU;EACZ,MAAM,CAAC,OAAO,QAAQ,MAAM,QAAQ,IAAI,CACtC,YAAY,UAAU,eAAe,EACrC,UAAU,UAAU,eAAe,CACpC,CAAC;AACF,eAAa;AACb,cAAY;;AAGd,QAAO,wBAAwB,gBAAgB;EAAE;EAAW;EAAY;EAAW,CAAC,CAAC;;AAGvF,SAAgB,wBAAwB,MAAmC;CACzE,MAAM,QAAyB,EAAE;AAEjC,MAAK,MAAM,OAAO,KAChB,KACE,IAAI,SAAS,SACb,OAAO,IAAI,eAAe,YAC1B,OAAO,SAAS,IAAI,WAAW,IAC/B,IAAI,aAAa,EAEjB,OAAM,sBAAsB,IAAI,QAAQ,IAAI,EAAE,KAAK,IAAI,YAAY;AAIvE,QAAO;;;;;;;;;;;;;AAcT,SAAgB,sBAAsB,SAAyB;AAC7D,QAAO,QACJ,MAAM,IAAI,CACV,KAAK,YAAY;AAChB,MAAI,QAAQ,WAAW,IAAI,CAGzB,QAAO,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,IAAI,GAAG,OAAO;AAEjE,SAAO;GACP,CACD,KAAK,IAAI;;AAGd,SAAgB,qBACd,oBACA,qBAIA;CACA,MAAM,aAAa,EAAE,GAAG,oBAAoB;CAC5C,MAAM,gBAA0B,EAAE;AAElC,MAAK,MAAM,CAAC,OAAO,kBAAkB,OAAO,QAAQ,oBAAoB,EAAE;EACxE,MAAM,eAAe,WAAW;AAEhC,MAAI,gBAAgB,wBAAwB,aAAa,EAAE;AACzD,iBAAc,KAAK,MAAM;AACzB;;AAGF,aAAW,SAAS;GAClB,GAAG;GACH,GAAG;GACJ;;AAGH,QAAO;EAAE;EAAY;EAAe;;AAGtC,SAAS,wBAAwB,MAAqC;AACpE,QACE,KAAK,QAAQ,KAAA,KACb,KAAK,UAAU,KAAA,KACf,KAAK,WAAW,KAAA,KAChB,KAAK,QAAQ,KAAA,KACb,KAAK,cAAc,KAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/build/precompress.d.ts
|
|
2
|
+
type PrecompressResult = {
|
|
3
|
+
filesCompressed: number;
|
|
4
|
+
totalOriginalBytes: number; /** Sum of brotli-compressed sizes (used for compression ratio reporting). */
|
|
5
|
+
totalBrotliBytes: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Precompress all compressible hashed assets under `clientDir/assets/`.
|
|
9
|
+
*
|
|
10
|
+
* Writes `.br`, `.gz`, and `.zst` files alongside each original.
|
|
11
|
+
* Safe to re-run — overwrites existing compressed variants with identical
|
|
12
|
+
* output, and never compresses `.br`, `.gz`, or `.zst` files themselves.
|
|
13
|
+
*/
|
|
14
|
+
declare function precompressAssets(clientDir: string, onProgress?: (completed: number, total: number, file: string) => void): Promise<PrecompressResult>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { PrecompressResult, precompressAssets };
|
|
17
|
+
//# sourceMappingURL=precompress.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import zlib from "node:zlib";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
//#region src/build/precompress.ts
|
|
7
|
+
/**
|
|
8
|
+
* Build-time precompression for hashed static assets.
|
|
9
|
+
*
|
|
10
|
+
* Generates .br (brotli q5), .gz (gzip l8), and .zst (zstd l8) files
|
|
11
|
+
* alongside compressible assets in dist/client/assets/. Served directly by
|
|
12
|
+
* the production server — no per-request compression needed for immutable
|
|
13
|
+
* build output.
|
|
14
|
+
*
|
|
15
|
+
* Only targets assets/ (hashed, immutable) — public directory files use
|
|
16
|
+
* on-the-fly compression since they may change between deploys.
|
|
17
|
+
*/
|
|
18
|
+
const brotliCompress = promisify(zlib.brotliCompress);
|
|
19
|
+
const gzip = promisify(zlib.gzip);
|
|
20
|
+
const zstdCompress = typeof zlib.zstdCompress === "function" ? promisify(zlib.zstdCompress) : null;
|
|
21
|
+
/** File extensions worth compressing (text-based, not already compressed). */
|
|
22
|
+
const COMPRESSIBLE_EXTENSIONS = new Set([
|
|
23
|
+
".js",
|
|
24
|
+
".mjs",
|
|
25
|
+
".css",
|
|
26
|
+
".html",
|
|
27
|
+
".json",
|
|
28
|
+
".xml",
|
|
29
|
+
".svg",
|
|
30
|
+
".txt",
|
|
31
|
+
".map",
|
|
32
|
+
".wasm"
|
|
33
|
+
]);
|
|
34
|
+
/** Below this size, compression overhead exceeds savings. */
|
|
35
|
+
const MIN_SIZE = 1024;
|
|
36
|
+
/**
|
|
37
|
+
* Past ~8 parallel files, mixed-size asset sets spend more time queueing zlib
|
|
38
|
+
* work than making forward progress. Keep the batch size bounded even on
|
|
39
|
+
* higher-core machines.
|
|
40
|
+
*/
|
|
41
|
+
const CONCURRENCY = Math.min(os.availableParallelism(), 8);
|
|
42
|
+
/**
|
|
43
|
+
* Walk a directory recursively, yielding relative paths for regular files.
|
|
44
|
+
*/
|
|
45
|
+
async function* walkFiles(dir, base = dir) {
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const fullPath = path.join(dir, entry.name);
|
|
54
|
+
if (entry.isDirectory()) yield* walkFiles(fullPath, base);
|
|
55
|
+
else if (entry.isFile()) yield path.relative(base, fullPath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Precompress all compressible hashed assets under `clientDir/assets/`.
|
|
60
|
+
*
|
|
61
|
+
* Writes `.br`, `.gz`, and `.zst` files alongside each original.
|
|
62
|
+
* Safe to re-run — overwrites existing compressed variants with identical
|
|
63
|
+
* output, and never compresses `.br`, `.gz`, or `.zst` files themselves.
|
|
64
|
+
*/
|
|
65
|
+
async function precompressAssets(clientDir, onProgress) {
|
|
66
|
+
const assetsDir = path.join(clientDir, "assets");
|
|
67
|
+
const result = {
|
|
68
|
+
filesCompressed: 0,
|
|
69
|
+
totalOriginalBytes: 0,
|
|
70
|
+
totalBrotliBytes: 0
|
|
71
|
+
};
|
|
72
|
+
const filePaths = [];
|
|
73
|
+
for await (const relativePath of walkFiles(assetsDir)) {
|
|
74
|
+
const ext = path.extname(relativePath).toLowerCase();
|
|
75
|
+
if (!COMPRESSIBLE_EXTENSIONS.has(ext)) continue;
|
|
76
|
+
filePaths.push(path.join(assetsDir, relativePath));
|
|
77
|
+
}
|
|
78
|
+
let processed = 0;
|
|
79
|
+
for (let i = 0; i < filePaths.length; i += CONCURRENCY) {
|
|
80
|
+
const chunk = filePaths.slice(i, i + CONCURRENCY);
|
|
81
|
+
await Promise.all(chunk.map(async (fullPath) => {
|
|
82
|
+
const content = await fsp.readFile(fullPath);
|
|
83
|
+
if (content.length < MIN_SIZE) return;
|
|
84
|
+
const compressions = [brotliCompress(content, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 } }), gzip(content, { level: 8 })];
|
|
85
|
+
if (zstdCompress) compressions.push(zstdCompress(content, { params: { [zlib.constants.ZSTD_c_compressionLevel]: 8 } }));
|
|
86
|
+
const [brContent, gzContent, zstdContent] = await Promise.all(compressions);
|
|
87
|
+
const writes = [fsp.writeFile(fullPath + ".br", brContent), fsp.writeFile(fullPath + ".gz", gzContent)];
|
|
88
|
+
if (zstdContent) writes.push(fsp.writeFile(fullPath + ".zst", zstdContent));
|
|
89
|
+
await Promise.all(writes);
|
|
90
|
+
result.filesCompressed++;
|
|
91
|
+
result.totalOriginalBytes += content.length;
|
|
92
|
+
result.totalBrotliBytes += brContent.length;
|
|
93
|
+
}));
|
|
94
|
+
processed += chunk.length;
|
|
95
|
+
onProgress?.(processed, filePaths.length, path.basename(chunk[chunk.length - 1]));
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { precompressAssets };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=precompress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precompress.js","names":[],"sources":["../../src/build/precompress.ts"],"sourcesContent":["/**\n * Build-time precompression for hashed static assets.\n *\n * Generates .br (brotli q5), .gz (gzip l8), and .zst (zstd l8) files\n * alongside compressible assets in dist/client/assets/. Served directly by\n * the production server — no per-request compression needed for immutable\n * build output.\n *\n * Only targets assets/ (hashed, immutable) — public directory files use\n * on-the-fly compression since they may change between deploys.\n */\nimport fsp from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport zlib from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst brotliCompress = promisify(zlib.brotliCompress);\nconst gzip = promisify(zlib.gzip);\nconst zstdCompress = typeof zlib.zstdCompress === \"function\" ? promisify(zlib.zstdCompress) : null;\n\n/** File extensions worth compressing (text-based, not already compressed). */\nconst COMPRESSIBLE_EXTENSIONS = new Set([\n \".js\",\n \".mjs\",\n \".css\",\n \".html\",\n \".json\",\n \".xml\",\n \".svg\",\n \".txt\",\n \".map\",\n \".wasm\",\n]);\n\n/** Below this size, compression overhead exceeds savings. */\nconst MIN_SIZE = 1024;\n\n/**\n * Past ~8 parallel files, mixed-size asset sets spend more time queueing zlib\n * work than making forward progress. Keep the batch size bounded even on\n * higher-core machines.\n */\nconst CONCURRENCY = Math.min(os.availableParallelism(), 8);\n\nexport type PrecompressResult = {\n filesCompressed: number;\n totalOriginalBytes: number;\n /** Sum of brotli-compressed sizes (used for compression ratio reporting). */\n totalBrotliBytes: number;\n};\n\n/**\n * Walk a directory recursively, yielding relative paths for regular files.\n */\nasync function* walkFiles(dir: string, base: string = dir): AsyncGenerator<string> {\n let entries;\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // directory doesn't exist\n }\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkFiles(fullPath, base);\n } else if (entry.isFile()) {\n yield path.relative(base, fullPath);\n }\n }\n}\n\n/**\n * Precompress all compressible hashed assets under `clientDir/assets/`.\n *\n * Writes `.br`, `.gz`, and `.zst` files alongside each original.\n * Safe to re-run — overwrites existing compressed variants with identical\n * output, and never compresses `.br`, `.gz`, or `.zst` files themselves.\n */\nexport async function precompressAssets(\n clientDir: string,\n onProgress?: (completed: number, total: number, file: string) => void,\n): Promise<PrecompressResult> {\n const assetsDir = path.join(clientDir, \"assets\");\n const result: PrecompressResult = {\n filesCompressed: 0,\n totalOriginalBytes: 0,\n totalBrotliBytes: 0,\n };\n\n // Collect compressible file paths, then read + compress in bounded chunks\n // to keep peak memory at O(CONCURRENCY * max_file_size) instead of\n // O(total_assets).\n const filePaths: string[] = [];\n\n for await (const relativePath of walkFiles(assetsDir)) {\n const ext = path.extname(relativePath).toLowerCase();\n\n if (!COMPRESSIBLE_EXTENSIONS.has(ext)) continue;\n // .br/.gz/.zst are intentionally absent from COMPRESSIBLE_EXTENSIONS, so\n // precompressed variants generated by a previous run are never re-compressed.\n\n filePaths.push(path.join(assetsDir, relativePath));\n }\n\n let processed = 0;\n for (let i = 0; i < filePaths.length; i += CONCURRENCY) {\n const chunk = filePaths.slice(i, i + CONCURRENCY);\n await Promise.all(\n chunk.map(async (fullPath) => {\n const content = await fsp.readFile(fullPath);\n // readFile already done before this check — stat()-first would save\n // the read for tiny files but costs an extra syscall per file;\n // sub-1KB hashed assets are rare enough that read-first is cheaper.\n if (content.length < MIN_SIZE) return;\n\n // Compress all variants concurrently within each file\n const compressions: Promise<Buffer>[] = [\n brotliCompress(content, {\n params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 },\n }),\n gzip(content, { level: 8 }),\n ];\n if (zstdCompress) {\n compressions.push(\n zstdCompress(content, {\n params: { [zlib.constants.ZSTD_c_compressionLevel]: 8 },\n }),\n );\n }\n\n const results = await Promise.all(compressions);\n const [brContent, gzContent, zstdContent] = results;\n\n const writes = [\n fsp.writeFile(fullPath + \".br\", brContent),\n fsp.writeFile(fullPath + \".gz\", gzContent),\n ];\n if (zstdContent) {\n writes.push(fsp.writeFile(fullPath + \".zst\", zstdContent));\n }\n await Promise.all(writes);\n\n // Increment counters only after all writes succeed, so partial\n // failures (e.g. ENOSPC mid-write) don't inflate the reported totals.\n result.filesCompressed++;\n result.totalOriginalBytes += content.length;\n result.totalBrotliBytes += brContent.length;\n }),\n );\n // Report progress once per chunk to avoid non-deterministic ordering\n // within Promise.all (smaller files complete before larger ones).\n // Progress tracks all files (including skipped ones below MIN_SIZE),\n // which differs from filesCompressed (only files actually compressed).\n processed += chunk.length;\n onProgress?.(processed, filePaths.length, path.basename(chunk[chunk.length - 1]));\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAM,iBAAiB,UAAU,KAAK,eAAe;AACrD,MAAM,OAAO,UAAU,KAAK,KAAK;AACjC,MAAM,eAAe,OAAO,KAAK,iBAAiB,aAAa,UAAU,KAAK,aAAa,GAAG;;AAG9F,MAAM,0BAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,MAAM,WAAW;;;;;;AAOjB,MAAM,cAAc,KAAK,IAAI,GAAG,sBAAsB,EAAE,EAAE;;;;AAY1D,gBAAgB,UAAU,KAAa,OAAe,KAA6B;CACjF,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,IAAI,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;SACnD;AACN;;AAEF,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,KAAK,MAAM,KAAK;AAC3C,MAAI,MAAM,aAAa,CACrB,QAAO,UAAU,UAAU,KAAK;WACvB,MAAM,QAAQ,CACvB,OAAM,KAAK,SAAS,MAAM,SAAS;;;;;;;;;;AAYzC,eAAsB,kBACpB,WACA,YAC4B;CAC5B,MAAM,YAAY,KAAK,KAAK,WAAW,SAAS;CAChD,MAAM,SAA4B;EAChC,iBAAiB;EACjB,oBAAoB;EACpB,kBAAkB;EACnB;CAKD,MAAM,YAAsB,EAAE;AAE9B,YAAW,MAAM,gBAAgB,UAAU,UAAU,EAAE;EACrD,MAAM,MAAM,KAAK,QAAQ,aAAa,CAAC,aAAa;AAEpD,MAAI,CAAC,wBAAwB,IAAI,IAAI,CAAE;AAIvC,YAAU,KAAK,KAAK,KAAK,WAAW,aAAa,CAAC;;CAGpD,IAAI,YAAY;AAChB,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,aAAa;EACtD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,YAAY;AACjD,QAAM,QAAQ,IACZ,MAAM,IAAI,OAAO,aAAa;GAC5B,MAAM,UAAU,MAAM,IAAI,SAAS,SAAS;AAI5C,OAAI,QAAQ,SAAS,SAAU;GAG/B,MAAM,eAAkC,CACtC,eAAe,SAAS,EACtB,QAAQ,GAAG,KAAK,UAAU,uBAAuB,GAAG,EACrD,CAAC,EACF,KAAK,SAAS,EAAE,OAAO,GAAG,CAAC,CAC5B;AACD,OAAI,aACF,cAAa,KACX,aAAa,SAAS,EACpB,QAAQ,GAAG,KAAK,UAAU,0BAA0B,GAAG,EACxD,CAAC,CACH;GAIH,MAAM,CAAC,WAAW,WAAW,eADb,MAAM,QAAQ,IAAI,aAAa;GAG/C,MAAM,SAAS,CACb,IAAI,UAAU,WAAW,OAAO,UAAU,EAC1C,IAAI,UAAU,WAAW,OAAO,UAAU,CAC3C;AACD,OAAI,YACF,QAAO,KAAK,IAAI,UAAU,WAAW,QAAQ,YAAY,CAAC;AAE5D,SAAM,QAAQ,IAAI,OAAO;AAIzB,UAAO;AACP,UAAO,sBAAsB,QAAQ;AACrC,UAAO,oBAAoB,UAAU;IACrC,CACH;AAKD,eAAa,MAAM;AACnB,eAAa,WAAW,UAAU,QAAQ,KAAK,SAAS,MAAM,MAAM,SAAS,GAAG,CAAC;;AAGnF,QAAO"}
|
|
@@ -5,10 +5,9 @@ import { readPrerenderSecret } from "./server-manifest.js";
|
|
|
5
5
|
import { Server } from "node:http";
|
|
6
6
|
|
|
7
7
|
//#region src/build/prerender.d.ts
|
|
8
|
-
|
|
9
|
-
/** One entry per route (including skipped/error routes). */
|
|
10
|
-
|
|
11
|
-
}
|
|
8
|
+
type PrerenderResult = {
|
|
9
|
+
/** One entry per route (including skipped/error routes). */routes: PrerenderRouteResult[];
|
|
10
|
+
};
|
|
12
11
|
type PrerenderRouteResult = {
|
|
13
12
|
/** The route's file-system pattern, e.g. `/blog/:slug`. */route: string;
|
|
14
13
|
status: "rendered";
|
|
@@ -37,13 +36,12 @@ type PrerenderProgressCallback = (update: {
|
|
|
37
36
|
route: string; /** Its final status. */
|
|
38
37
|
status: PrerenderRouteResult["status"];
|
|
39
38
|
}) => void;
|
|
40
|
-
|
|
39
|
+
type PrerenderOptions = {
|
|
41
40
|
/**
|
|
42
41
|
* 'default' — prerender static/ISR routes; skip SSR routes
|
|
43
42
|
* 'export' — same as default but SSR routes are errors
|
|
44
43
|
*/
|
|
45
|
-
mode: "default" | "export";
|
|
46
|
-
/** Output directory for generated HTML/RSC files. */
|
|
44
|
+
mode: "default" | "export"; /** Output directory for generated HTML/RSC files. */
|
|
47
45
|
outDir: string;
|
|
48
46
|
/**
|
|
49
47
|
* Directory where `vinext-prerender.json` is written.
|
|
@@ -51,8 +49,7 @@ interface PrerenderOptions {
|
|
|
51
49
|
* Set this when the manifest should land in a different location than the
|
|
52
50
|
* generated HTML/RSC files (e.g. `dist/server/` while HTML goes to `dist/server/prerendered-routes/`).
|
|
53
51
|
*/
|
|
54
|
-
manifestDir?: string;
|
|
55
|
-
/** Resolved next.config.js. */
|
|
52
|
+
manifestDir?: string; /** Resolved next.config.js. */
|
|
56
53
|
config: ResolvedNextConfig;
|
|
57
54
|
/**
|
|
58
55
|
* Maximum number of routes rendered in parallel.
|
|
@@ -70,13 +67,10 @@ interface PrerenderOptions {
|
|
|
70
67
|
* multiple phases and write a single unified manifest itself.
|
|
71
68
|
*/
|
|
72
69
|
skipManifest?: boolean;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Discovered page routes (non-API). */
|
|
76
|
-
|
|
77
|
-
/** Discovered API routes. */
|
|
78
|
-
apiRoutes: Route[];
|
|
79
|
-
/** Pages directory path. */
|
|
70
|
+
};
|
|
71
|
+
type PrerenderPagesOptions = {
|
|
72
|
+
/** Discovered page routes (non-API). */routes: Route[]; /** Discovered API routes. */
|
|
73
|
+
apiRoutes: Route[]; /** Pages directory path. */
|
|
80
74
|
pagesDir: string;
|
|
81
75
|
/**
|
|
82
76
|
* Absolute path to the pre-built Pages Router server bundle
|
|
@@ -86,15 +80,14 @@ interface PrerenderPagesOptions extends PrerenderOptions {
|
|
|
86
80
|
* `runPrerender` passes a shared `_prodServer` instead.
|
|
87
81
|
*/
|
|
88
82
|
pagesBundlePath?: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Discovered app routes. */
|
|
92
|
-
routes: AppRoute[];
|
|
83
|
+
} & PrerenderOptions;
|
|
84
|
+
type PrerenderAppOptions = {
|
|
85
|
+
/** Discovered app routes. */routes: AppRoute[];
|
|
93
86
|
/**
|
|
94
87
|
* Absolute path to the pre-built RSC handler bundle (e.g. `dist/server/index.js`).
|
|
95
88
|
*/
|
|
96
89
|
rscBundlePath: string;
|
|
97
|
-
}
|
|
90
|
+
} & PrerenderOptions;
|
|
98
91
|
type PrerenderPagesOptionsInternal = PrerenderPagesOptions & {
|
|
99
92
|
_prodServer?: {
|
|
100
93
|
server: Server;
|
|
@@ -124,6 +117,18 @@ declare function buildUrlFromParams(pattern: string, params: Record<string, stri
|
|
|
124
117
|
* Respects trailingSlash config.
|
|
125
118
|
*/
|
|
126
119
|
declare function getOutputPath(urlPath: string, trailingSlash: boolean): string;
|
|
120
|
+
/** Map of route patterns to generateStaticParams functions (or null/undefined). */
|
|
121
|
+
type StaticParamsMap = Record<string, ((opts: {
|
|
122
|
+
params: Record<string, string | string[]>;
|
|
123
|
+
}) => Promise<Record<string, string | string[]>[]>) | null | undefined>;
|
|
124
|
+
/**
|
|
125
|
+
* Resolve parent dynamic segment params for a route.
|
|
126
|
+
* Handles top-down generateStaticParams resolution for nested dynamic routes.
|
|
127
|
+
*
|
|
128
|
+
* Uses the `staticParamsMap` (pattern → generateStaticParams) exported from
|
|
129
|
+
* the production bundle.
|
|
130
|
+
*/
|
|
131
|
+
declare function resolveParentParams(childRoute: AppRoute, routeIndex: ReadonlyMap<string, AppRoute>, staticParamsMap: StaticParamsMap): Promise<Record<string, string | string[]>[]>;
|
|
127
132
|
/**
|
|
128
133
|
* Run the prerender phase for Pages Router.
|
|
129
134
|
*
|
|
@@ -189,5 +194,5 @@ declare function writePrerenderIndex(routes: PrerenderRouteResult[], outDir: str
|
|
|
189
194
|
trailingSlash?: boolean;
|
|
190
195
|
}): void;
|
|
191
196
|
//#endregion
|
|
192
|
-
export { PrerenderAppOptions, PrerenderOptions, PrerenderPagesOptions, PrerenderProgressCallback, PrerenderResult, PrerenderRouteResult, buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, writePrerenderIndex };
|
|
197
|
+
export { PrerenderAppOptions, PrerenderOptions, PrerenderPagesOptions, PrerenderProgressCallback, PrerenderResult, PrerenderRouteResult, StaticParamsMap, buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
|
|
193
198
|
//# sourceMappingURL=prerender.d.ts.map
|
package/dist/build/prerender.js
CHANGED
|
@@ -86,31 +86,30 @@ function getOutputPath(urlPath, trailingSlash) {
|
|
|
86
86
|
* Uses the `staticParamsMap` (pattern → generateStaticParams) exported from
|
|
87
87
|
* the production bundle.
|
|
88
88
|
*/
|
|
89
|
-
async function resolveParentParams(childRoute,
|
|
90
|
-
const patternParts = childRoute
|
|
89
|
+
async function resolveParentParams(childRoute, routeIndex, staticParamsMap) {
|
|
90
|
+
const { patternParts } = childRoute;
|
|
91
|
+
let lastDynamicIdx = -1;
|
|
92
|
+
for (let i = patternParts.length - 1; i >= 0; i--) if (patternParts[i].startsWith(":")) {
|
|
93
|
+
lastDynamicIdx = i;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
91
96
|
const parentSegments = [];
|
|
92
|
-
|
|
97
|
+
let prefixPattern = "";
|
|
98
|
+
for (let i = 0; i < lastDynamicIdx; i++) {
|
|
93
99
|
const part = patternParts[i];
|
|
100
|
+
prefixPattern += "/" + part;
|
|
94
101
|
if (!part.startsWith(":")) continue;
|
|
95
|
-
if (
|
|
96
|
-
const prefixPattern = "/" + patternParts.slice(0, i + 1).join("/");
|
|
97
|
-
if (allRoutes.find((r) => r.pattern === prefixPattern)?.pagePath) {
|
|
102
|
+
if (routeIndex.get(prefixPattern)?.pagePath) {
|
|
98
103
|
const fn = staticParamsMap[prefixPattern];
|
|
99
|
-
if (typeof fn === "function")
|
|
100
|
-
const paramName = part.replace(/^:/, "").replace(/[+*]$/, "");
|
|
101
|
-
parentSegments.push({
|
|
102
|
-
params: [paramName],
|
|
103
|
-
generateStaticParams: fn
|
|
104
|
-
});
|
|
105
|
-
}
|
|
104
|
+
if (typeof fn === "function") parentSegments.push(fn);
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
if (parentSegments.length === 0) return [];
|
|
109
108
|
let currentParams = [{}];
|
|
110
|
-
for (const
|
|
109
|
+
for (const generateStaticParams of parentSegments) {
|
|
111
110
|
const nextParams = [];
|
|
112
111
|
for (const parentParams of currentParams) {
|
|
113
|
-
const results = await
|
|
112
|
+
const results = await generateStaticParams({ params: parentParams });
|
|
114
113
|
if (Array.isArray(results)) for (const result of results) nextParams.push({
|
|
115
114
|
...parentParams,
|
|
116
115
|
...result
|
|
@@ -414,6 +413,7 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
|
|
|
414
413
|
return false;
|
|
415
414
|
}
|
|
416
415
|
});
|
|
416
|
+
const routeIndex = new Map(routes.map((r) => [r.pattern, r]));
|
|
417
417
|
const urlsToRender = [];
|
|
418
418
|
for (const route of routes) {
|
|
419
419
|
if (route.routePath && !route.pagePath) {
|
|
@@ -463,7 +463,7 @@ async function prerenderApp({ routes, outDir, config, mode, rscBundlePath, ...op
|
|
|
463
463
|
});
|
|
464
464
|
continue;
|
|
465
465
|
}
|
|
466
|
-
const parentParamSets = await resolveParentParams(route,
|
|
466
|
+
const parentParamSets = await resolveParentParams(route, routeIndex, staticParamsMap);
|
|
467
467
|
let paramSets;
|
|
468
468
|
if (parentParamSets.length > 0) {
|
|
469
469
|
paramSets = [];
|
|
@@ -688,6 +688,6 @@ function writePrerenderIndex(routes, outDir, options) {
|
|
|
688
688
|
fs.writeFileSync(path.join(outDir, "vinext-prerender.json"), JSON.stringify(index, null, 2), "utf-8");
|
|
689
689
|
}
|
|
690
690
|
//#endregion
|
|
691
|
-
export { buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, writePrerenderIndex };
|
|
691
|
+
export { buildUrlFromParams, getOutputPath, getRscOutputPath, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
|
|
692
692
|
|
|
693
693
|
//# sourceMappingURL=prerender.js.map
|