vinext 0.0.47 → 0.0.48
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 +1 -1
- package/dist/build/layout-classification.js +3 -1
- package/dist/build/layout-classification.js.map +1 -1
- package/dist/build/prerender.js +10 -10
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/report.d.ts +8 -4
- package/dist/build/report.js +17 -7
- package/dist/build/report.js.map +1 -1
- package/dist/build/run-prerender.d.ts +5 -0
- package/dist/build/run-prerender.js +4 -1
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/build/server-manifest.js +2 -7
- package/dist/build/server-manifest.js.map +1 -1
- package/dist/build/standalone.js +3 -5
- package/dist/build/standalone.js.map +1 -1
- package/dist/check.js +45 -29
- package/dist/check.js.map +1 -1
- package/dist/cli-args.d.ts +3 -1
- package/dist/cli-args.js +18 -1
- package/dist/cli-args.js.map +1 -1
- package/dist/cli.js +9 -1
- package/dist/cli.js.map +1 -1
- package/dist/config/config-matchers.js +46 -37
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/deploy.d.ts +18 -2
- package/dist/deploy.js +47 -4
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +11 -9
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.js +4 -1
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-client-entry.js +3 -2
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +14 -59
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +12 -3
- package/dist/entries/runtime-entry-module.js +15 -4
- package/dist/entries/runtime-entry-module.js.map +1 -1
- package/dist/index.js +12 -7
- package/dist/index.js.map +1 -1
- package/dist/plugins/og-assets.js +15 -16
- package/dist/plugins/og-assets.js.map +1 -1
- package/dist/plugins/rsc-client-shim-excludes.d.ts +2 -1
- package/dist/plugins/rsc-client-shim-excludes.js +10 -1
- package/dist/plugins/rsc-client-shim-excludes.js.map +1 -1
- package/dist/routing/app-route-graph.d.ts +90 -4
- package/dist/routing/app-route-graph.js +210 -7
- package/dist/routing/app-route-graph.js.map +1 -1
- package/dist/routing/app-router.d.ts +15 -3
- package/dist/routing/app-router.js +20 -23
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +3 -1
- package/dist/routing/file-matcher.js +6 -1
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.js +10 -19
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-matching.d.ts +28 -0
- package/dist/routing/route-matching.js +44 -0
- package/dist/routing/route-matching.js.map +1 -0
- package/dist/routing/route-pattern.js +4 -1
- package/dist/routing/route-pattern.js.map +1 -1
- package/dist/routing/route-trie.d.ts +8 -0
- package/dist/routing/route-trie.js +12 -1
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/routing/route-validation.js +3 -4
- package/dist/routing/route-validation.js.map +1 -1
- package/dist/routing/utils.d.ts +8 -1
- package/dist/routing/utils.js +25 -2
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/app-browser-entry.js +66 -49
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-navigation-controller.d.ts +7 -5
- package/dist/server/app-browser-navigation-controller.js +43 -35
- package/dist/server/app-browser-navigation-controller.js.map +1 -1
- package/dist/server/app-browser-state.d.ts +33 -15
- package/dist/server/app-browser-state.js +52 -59
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-browser-visible-commit.d.ts +68 -0
- package/dist/server/app-browser-visible-commit.js +182 -0
- package/dist/server/app-browser-visible-commit.js.map +1 -0
- package/dist/server/app-client-reference-preloader.d.ts +15 -0
- package/dist/server/app-client-reference-preloader.js +46 -0
- package/dist/server/app-client-reference-preloader.js.map +1 -0
- package/dist/server/app-elements-wire.d.ts +130 -0
- package/dist/server/app-elements-wire.js +205 -0
- package/dist/server/app-elements-wire.js.map +1 -0
- package/dist/server/app-elements.d.ts +2 -84
- package/dist/server/app-elements.js +3 -102
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.d.ts +1 -1
- package/dist/server/app-middleware.d.ts +2 -1
- package/dist/server/app-middleware.js +34 -11
- package/dist/server/app-middleware.js.map +1 -1
- package/dist/server/app-page-boundary-render.d.ts +1 -1
- package/dist/server/app-page-boundary-render.js +8 -5
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.js +2 -1
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +1 -0
- package/dist/server/app-page-cache.js +8 -13
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +2 -1
- package/dist/server/app-page-dispatch.js +18 -10
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.d.ts +1 -1
- package/dist/server/app-page-element-builder.js +8 -5
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +23 -5
- package/dist/server/app-page-execution.js +39 -24
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.js +2 -1
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-method.js +2 -5
- package/dist/server/app-page-method.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +1 -1
- package/dist/server/app-page-probe.js +5 -1
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-page-render.d.ts +1 -1
- package/dist/server/app-page-render.js +38 -3
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +0 -1
- package/dist/server/app-page-request.js +7 -10
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.js +3 -2
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +5 -2
- package/dist/server/app-page-route-wiring.js +15 -12
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +7 -0
- package/dist/server/app-page-stream.js +9 -2
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-prerender-endpoints.js +3 -2
- package/dist/server/app-prerender-endpoints.js.map +1 -1
- package/dist/server/app-route-handler-cache.js +2 -1
- package/dist/server/app-route-handler-cache.js.map +1 -1
- package/dist/server/app-route-handler-dispatch.js +6 -5
- package/dist/server/app-route-handler-dispatch.js.map +1 -1
- package/dist/server/app-route-handler-policy.js +13 -13
- package/dist/server/app-route-handler-policy.js.map +1 -1
- package/dist/server/app-route-handler-response.js +2 -1
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-route-handler-runtime.d.ts +9 -1
- package/dist/server/app-route-handler-runtime.js +11 -1
- package/dist/server/app-route-handler-runtime.js.map +1 -1
- package/dist/server/app-router-entry.js +9 -4
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +34 -0
- package/dist/server/app-rsc-cache-busting.js +137 -0
- package/dist/server/app-rsc-cache-busting.js.map +1 -0
- package/dist/server/app-rsc-handler.js +22 -11
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-request-normalization.d.ts +4 -2
- package/dist/server/app-rsc-request-normalization.js +10 -6
- package/dist/server/app-rsc-request-normalization.js.map +1 -1
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- package/dist/server/app-rsc-route-matching.js +8 -4
- package/dist/server/app-rsc-route-matching.js.map +1 -1
- package/dist/server/app-segment-config.js +4 -0
- package/dist/server/app-segment-config.js.map +1 -1
- package/dist/server/app-server-action-execution.js +43 -51
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.js +21 -20
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +44 -0
- package/dist/server/artifact-compatibility.js +82 -0
- package/dist/server/artifact-compatibility.js.map +1 -0
- package/dist/server/cache-proof.d.ts +200 -0
- package/dist/server/cache-proof.js +342 -0
- package/dist/server/cache-proof.js.map +1 -0
- package/dist/server/dev-origin-check.js +8 -4
- package/dist/server/dev-origin-check.js.map +1 -1
- package/dist/server/dev-server.js +1 -6
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/http-error-responses.d.ts +67 -0
- package/dist/server/http-error-responses.js +77 -0
- package/dist/server/http-error-responses.js.map +1 -0
- package/dist/server/image-optimization.js +2 -1
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/metadata-route-response.js +6 -5
- package/dist/server/metadata-route-response.js.map +1 -1
- package/dist/server/metadata-routes.d.ts +1 -0
- package/dist/server/metadata-routes.js +6 -0
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware-matcher.js +2 -2
- package/dist/server/middleware-matcher.js.map +1 -1
- package/dist/server/middleware-response-headers.js +21 -0
- package/dist/server/middleware-response-headers.js.map +1 -1
- package/dist/server/middleware-runtime.js +3 -3
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/navigation-trace.d.ts +33 -0
- package/dist/server/navigation-trace.js +35 -0
- package/dist/server/navigation-trace.js.map +1 -0
- package/dist/server/next-error-digest.d.ts +44 -0
- package/dist/server/next-error-digest.js +40 -0
- package/dist/server/next-error-digest.js.map +1 -0
- package/dist/server/pages-api-route.js +2 -1
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-node-compat.js +4 -16
- package/dist/server/pages-node-compat.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +2 -8
- package/dist/server/pages-page-response.js +44 -14
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.d.ts +6 -0
- package/dist/server/prod-server.js +28 -21
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-pipeline.d.ts +42 -1
- package/dist/server/request-pipeline.js +97 -17
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/shims/cache-runtime.d.ts +2 -2
- package/dist/shims/cache-runtime.js +3 -6
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.js +3 -5
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/fetch-cache.js +2 -3
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/head-state.js +2 -3
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/headers.js +4 -44
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/i18n-state.js +2 -3
- package/dist/shims/i18n-state.js.map +1 -1
- package/dist/shims/internal/als-registry.d.ts +15 -0
- package/dist/shims/internal/als-registry.js +55 -0
- package/dist/shims/internal/als-registry.js.map +1 -0
- package/dist/shims/internal/cookie-serialize.d.ts +46 -0
- package/dist/shims/internal/cookie-serialize.js +51 -0
- package/dist/shims/internal/cookie-serialize.js.map +1 -0
- package/dist/shims/link.js +31 -26
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +26 -1
- package/dist/shims/metadata.js +94 -4
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation-state.js +2 -3
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.d.ts +2 -7
- package/dist/shims/navigation.js +44 -36
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/request-context.js +2 -4
- package/dist/shims/request-context.js.map +1 -1
- package/dist/shims/router-state.js +2 -3
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.js +2 -2
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/server.js +5 -30
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.d.ts +1 -1
- package/dist/shims/slot.js +5 -4
- package/dist/shims/slot.js.map +1 -1
- package/dist/shims/thenable-params.d.ts +5 -2
- package/dist/shims/thenable-params.js +26 -6
- package/dist/shims/thenable-params.js.map +1 -1
- package/dist/shims/unified-request-context.js +2 -14
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/utils/base-path.d.ts +7 -1
- package/dist/utils/base-path.js +12 -1
- package/dist/utils/base-path.js.map +1 -1
- package/dist/utils/safe-json-file.d.ts +18 -0
- package/dist/utils/safe-json-file.js +25 -0
- package/dist/utils/safe-json-file.js.map +1 -0
- package/dist/utils/text-stream.d.ts +29 -0
- package/dist/utils/text-stream.js +66 -0
- package/dist/utils/text-stream.js.map +1 -0
- package/package.json +5 -5
|
@@ -30,17 +30,22 @@ function createOgInlineFetchAssetsPlugin() {
|
|
|
30
30
|
const moduleDir = path.dirname(id);
|
|
31
31
|
let newCode = code;
|
|
32
32
|
let didReplace = false;
|
|
33
|
+
const readAsBase64 = async (absPath) => {
|
|
34
|
+
const cached = useCache ? cache.get(absPath) : void 0;
|
|
35
|
+
if (cached !== void 0) return cached;
|
|
36
|
+
try {
|
|
37
|
+
const b64 = (await fs.promises.readFile(absPath)).toString("base64");
|
|
38
|
+
if (useCache) cache.set(absPath, b64);
|
|
39
|
+
return b64;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
33
44
|
if (code.includes("fetch(")) for (const match of code.matchAll(/fetch\(\s*new URL\(\s*(["'])(\.\/[^"']+)\1\s*,\s*import\.meta\.url\s*\)\s*\)(?:\.then\(\s*(?:function\s*\([^)]*\)|\([^)]*\)\s*=>)\s*\{?\s*return\s+[^.]+\.arrayBuffer\(\)\s*\}?\s*\)|\.then\(\s*\([^)]*\)\s*=>\s*[^.]+\.arrayBuffer\(\)\s*\))/g)) {
|
|
34
45
|
const fullMatch = match[0];
|
|
35
46
|
const relPath = match[2];
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
if (fileBase64 === void 0) try {
|
|
39
|
-
fileBase64 = (await fs.promises.readFile(absPath)).toString("base64");
|
|
40
|
-
if (useCache) cache.set(absPath, fileBase64);
|
|
41
|
-
} catch {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
47
|
+
const fileBase64 = await readAsBase64(path.resolve(moduleDir, relPath));
|
|
48
|
+
if (fileBase64 === null) continue;
|
|
44
49
|
const inlined = [
|
|
45
50
|
`(function(){`,
|
|
46
51
|
`var b=${JSON.stringify(fileBase64)};`,
|
|
@@ -56,14 +61,8 @@ function createOgInlineFetchAssetsPlugin() {
|
|
|
56
61
|
if (code.includes("readFileSync(")) for (const match of newCode.matchAll(/[a-zA-Z_$][a-zA-Z0-9_$]*\.readFileSync\(\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*\.)?fileURLToPath\(\s*new URL\(\s*(["'])(\.\/[^"']+)\1\s*,\s*import\.meta\.url\s*\)\s*\)\s*\)/g)) {
|
|
57
62
|
const fullMatch = match[0];
|
|
58
63
|
const relPath = match[2];
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
if (fileBase64 === void 0) try {
|
|
62
|
-
fileBase64 = (await fs.promises.readFile(absPath)).toString("base64");
|
|
63
|
-
if (useCache) cache.set(absPath, fileBase64);
|
|
64
|
-
} catch {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
64
|
+
const fileBase64 = await readAsBase64(path.resolve(moduleDir, relPath));
|
|
65
|
+
if (fileBase64 === null) continue;
|
|
67
66
|
const inlined = `Buffer.from(${JSON.stringify(fileBase64)},"base64")`;
|
|
68
67
|
newCode = newCode.replaceAll(fullMatch, inlined);
|
|
69
68
|
didReplace = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"og-assets.js","names":[],"sources":["../../src/plugins/og-assets.ts"],"sourcesContent":["/**\n * vinext OG image asset plugins\n *\n * Exports two Vite plugins:\n *\n * `createOgInlineFetchAssetsPlugin` — vinext:og-inline-fetch-assets\n * Some bundled libraries (notably @vercel/og) load assets at module init\n * time with the pattern:\n *\n * fetch(new URL(\"./some-font.ttf\", import.meta.url)).then(res => res.arrayBuffer())\n *\n * This works in browser and standard Node.js because import.meta.url is a\n * real file:// URL. In Cloudflare Workers (both wrangler dev and production),\n * however, import.meta.url is the string \"worker\" — not a URL — so\n * new URL(...) throws \"TypeError: Invalid URL string\" and the Worker fails to\n * start.\n *\n * Fix: at Vite transform time, find every such pattern, resolve the referenced\n * file relative to the module's actual path on disk (available as `id`), read\n * it, and replace the entire fetch(new URL(...)) expression with an inline\n * base64 IIFE that resolves synchronously. This eliminates the runtime fetch\n * entirely and works in all environments (workerd, Node.js, browser).\n *\n * Note: WASM files imported via `import ... from \"./foo.wasm?module\"` are\n * handled by the bundler/Vite directly and do not need this treatment. Only\n * assets that are runtime-fetched (not statically imported) need inlining.\n *\n * `ogAssetsPlugin` — vinext:og-assets\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * og-inline-fetch-assets; this plugin is a safety net to ensure resvg.wasm is\n * present for the Node.js disk-read fallback.\n */\n\nimport type { Plugin } from \"vite\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\n\n// ── Plugin factories ──────────────────────────────────────────────────────────\n\n/**\n * Create the `vinext:og-inline-fetch-assets` Vite plugin.\n *\n * Inlines binary assets that are runtime-fetched via\n * `fetch(new URL(\"./asset\", import.meta.url))` or read via\n * `readFileSync(fileURLToPath(new URL(\"./asset\", import.meta.url)))`.\n * Both patterns are rewritten to inline base64 literals so the code works\n * correctly inside Cloudflare Workers where `import.meta.url` is not a\n * valid file URL.\n */\nexport function createOgInlineFetchAssetsPlugin(): Plugin {\n // Build-only cache to avoid repeated file reads during a single production\n // build. Dev mode skips the cache so asset edits are picked up without\n // restarting the Vite server.\n const cache = new Map<string, string>(); // absPath -> base64\n let isBuild = false;\n\n return {\n name: \"vinext:og-inline-fetch-assets\",\n enforce: \"pre\",\n\n configResolved(config) {\n isBuild = config.command === \"build\";\n },\n\n buildStart() {\n if (isBuild) {\n cache.clear();\n }\n },\n\n async transform(code, id) {\n // Quick bail-out: only process modules that use new URL(..., import.meta.url)\n if (!code.includes(\"import.meta.url\")) {\n return null;\n }\n\n const useCache = isBuild;\n const moduleDir = path.dirname(id);\n let newCode = code;\n let didReplace = false;\n\n // Pattern 1 — edge build: fetch(new URL(\"./file\", import.meta.url)).then((res) => res.arrayBuffer())\n // Replace with an inline IIFE that decodes the asset as base64 and returns Promise<ArrayBuffer>.\n if (code.includes(\"fetch(\")) {\n const fetchPattern =\n /fetch\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)(?:\\.then\\(\\s*(?:function\\s*\\([^)]*\\)|\\([^)]*\\)\\s*=>)\\s*\\{?\\s*return\\s+[^.]+\\.arrayBuffer\\(\\)\\s*\\}?\\s*\\)|\\.then\\(\\s*\\([^)]*\\)\\s*=>\\s*[^.]+\\.arrayBuffer\\(\\)\\s*\\))/g;\n\n for (const match of code.matchAll(fetchPattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n let fileBase64 = useCache ? cache.get(absPath) : undefined;\n if (fileBase64 === undefined) {\n try {\n const buf = await fs.promises.readFile(absPath);\n fileBase64 = buf.toString(\"base64\");\n if (useCache) {\n cache.set(absPath, fileBase64);\n }\n } catch {\n // File not found on disk — skip (may be a runtime-only asset)\n continue;\n }\n }\n\n // Replace fetch(...).then(...) with an inline IIFE that returns Promise<ArrayBuffer>.\n const inlined = [\n `(function(){`,\n `var b=${JSON.stringify(fileBase64)};`,\n `var r=atob(b);`,\n `var a=new Uint8Array(r.length);`,\n `for(var i=0;i<r.length;i++)a[i]=r.charCodeAt(i);`,\n `return Promise.resolve(a.buffer);`,\n `})()`,\n ].join(\"\");\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n // Pattern 2 — node build: readFileSync(fileURLToPath(new URL(\"./file\", import.meta.url)))\n // Replace with Buffer.from(\"<base64>\", \"base64\"), which returns a Buffer (compatible with\n // both font data passed to satori and WASM bytes passed to initWasm).\n if (code.includes(\"readFileSync(\")) {\n const readFilePattern =\n /[a-zA-Z_$][a-zA-Z0-9_$]*\\.readFileSync\\(\\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*\\.)?fileURLToPath\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)\\s*\\)/g;\n\n for (const match of newCode.matchAll(readFilePattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n let fileBase64 = useCache ? cache.get(absPath) : undefined;\n if (fileBase64 === undefined) {\n try {\n const buf = await fs.promises.readFile(absPath);\n fileBase64 = buf.toString(\"base64\");\n if (useCache) {\n cache.set(absPath, fileBase64);\n }\n } catch {\n // File not found on disk — skip\n continue;\n }\n }\n\n // Replace readFileSync(...) with Buffer.from(\"<base64>\", \"base64\").\n // Buffer is always available in Node.js and in the vinext SSR/RSC environments.\n const inlined = `Buffer.from(${JSON.stringify(fileBase64)},\"base64\")`;\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n if (!didReplace) return null;\n return { code: newCode, map: null };\n },\n } satisfies Plugin;\n}\n\n/**\n * The `vinext:og-assets` Vite plugin.\n *\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * `vinext:og-inline-fetch-assets`; this plugin is a safety net to ensure\n * resvg.wasm exists in the output directory for the Node.js disk-read fallback.\n */\nexport const ogAssetsPlugin: Plugin = {\n name: \"vinext:og-assets\",\n apply: \"build\",\n enforce: \"post\",\n writeBundle: {\n sequential: true,\n order: \"post\",\n async handler(options) {\n const envName = this.environment?.name;\n if (envName !== \"rsc\") return;\n\n const outDir = options.dir;\n if (!outDir) return;\n\n // Check if the bundle references @vercel/og assets\n const indexPath = path.join(outDir, \"index.js\");\n if (!fs.existsSync(indexPath)) return;\n\n const content = fs.readFileSync(indexPath, \"utf-8\");\n // The font is inlined as base64 by vinext:og-inline-fetch-assets, so only\n // the WASM needs to be present as a file alongside the bundle.\n const ogAssets = [\"resvg.wasm\"];\n\n // Only copy if the bundle actually references these files\n const referencedAssets = ogAssets.filter((asset) => content.includes(asset));\n if (referencedAssets.length === 0) return;\n\n // Find @vercel/og in node_modules\n try {\n const require = createRequire(import.meta.url);\n const ogPkgPath = require.resolve(\"@vercel/og/package.json\");\n const ogDistDir = path.join(path.dirname(ogPkgPath), \"dist\");\n\n for (const asset of referencedAssets) {\n const src = path.join(ogDistDir, asset);\n const dest = path.join(outDir, asset);\n if (fs.existsSync(src) && !fs.existsSync(dest)) {\n fs.copyFileSync(src, dest);\n }\n }\n } catch {\n // @vercel/og not installed — nothing to copy\n }\n },\n },\n};\n"],"mappings":";;;;;;;;;;;;;;AAmDA,SAAgB,kCAA0C;CAIxD,MAAM,wBAAQ,IAAI,KAAqB;CACvC,IAAI,UAAU;AAEd,QAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAQ;AACrB,aAAU,OAAO,YAAY;;EAG/B,aAAa;AACX,OAAI,QACF,OAAM,OAAO;;EAIjB,MAAM,UAAU,MAAM,IAAI;AAExB,OAAI,CAAC,KAAK,SAAS,kBAAkB,CACnC,QAAO;GAGT,MAAM,WAAW;GACjB,MAAM,YAAY,KAAK,QAAQ,GAAG;GAClC,IAAI,UAAU;GACd,IAAI,aAAa;AAIjB,OAAI,KAAK,SAAS,SAAS,CAIzB,MAAK,MAAM,SAAS,KAAK,SAFvB,iPAE6C,EAAE;IAC/C,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IACtB,MAAM,UAAU,KAAK,QAAQ,WAAW,QAAQ;IAEhD,IAAI,aAAa,WAAW,MAAM,IAAI,QAAQ,GAAG,KAAA;AACjD,QAAI,eAAe,KAAA,EACjB,KAAI;AAEF,mBADY,MAAM,GAAG,SAAS,SAAS,QAAQ,EAC9B,SAAS,SAAS;AACnC,SAAI,SACF,OAAM,IAAI,SAAS,WAAW;YAE1B;AAEN;;IAKJ,MAAM,UAAU;KACd;KACA,SAAS,KAAK,UAAU,WAAW,CAAC;KACpC;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;AAEV,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAOjB,OAAI,KAAK,SAAS,gBAAgB,CAIhC,MAAK,MAAM,SAAS,QAAQ,SAF1B,uKAEmD,EAAE;IACrD,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IACtB,MAAM,UAAU,KAAK,QAAQ,WAAW,QAAQ;IAEhD,IAAI,aAAa,WAAW,MAAM,IAAI,QAAQ,GAAG,KAAA;AACjD,QAAI,eAAe,KAAA,EACjB,KAAI;AAEF,mBADY,MAAM,GAAG,SAAS,SAAS,QAAQ,EAC9B,SAAS,SAAS;AACnC,SAAI,SACF,OAAM,IAAI,SAAS,WAAW;YAE1B;AAEN;;IAMJ,MAAM,UAAU,eAAe,KAAK,UAAU,WAAW,CAAC;AAE1D,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAIjB,OAAI,CAAC,WAAY,QAAO;AACxB,UAAO;IAAE,MAAM;IAAS,KAAK;IAAM;;EAEtC;;;;;;;;;;AAWH,MAAa,iBAAyB;CACpC,MAAM;CACN,OAAO;CACP,SAAS;CACT,aAAa;EACX,YAAY;EACZ,OAAO;EACP,MAAM,QAAQ,SAAS;AAErB,OADgB,KAAK,aAAa,SAClB,MAAO;GAEvB,MAAM,SAAS,QAAQ;AACvB,OAAI,CAAC,OAAQ;GAGb,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW;AAC/C,OAAI,CAAC,GAAG,WAAW,UAAU,CAAE;GAE/B,MAAM,UAAU,GAAG,aAAa,WAAW,QAAQ;GAMnD,MAAM,mBAHW,CAAC,aAAa,CAGG,QAAQ,UAAU,QAAQ,SAAS,MAAM,CAAC;AAC5E,OAAI,iBAAiB,WAAW,EAAG;AAGnC,OAAI;IAEF,MAAM,YADU,cAAc,OAAO,KAAK,IAAI,CACpB,QAAQ,0BAA0B;IAC5D,MAAM,YAAY,KAAK,KAAK,KAAK,QAAQ,UAAU,EAAE,OAAO;AAE5D,SAAK,MAAM,SAAS,kBAAkB;KACpC,MAAM,MAAM,KAAK,KAAK,WAAW,MAAM;KACvC,MAAM,OAAO,KAAK,KAAK,QAAQ,MAAM;AACrC,SAAI,GAAG,WAAW,IAAI,IAAI,CAAC,GAAG,WAAW,KAAK,CAC5C,IAAG,aAAa,KAAK,KAAK;;WAGxB;;EAIX;CACF"}
|
|
1
|
+
{"version":3,"file":"og-assets.js","names":[],"sources":["../../src/plugins/og-assets.ts"],"sourcesContent":["/**\n * vinext OG image asset plugins\n *\n * Exports two Vite plugins:\n *\n * `createOgInlineFetchAssetsPlugin` — vinext:og-inline-fetch-assets\n * Some bundled libraries (notably @vercel/og) load assets at module init\n * time with the pattern:\n *\n * fetch(new URL(\"./some-font.ttf\", import.meta.url)).then(res => res.arrayBuffer())\n *\n * This works in browser and standard Node.js because import.meta.url is a\n * real file:// URL. In Cloudflare Workers (both wrangler dev and production),\n * however, import.meta.url is the string \"worker\" — not a URL — so\n * new URL(...) throws \"TypeError: Invalid URL string\" and the Worker fails to\n * start.\n *\n * Fix: at Vite transform time, find every such pattern, resolve the referenced\n * file relative to the module's actual path on disk (available as `id`), read\n * it, and replace the entire fetch(new URL(...)) expression with an inline\n * base64 IIFE that resolves synchronously. This eliminates the runtime fetch\n * entirely and works in all environments (workerd, Node.js, browser).\n *\n * Note: WASM files imported via `import ... from \"./foo.wasm?module\"` are\n * handled by the bundler/Vite directly and do not need this treatment. Only\n * assets that are runtime-fetched (not statically imported) need inlining.\n *\n * `ogAssetsPlugin` — vinext:og-assets\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * og-inline-fetch-assets; this plugin is a safety net to ensure resvg.wasm is\n * present for the Node.js disk-read fallback.\n */\n\nimport type { Plugin } from \"vite\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\n\n// ── Plugin factories ──────────────────────────────────────────────────────────\n\n/**\n * Create the `vinext:og-inline-fetch-assets` Vite plugin.\n *\n * Inlines binary assets that are runtime-fetched via\n * `fetch(new URL(\"./asset\", import.meta.url))` or read via\n * `readFileSync(fileURLToPath(new URL(\"./asset\", import.meta.url)))`.\n * Both patterns are rewritten to inline base64 literals so the code works\n * correctly inside Cloudflare Workers where `import.meta.url` is not a\n * valid file URL.\n */\nexport function createOgInlineFetchAssetsPlugin(): Plugin {\n // Build-only cache to avoid repeated file reads during a single production\n // build. Dev mode skips the cache so asset edits are picked up without\n // restarting the Vite server.\n const cache = new Map<string, string>(); // absPath -> base64\n let isBuild = false;\n\n return {\n name: \"vinext:og-inline-fetch-assets\",\n enforce: \"pre\",\n\n configResolved(config) {\n isBuild = config.command === \"build\";\n },\n\n buildStart() {\n if (isBuild) {\n cache.clear();\n }\n },\n\n async transform(code, id) {\n // Quick bail-out: only process modules that use new URL(..., import.meta.url)\n if (!code.includes(\"import.meta.url\")) {\n return null;\n }\n\n const useCache = isBuild;\n const moduleDir = path.dirname(id);\n let newCode = code;\n let didReplace = false;\n\n // Read a file from disk and return its base64 encoding, using the build\n // cache when enabled. Returns null on any read error so callers can skip\n // the match (e.g. file not present on disk for the active environment).\n const readAsBase64 = async (absPath: string): Promise<string | null> => {\n const cached = useCache ? cache.get(absPath) : undefined;\n if (cached !== undefined) return cached;\n try {\n const buf = await fs.promises.readFile(absPath);\n const b64 = buf.toString(\"base64\");\n if (useCache) cache.set(absPath, b64);\n return b64;\n } catch {\n return null;\n }\n };\n\n // Pattern 1 — edge build: fetch(new URL(\"./file\", import.meta.url)).then((res) => res.arrayBuffer())\n // Replace with an inline IIFE that decodes the asset as base64 and returns Promise<ArrayBuffer>.\n if (code.includes(\"fetch(\")) {\n const fetchPattern =\n /fetch\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)(?:\\.then\\(\\s*(?:function\\s*\\([^)]*\\)|\\([^)]*\\)\\s*=>)\\s*\\{?\\s*return\\s+[^.]+\\.arrayBuffer\\(\\)\\s*\\}?\\s*\\)|\\.then\\(\\s*\\([^)]*\\)\\s*=>\\s*[^.]+\\.arrayBuffer\\(\\)\\s*\\))/g;\n\n for (const match of code.matchAll(fetchPattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n const fileBase64 = await readAsBase64(absPath);\n if (fileBase64 === null) continue; // may be a runtime-only asset\n\n // Replace fetch(...).then(...) with an inline IIFE that returns Promise<ArrayBuffer>.\n const inlined = [\n `(function(){`,\n `var b=${JSON.stringify(fileBase64)};`,\n `var r=atob(b);`,\n `var a=new Uint8Array(r.length);`,\n `for(var i=0;i<r.length;i++)a[i]=r.charCodeAt(i);`,\n `return Promise.resolve(a.buffer);`,\n `})()`,\n ].join(\"\");\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n // Pattern 2 — node build: readFileSync(fileURLToPath(new URL(\"./file\", import.meta.url)))\n // Replace with Buffer.from(\"<base64>\", \"base64\"), which returns a Buffer (compatible with\n // both font data passed to satori and WASM bytes passed to initWasm).\n if (code.includes(\"readFileSync(\")) {\n const readFilePattern =\n /[a-zA-Z_$][a-zA-Z0-9_$]*\\.readFileSync\\(\\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*\\.)?fileURLToPath\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)\\s*\\)/g;\n\n for (const match of newCode.matchAll(readFilePattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n const fileBase64 = await readAsBase64(absPath);\n if (fileBase64 === null) continue;\n\n // Replace readFileSync(...) with Buffer.from(\"<base64>\", \"base64\").\n // Buffer is always available in Node.js and in the vinext SSR/RSC environments.\n const inlined = `Buffer.from(${JSON.stringify(fileBase64)},\"base64\")`;\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n if (!didReplace) return null;\n return { code: newCode, map: null };\n },\n } satisfies Plugin;\n}\n\n/**\n * The `vinext:og-assets` Vite plugin.\n *\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * `vinext:og-inline-fetch-assets`; this plugin is a safety net to ensure\n * resvg.wasm exists in the output directory for the Node.js disk-read fallback.\n */\nexport const ogAssetsPlugin: Plugin = {\n name: \"vinext:og-assets\",\n apply: \"build\",\n enforce: \"post\",\n writeBundle: {\n sequential: true,\n order: \"post\",\n async handler(options) {\n const envName = this.environment?.name;\n if (envName !== \"rsc\") return;\n\n const outDir = options.dir;\n if (!outDir) return;\n\n // Check if the bundle references @vercel/og assets\n const indexPath = path.join(outDir, \"index.js\");\n if (!fs.existsSync(indexPath)) return;\n\n const content = fs.readFileSync(indexPath, \"utf-8\");\n // The font is inlined as base64 by vinext:og-inline-fetch-assets, so only\n // the WASM needs to be present as a file alongside the bundle.\n const ogAssets = [\"resvg.wasm\"];\n\n // Only copy if the bundle actually references these files\n const referencedAssets = ogAssets.filter((asset) => content.includes(asset));\n if (referencedAssets.length === 0) return;\n\n // Find @vercel/og in node_modules\n try {\n const require = createRequire(import.meta.url);\n const ogPkgPath = require.resolve(\"@vercel/og/package.json\");\n const ogDistDir = path.join(path.dirname(ogPkgPath), \"dist\");\n\n for (const asset of referencedAssets) {\n const src = path.join(ogDistDir, asset);\n const dest = path.join(outDir, asset);\n if (fs.existsSync(src) && !fs.existsSync(dest)) {\n fs.copyFileSync(src, dest);\n }\n }\n } catch {\n // @vercel/og not installed — nothing to copy\n }\n },\n },\n};\n"],"mappings":";;;;;;;;;;;;;;AAmDA,SAAgB,kCAA0C;CAIxD,MAAM,wBAAQ,IAAI,KAAqB;CACvC,IAAI,UAAU;AAEd,QAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAQ;AACrB,aAAU,OAAO,YAAY;;EAG/B,aAAa;AACX,OAAI,QACF,OAAM,OAAO;;EAIjB,MAAM,UAAU,MAAM,IAAI;AAExB,OAAI,CAAC,KAAK,SAAS,kBAAkB,CACnC,QAAO;GAGT,MAAM,WAAW;GACjB,MAAM,YAAY,KAAK,QAAQ,GAAG;GAClC,IAAI,UAAU;GACd,IAAI,aAAa;GAKjB,MAAM,eAAe,OAAO,YAA4C;IACtE,MAAM,SAAS,WAAW,MAAM,IAAI,QAAQ,GAAG,KAAA;AAC/C,QAAI,WAAW,KAAA,EAAW,QAAO;AACjC,QAAI;KAEF,MAAM,OADM,MAAM,GAAG,SAAS,SAAS,QAAQ,EAC/B,SAAS,SAAS;AAClC,SAAI,SAAU,OAAM,IAAI,SAAS,IAAI;AACrC,YAAO;YACD;AACN,YAAO;;;AAMX,OAAI,KAAK,SAAS,SAAS,CAIzB,MAAK,MAAM,SAAS,KAAK,SAFvB,iPAE6C,EAAE;IAC/C,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IAGtB,MAAM,aAAa,MAAM,aAFT,KAAK,QAAQ,WAAW,QAAQ,CAEF;AAC9C,QAAI,eAAe,KAAM;IAGzB,MAAM,UAAU;KACd;KACA,SAAS,KAAK,UAAU,WAAW,CAAC;KACpC;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;AAEV,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAOjB,OAAI,KAAK,SAAS,gBAAgB,CAIhC,MAAK,MAAM,SAAS,QAAQ,SAF1B,uKAEmD,EAAE;IACrD,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IAGtB,MAAM,aAAa,MAAM,aAFT,KAAK,QAAQ,WAAW,QAAQ,CAEF;AAC9C,QAAI,eAAe,KAAM;IAIzB,MAAM,UAAU,eAAe,KAAK,UAAU,WAAW,CAAC;AAE1D,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAIjB,OAAI,CAAC,WAAY,QAAO;AACxB,UAAO;IAAE,MAAM;IAAS,KAAK;IAAM;;EAEtC;;;;;;;;;;AAWH,MAAa,iBAAyB;CACpC,MAAM;CACN,OAAO;CACP,SAAS;CACT,aAAa;EACX,YAAY;EACZ,OAAO;EACP,MAAM,QAAQ,SAAS;AAErB,OADgB,KAAK,aAAa,SAClB,MAAO;GAEvB,MAAM,SAAS,QAAQ;AACvB,OAAI,CAAC,OAAQ;GAGb,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW;AAC/C,OAAI,CAAC,GAAG,WAAW,UAAU,CAAE;GAE/B,MAAM,UAAU,GAAG,aAAa,WAAW,QAAQ;GAMnD,MAAM,mBAHW,CAAC,aAAa,CAGG,QAAQ,UAAU,QAAQ,SAAS,MAAM,CAAC;AAC5E,OAAI,iBAAiB,WAAW,EAAG;AAGnC,OAAI;IAEF,MAAM,YADU,cAAc,OAAO,KAAK,IAAI,CACpB,QAAQ,0BAA0B;IAC5D,MAAM,YAAY,KAAK,KAAK,KAAK,QAAQ,UAAU,EAAE,OAAO;AAE5D,SAAK,MAAM,SAAS,kBAAkB;KACpC,MAAM,MAAM,KAAK,KAAK,WAAW,MAAM;KACvC,MAAM,OAAO,KAAK,KAAK,QAAQ,MAAM;AACrC,SAAI,GAAG,WAAW,IAAI,IAAI,CAAC,GAAG,WAAW,KAAK,CAC5C,IAAG,aAAa,KAAK,KAAK;;WAGxB;;EAIX;CACF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//#region src/plugins/rsc-client-shim-excludes.d.ts
|
|
2
2
|
declare const VINEXT_OPTIMIZE_DEPS_EXCLUDE: readonly string[];
|
|
3
|
+
declare const SSR_EXTERNAL_REACT_ENTRIES: readonly string[];
|
|
3
4
|
declare function mergeOptimizeDepsExclude(...excludeGroups: readonly (readonly string[])[]): string[];
|
|
4
5
|
//#endregion
|
|
5
|
-
export { VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
|
|
6
|
+
export { SSR_EXTERNAL_REACT_ENTRIES, VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
|
|
6
7
|
//# sourceMappingURL=rsc-client-shim-excludes.d.ts.map
|
|
@@ -14,6 +14,15 @@ const VINEXT_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([
|
|
|
14
14
|
"private-next-instrumentation-client",
|
|
15
15
|
...RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE
|
|
16
16
|
]);
|
|
17
|
+
const SSR_EXTERNAL_REACT_ENTRIES = Object.freeze([
|
|
18
|
+
"react",
|
|
19
|
+
"react-dom",
|
|
20
|
+
"react-dom/server.edge",
|
|
21
|
+
"react-dom/static.edge",
|
|
22
|
+
"react/jsx-runtime",
|
|
23
|
+
"react/jsx-dev-runtime",
|
|
24
|
+
"react-server-dom-webpack/client.edge"
|
|
25
|
+
]);
|
|
17
26
|
function mergeOptimizeDepsExclude(...excludeGroups) {
|
|
18
27
|
const seen = /* @__PURE__ */ new Set();
|
|
19
28
|
for (const group of excludeGroups) for (const entry of group) {
|
|
@@ -23,6 +32,6 @@ function mergeOptimizeDepsExclude(...excludeGroups) {
|
|
|
23
32
|
return [...seen];
|
|
24
33
|
}
|
|
25
34
|
//#endregion
|
|
26
|
-
export { VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
|
|
35
|
+
export { SSR_EXTERNAL_REACT_ENTRIES, VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
|
|
27
36
|
|
|
28
37
|
//# sourceMappingURL=rsc-client-shim-excludes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc-client-shim-excludes.js","names":[],"sources":["../../src/plugins/rsc-client-shim-excludes.ts"],"sourcesContent":["const RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n // @vitejs/plugin-rsc tracks package client references by the original\n // bare source. If Vite pre-bundles these known client shims, the generated\n // client-package proxy can lose the matching export metadata in dev.\n \"vinext/shims/error-boundary\",\n \"vinext/shims/form\",\n \"vinext/shims/layout-segment-context\",\n \"vinext/shims/link\",\n \"vinext/shims/script\",\n \"vinext/shims/slot\",\n \"vinext/shims/offline\",\n]);\n\nexport const VINEXT_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n \"vinext\",\n \"@vercel/og\",\n // Aliased to the user's instrumentation-client source file (or an empty\n // shim). Not a real npm dep, so pre-bundling it would break HMR and cause\n // a \"new dependencies optimized\" reload on the first request.\n \"private-next-instrumentation-client\",\n ...RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE,\n]);\n\nexport function mergeOptimizeDepsExclude(\n ...excludeGroups: readonly (readonly string[])[]\n): string[] {\n const seen = new Set<string>();\n\n for (const group of excludeGroups) {\n for (const entry of group) {\n if (seen.has(entry)) continue;\n seen.add(entry);\n }\n }\n\n return [...seen];\n}\n"],"mappings":";AAAA,MAAM,wCAAwC,OAAO,OAAO;CAI1D;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,+BAA+B,OAAO,OAAO;CACxD;CACA;CAIA;CACA,GAAG;CACJ,CAAC;AAEF,SAAgB,yBACd,GAAG,eACO;CACV,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,SAAS,cAClB,MAAK,MAAM,SAAS,OAAO;AACzB,MAAI,KAAK,IAAI,MAAM,CAAE;AACrB,OAAK,IAAI,MAAM;;AAInB,QAAO,CAAC,GAAG,KAAK"}
|
|
1
|
+
{"version":3,"file":"rsc-client-shim-excludes.js","names":[],"sources":["../../src/plugins/rsc-client-shim-excludes.ts"],"sourcesContent":["const RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n // @vitejs/plugin-rsc tracks package client references by the original\n // bare source. If Vite pre-bundles these known client shims, the generated\n // client-package proxy can lose the matching export metadata in dev.\n \"vinext/shims/error-boundary\",\n \"vinext/shims/form\",\n \"vinext/shims/layout-segment-context\",\n \"vinext/shims/link\",\n \"vinext/shims/script\",\n \"vinext/shims/slot\",\n \"vinext/shims/offline\",\n]);\n\nexport const VINEXT_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n \"vinext\",\n \"@vercel/og\",\n // Aliased to the user's instrumentation-client source file (or an empty\n // shim). Not a real npm dep, so pre-bundling it would break HMR and cause\n // a \"new dependencies optimized\" reload on the first request.\n \"private-next-instrumentation-client\",\n ...RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE,\n]);\n\n// React entries that @vitejs/plugin-rsc adds to environments.ssr.optimizeDeps.include\n// via crawlFrameworkPkgs. When the user sets ssr.external: true, the SSR env loads\n// everything via Node's resolver (including React from /node_modules/react). If Vite\n// also pre-bundles React into deps_ssr/, two distinct React module records coexist:\n// react-dom-server.edge sets the dispatcher on its bundled React, but externalized\n// callers (vinext's runtime, and 'use client' modules going through the SSR transform)\n// see a different React → React.H is null → useContext / useSyncExternalStore crash.\n// Adding these to optimizeDeps.exclude keeps deps_ssr/ React-free so the runtime and\n// the renderer share a single Node-loaded React copy.\nexport const SSR_EXTERNAL_REACT_ENTRIES = Object.freeze([\n \"react\",\n \"react-dom\",\n \"react-dom/server.edge\",\n \"react-dom/static.edge\",\n \"react/jsx-runtime\",\n \"react/jsx-dev-runtime\",\n \"react-server-dom-webpack/client.edge\",\n]);\n\nexport function mergeOptimizeDepsExclude(\n ...excludeGroups: readonly (readonly string[])[]\n): string[] {\n const seen = new Set<string>();\n\n for (const group of excludeGroups) {\n for (const entry of group) {\n if (seen.has(entry)) continue;\n seen.add(entry);\n }\n }\n\n return [...seen];\n}\n"],"mappings":";AAAA,MAAM,wCAAwC,OAAO,OAAO;CAI1D;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,+BAA+B,OAAO,OAAO;CACxD;CACA;CAIA;CACA,GAAG;CACJ,CAAC;AAWF,MAAa,6BAA6B,OAAO,OAAO;CACtD;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAgB,yBACd,GAAG,eACO;CACV,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,SAAS,cAClB,MAAK,MAAM,SAAS,OAAO;AACzB,MAAI,KAAK,IAAI,MAAM,CAAE;AACrB,OAAK,IAAI,MAAM;;AAInB,QAAO,CAAC,GAAG,KAAK"}
|
|
@@ -9,7 +9,8 @@ type InterceptingRoute = {
|
|
|
9
9
|
params: string[];
|
|
10
10
|
};
|
|
11
11
|
type ParallelSlot = {
|
|
12
|
-
/** Stable slot identity (name + owning directory), used for route serialization keys. */
|
|
12
|
+
/** Graph-owned semantic slot identity. Required on AppRouteGraphParallelSlot. */id?: string; /** Stable slot identity (name + owning directory), used for route serialization keys. */
|
|
13
|
+
key: string; /** Slot name (e.g. "team" from @team) */
|
|
13
14
|
name: string; /** Absolute path to the @slot directory that owns this slot. Internal routing metadata. */
|
|
14
15
|
ownerDir: string; /** Absolute path to the slot's page component */
|
|
15
16
|
pagePath: string | null; /** Absolute path to the slot's default.tsx fallback */
|
|
@@ -47,7 +48,8 @@ type ParallelSlot = {
|
|
|
47
48
|
slotParamNames?: string[];
|
|
48
49
|
};
|
|
49
50
|
type AppRoute = {
|
|
50
|
-
/** URL pattern, e.g. "/" or "/about" or "/blog/:slug" */
|
|
51
|
+
/** Graph-owned semantic identities. Required on AppRouteGraphRoute. */ids?: AppRouteSemanticIds; /** URL pattern, e.g. "/" or "/about" or "/blog/:slug" */
|
|
52
|
+
pattern: string; /** Absolute file path to the page component */
|
|
51
53
|
pagePath: string | null; /** Absolute file path to the route handler (route.ts) */
|
|
52
54
|
routePath: string | null; /** Ordered list of layout files from root to leaf */
|
|
53
55
|
layouts: string[]; /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */
|
|
@@ -100,10 +102,94 @@ type AppRoute = {
|
|
|
100
102
|
rootParamNames?: string[]; /** Pre-split pattern segments (computed once at scan time, reused per request) */
|
|
101
103
|
patternParts: string[];
|
|
102
104
|
};
|
|
105
|
+
type AppRouteSemanticIds = {
|
|
106
|
+
route: string;
|
|
107
|
+
page: string | null;
|
|
108
|
+
routeHandler: string | null;
|
|
109
|
+
rootBoundary: RootBoundaryId | null;
|
|
110
|
+
layouts: readonly string[];
|
|
111
|
+
templates: readonly string[];
|
|
112
|
+
/**
|
|
113
|
+
* Bridge map for the current route metadata shape: keyed by `slot.key`
|
|
114
|
+
* (`name@relative/path` infrastructure id), value is the graph-owned semantic slot id.
|
|
115
|
+
*/
|
|
116
|
+
slots: Readonly<Record<string, string>>;
|
|
117
|
+
};
|
|
118
|
+
type AppRouteGraphParallelSlot = ParallelSlot & {
|
|
119
|
+
id: string;
|
|
120
|
+
};
|
|
121
|
+
type AppRouteGraphRoute = Omit<AppRoute, "ids" | "parallelSlots" | "rootParamNames"> & {
|
|
122
|
+
ids: AppRouteSemanticIds;
|
|
123
|
+
parallelSlots: AppRouteGraphParallelSlot[];
|
|
124
|
+
rootParamNames: string[];
|
|
125
|
+
};
|
|
126
|
+
type Flavor<T, Brand extends string> = T & {
|
|
127
|
+
readonly __flavor?: Brand;
|
|
128
|
+
};
|
|
129
|
+
type GraphVersion = Flavor<string, "GraphVersion">;
|
|
130
|
+
type RootBoundaryId = Flavor<string, "RootBoundaryId">;
|
|
131
|
+
type RouteManifestRoute = {
|
|
132
|
+
id: string;
|
|
133
|
+
pattern: string;
|
|
134
|
+
patternParts: readonly string[];
|
|
135
|
+
isDynamic: boolean;
|
|
136
|
+
paramNames: readonly string[];
|
|
137
|
+
rootParamNames: readonly string[];
|
|
138
|
+
rootBoundaryId: RootBoundaryId | null;
|
|
139
|
+
pageId: string | null;
|
|
140
|
+
routeHandlerId: string | null;
|
|
141
|
+
layoutIds: readonly string[];
|
|
142
|
+
templateIds: readonly string[];
|
|
143
|
+
slotIds: readonly string[];
|
|
144
|
+
};
|
|
145
|
+
type RouteManifestPage = {
|
|
146
|
+
id: string;
|
|
147
|
+
routeId: string;
|
|
148
|
+
pattern: string;
|
|
149
|
+
};
|
|
150
|
+
type RouteManifestRouteHandler = {
|
|
151
|
+
id: string;
|
|
152
|
+
routeId: string;
|
|
153
|
+
pattern: string;
|
|
154
|
+
};
|
|
155
|
+
type RouteManifestLayout = {
|
|
156
|
+
id: string;
|
|
157
|
+
treePath: string;
|
|
158
|
+
rootBoundaryId: RootBoundaryId | null;
|
|
159
|
+
};
|
|
160
|
+
type RouteManifestTemplate = {
|
|
161
|
+
id: string;
|
|
162
|
+
treePath: string;
|
|
163
|
+
rootBoundaryId: RootBoundaryId | null;
|
|
164
|
+
};
|
|
165
|
+
type RouteManifestSlot = {
|
|
166
|
+
id: string;
|
|
167
|
+
key: string;
|
|
168
|
+
name: string;
|
|
169
|
+
};
|
|
170
|
+
type RouteManifestRootBoundary = {
|
|
171
|
+
id: RootBoundaryId;
|
|
172
|
+
layoutId: string;
|
|
173
|
+
treePath: string;
|
|
174
|
+
};
|
|
175
|
+
type StaticSegmentGraph = {
|
|
176
|
+
routes: ReadonlyMap<string, RouteManifestRoute>;
|
|
177
|
+
pages: ReadonlyMap<string, RouteManifestPage>;
|
|
178
|
+
routeHandlers: ReadonlyMap<string, RouteManifestRouteHandler>;
|
|
179
|
+
layouts: ReadonlyMap<string, RouteManifestLayout>;
|
|
180
|
+
templates: ReadonlyMap<string, RouteManifestTemplate>;
|
|
181
|
+
slots: ReadonlyMap<string, RouteManifestSlot>;
|
|
182
|
+
rootBoundaries: ReadonlyMap<RootBoundaryId, RouteManifestRootBoundary>;
|
|
183
|
+
};
|
|
184
|
+
type RouteManifest = {
|
|
185
|
+
graphVersion: GraphVersion;
|
|
186
|
+
segmentGraph: StaticSegmentGraph;
|
|
187
|
+
};
|
|
103
188
|
declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher): Promise<{
|
|
104
|
-
routes:
|
|
189
|
+
routes: AppRouteGraphRoute[];
|
|
190
|
+
routeManifest: RouteManifest;
|
|
105
191
|
}>;
|
|
106
192
|
declare function computeRootParamNames(routeSegments: readonly string[], layoutTreePositions: readonly number[]): string[];
|
|
107
193
|
//#endregion
|
|
108
|
-
export { AppRoute, InterceptingRoute, ParallelSlot, buildAppRouteGraph, computeRootParamNames };
|
|
194
|
+
export { AppRoute, AppRouteGraphParallelSlot, AppRouteGraphRoute, AppRouteSemanticIds, GraphVersion, InterceptingRoute, ParallelSlot, RootBoundaryId, RouteManifest, RouteManifestLayout, RouteManifestPage, RouteManifestRootBoundary, RouteManifestRoute, RouteManifestRouteHandler, RouteManifestSlot, RouteManifestTemplate, StaticSegmentGraph, buildAppRouteGraph, computeRootParamNames };
|
|
109
195
|
//# sourceMappingURL=app-route-graph.d.ts.map
|
|
@@ -3,6 +3,7 @@ import { scanWithExtensions } from "./file-matcher.js";
|
|
|
3
3
|
import { validateRoutePatterns } from "./route-validation.js";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
//#region src/routing/app-route-graph.ts
|
|
7
8
|
/**
|
|
8
9
|
* App Router route graph construction.
|
|
@@ -10,6 +11,139 @@ import path from "node:path";
|
|
|
10
11
|
* Scans app/ directories and materializes route metadata before the request-time
|
|
11
12
|
* matcher consumes it. Keep request matching and cache ownership in app-router.ts.
|
|
12
13
|
*/
|
|
14
|
+
function createAppRouteGraphRouteId(pattern) {
|
|
15
|
+
return `route:${pattern}`;
|
|
16
|
+
}
|
|
17
|
+
function createAppRouteGraphPageId(pattern) {
|
|
18
|
+
return `page:${pattern}`;
|
|
19
|
+
}
|
|
20
|
+
function createAppRouteGraphRouteHandlerId(pattern) {
|
|
21
|
+
return `route-handler:${pattern}`;
|
|
22
|
+
}
|
|
23
|
+
function createAppRouteGraphLayoutId(treePath) {
|
|
24
|
+
return `layout:${treePath}`;
|
|
25
|
+
}
|
|
26
|
+
function createAppRouteGraphTemplateId(treePath) {
|
|
27
|
+
return `template:${treePath}`;
|
|
28
|
+
}
|
|
29
|
+
function createAppRouteGraphSlotId(slotName, ownerTreePath) {
|
|
30
|
+
return `slot:${slotName}:${ownerTreePath}`;
|
|
31
|
+
}
|
|
32
|
+
function createAppRouteGraphRootBoundaryId(treePath) {
|
|
33
|
+
return `root-boundary:${treePath}`;
|
|
34
|
+
}
|
|
35
|
+
function compareStableStrings(left, right) {
|
|
36
|
+
if (left < right) return -1;
|
|
37
|
+
if (left > right) return 1;
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
function sortedMapValues(map) {
|
|
41
|
+
return Array.from(map.entries()).sort(([left], [right]) => compareStableStrings(left, right)).map(([, value]) => value);
|
|
42
|
+
}
|
|
43
|
+
function createRouteManifest(routes) {
|
|
44
|
+
const segmentGraph = createStaticSegmentGraph(routes);
|
|
45
|
+
return {
|
|
46
|
+
graphVersion: createRouteManifestGraphVersion(segmentGraph),
|
|
47
|
+
segmentGraph
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function createStaticSegmentGraph(routes) {
|
|
51
|
+
const routeEntries = /* @__PURE__ */ new Map();
|
|
52
|
+
const pages = /* @__PURE__ */ new Map();
|
|
53
|
+
const routeHandlers = /* @__PURE__ */ new Map();
|
|
54
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
55
|
+
const templates = /* @__PURE__ */ new Map();
|
|
56
|
+
const slots = /* @__PURE__ */ new Map();
|
|
57
|
+
const rootBoundaries = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const route of routes) {
|
|
59
|
+
routeEntries.set(route.ids.route, {
|
|
60
|
+
id: route.ids.route,
|
|
61
|
+
pattern: route.pattern,
|
|
62
|
+
patternParts: [...route.patternParts],
|
|
63
|
+
isDynamic: route.isDynamic,
|
|
64
|
+
paramNames: [...route.params],
|
|
65
|
+
rootParamNames: [...route.rootParamNames],
|
|
66
|
+
rootBoundaryId: route.ids.rootBoundary,
|
|
67
|
+
pageId: route.ids.page,
|
|
68
|
+
routeHandlerId: route.ids.routeHandler,
|
|
69
|
+
layoutIds: [...route.ids.layouts],
|
|
70
|
+
templateIds: [...route.ids.templates],
|
|
71
|
+
slotIds: route.parallelSlots.map((slot) => slot.id).sort(compareStableStrings)
|
|
72
|
+
});
|
|
73
|
+
if (route.ids.page) pages.set(route.ids.page, {
|
|
74
|
+
id: route.ids.page,
|
|
75
|
+
routeId: route.ids.route,
|
|
76
|
+
pattern: route.pattern
|
|
77
|
+
});
|
|
78
|
+
if (route.ids.routeHandler) routeHandlers.set(route.ids.routeHandler, {
|
|
79
|
+
id: route.ids.routeHandler,
|
|
80
|
+
routeId: route.ids.route,
|
|
81
|
+
pattern: route.pattern
|
|
82
|
+
});
|
|
83
|
+
for (const [index, layoutId] of route.ids.layouts.entries()) {
|
|
84
|
+
const treePosition = route.layoutTreePositions[index];
|
|
85
|
+
assertRouteManifestTreePosition("layout", route, layoutId, treePosition);
|
|
86
|
+
const treePath = createAppRouteGraphTreePath(route.routeSegments, treePosition);
|
|
87
|
+
const existingLayout = layouts.get(layoutId);
|
|
88
|
+
if (existingLayout) assertRouteManifestRootBoundary("layout", route, layoutId, existingLayout.rootBoundaryId);
|
|
89
|
+
layouts.set(layoutId, {
|
|
90
|
+
id: layoutId,
|
|
91
|
+
treePath,
|
|
92
|
+
rootBoundaryId: route.ids.rootBoundary
|
|
93
|
+
});
|
|
94
|
+
if (index === 0 && route.ids.rootBoundary) rootBoundaries.set(route.ids.rootBoundary, {
|
|
95
|
+
id: route.ids.rootBoundary,
|
|
96
|
+
layoutId,
|
|
97
|
+
treePath
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
for (const [index, templateId] of route.ids.templates.entries()) {
|
|
101
|
+
const treePosition = route.templateTreePositions?.[index];
|
|
102
|
+
assertRouteManifestTreePosition("template", route, templateId, treePosition);
|
|
103
|
+
const existingTemplate = templates.get(templateId);
|
|
104
|
+
if (existingTemplate) assertRouteManifestRootBoundary("template", route, templateId, existingTemplate.rootBoundaryId);
|
|
105
|
+
templates.set(templateId, {
|
|
106
|
+
id: templateId,
|
|
107
|
+
treePath: createAppRouteGraphTreePath(route.routeSegments, treePosition),
|
|
108
|
+
rootBoundaryId: route.ids.rootBoundary
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
for (const slot of route.parallelSlots) slots.set(slot.id, {
|
|
112
|
+
id: slot.id,
|
|
113
|
+
key: slot.key,
|
|
114
|
+
name: slot.name
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
routes: routeEntries,
|
|
119
|
+
pages,
|
|
120
|
+
routeHandlers,
|
|
121
|
+
layouts,
|
|
122
|
+
templates,
|
|
123
|
+
slots,
|
|
124
|
+
rootBoundaries
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function assertRouteManifestTreePosition(kind, route, id, treePosition) {
|
|
128
|
+
if (treePosition !== void 0) return;
|
|
129
|
+
throw new Error(`[vinext] App route graph invariant violated: missing ${kind} tree position for ${id} on ${route.pattern}`);
|
|
130
|
+
}
|
|
131
|
+
function assertRouteManifestRootBoundary(kind, route, id, existingRootBoundaryId) {
|
|
132
|
+
if (existingRootBoundaryId === route.ids.rootBoundary) return;
|
|
133
|
+
throw new Error(`[vinext] App route graph invariant violated: ${kind} ${id} is shared across root boundaries (${existingRootBoundaryId ?? "none"} and ${route.ids.rootBoundary ?? "none"}) on ${route.pattern}`);
|
|
134
|
+
}
|
|
135
|
+
function createRouteManifestGraphVersion(segmentGraph) {
|
|
136
|
+
const stableShape = {
|
|
137
|
+
routes: sortedMapValues(segmentGraph.routes),
|
|
138
|
+
pages: sortedMapValues(segmentGraph.pages),
|
|
139
|
+
routeHandlers: sortedMapValues(segmentGraph.routeHandlers),
|
|
140
|
+
layouts: sortedMapValues(segmentGraph.layouts),
|
|
141
|
+
templates: sortedMapValues(segmentGraph.templates),
|
|
142
|
+
slots: sortedMapValues(segmentGraph.slots),
|
|
143
|
+
rootBoundaries: sortedMapValues(segmentGraph.rootBoundaries)
|
|
144
|
+
};
|
|
145
|
+
return `graph:${createHash("sha256").update(JSON.stringify(stableShape)).digest("hex")}`;
|
|
146
|
+
}
|
|
13
147
|
async function buildAppRouteGraph(appDir, matcher) {
|
|
14
148
|
const routes = [];
|
|
15
149
|
const excludeDir = (name) => name.startsWith("@") || name.startsWith("_");
|
|
@@ -38,7 +172,10 @@ async function buildAppRouteGraph(appDir, matcher) {
|
|
|
38
172
|
validateRoutePatterns(routes.map((route) => route.pattern));
|
|
39
173
|
validateRoutePatterns([...new Set(routes.flatMap((route) => route.parallelSlots.flatMap((slot) => slot.interceptingRoutes.map((intercept) => intercept.targetPattern))))]);
|
|
40
174
|
routes.sort(compareRoutes);
|
|
41
|
-
return {
|
|
175
|
+
return {
|
|
176
|
+
routes,
|
|
177
|
+
routeManifest: createRouteManifest(routes)
|
|
178
|
+
};
|
|
42
179
|
}
|
|
43
180
|
function hasParallelSlotDirectory(dir) {
|
|
44
181
|
try {
|
|
@@ -99,8 +236,9 @@ function discoverSlotSubRoutes(routes, matcher) {
|
|
|
99
236
|
};
|
|
100
237
|
for (const parentRoute of routes) {
|
|
101
238
|
if (parentRoute.parallelSlots.length === 0) continue;
|
|
102
|
-
|
|
103
|
-
|
|
239
|
+
const isLayoutOnlyUiRoute = !parentRoute.pagePath && !parentRoute.routePath && parentRoute.layouts.length > 0;
|
|
240
|
+
if (!parentRoute.pagePath && !isLayoutOnlyUiRoute) continue;
|
|
241
|
+
const parentPageDir = parentRoute.pagePath ? path.dirname(parentRoute.pagePath) : path.dirname(parentRoute.layouts[parentRoute.layouts.length - 1]);
|
|
104
242
|
const subPathMap = /* @__PURE__ */ new Map();
|
|
105
243
|
for (const slot of parentRoute.parallelSlots) {
|
|
106
244
|
if (path.dirname(slot.ownerDir) !== parentPageDir) continue;
|
|
@@ -131,7 +269,7 @@ function discoverSlotSubRoutes(routes, matcher) {
|
|
|
131
269
|
}
|
|
132
270
|
if (subPathMap.size === 0) continue;
|
|
133
271
|
const childrenDefault = findFile(parentPageDir, "default", matcher);
|
|
134
|
-
if (!childrenDefault) continue;
|
|
272
|
+
if (parentRoute.pagePath && !childrenDefault) continue;
|
|
135
273
|
for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
|
|
136
274
|
const { urlSegments: urlParts, params: subParams, isDynamic: subIsDynamic } = convertedSubRoute;
|
|
137
275
|
const subUrlPath = urlParts.join("/");
|
|
@@ -142,6 +280,8 @@ function discoverSlotSubRoutes(routes, matcher) {
|
|
|
142
280
|
applySlotSubPages(existingRoute, slotPages, rawSegments);
|
|
143
281
|
continue;
|
|
144
282
|
}
|
|
283
|
+
const syntheticParts = [...parentRoute.patternParts, ...urlParts];
|
|
284
|
+
if (Array.from(routesByPattern.values()).some((r) => patternsStructurallyEquivalent(r.patternParts, syntheticParts))) continue;
|
|
145
285
|
const subSlots = parentRoute.parallelSlots.map((slot) => {
|
|
146
286
|
const subPage = slotPages.get(slot.key);
|
|
147
287
|
return {
|
|
@@ -151,6 +291,15 @@ function discoverSlotSubRoutes(routes, matcher) {
|
|
|
151
291
|
};
|
|
152
292
|
});
|
|
153
293
|
const newRoute = {
|
|
294
|
+
ids: createAppRouteSemanticIds({
|
|
295
|
+
pattern,
|
|
296
|
+
pagePath: childrenDefault,
|
|
297
|
+
routePath: null,
|
|
298
|
+
routeSegments: [...parentRoute.routeSegments, ...rawSegments],
|
|
299
|
+
layoutTreePositions: parentRoute.layoutTreePositions,
|
|
300
|
+
templateTreePositions: parentRoute.templateTreePositions,
|
|
301
|
+
slots: subSlots
|
|
302
|
+
}),
|
|
154
303
|
pattern,
|
|
155
304
|
pagePath: childrenDefault,
|
|
156
305
|
routePath: null,
|
|
@@ -245,6 +394,15 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
|
|
|
245
394
|
const unauthorizedPaths = discoverBoundaryFilePerLayout(layouts, "unauthorized", matcher);
|
|
246
395
|
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
|
|
247
396
|
return {
|
|
397
|
+
ids: createAppRouteSemanticIds({
|
|
398
|
+
pattern: pattern === "/" ? "/" : pattern,
|
|
399
|
+
pagePath,
|
|
400
|
+
routePath,
|
|
401
|
+
routeSegments: segments,
|
|
402
|
+
layoutTreePositions,
|
|
403
|
+
templateTreePositions,
|
|
404
|
+
slots: parallelSlots
|
|
405
|
+
}),
|
|
248
406
|
pattern: pattern === "/" ? "/" : pattern,
|
|
249
407
|
pagePath,
|
|
250
408
|
routePath,
|
|
@@ -285,6 +443,29 @@ function computeRootParamNames(routeSegments, layoutTreePositions) {
|
|
|
285
443
|
}
|
|
286
444
|
return names;
|
|
287
445
|
}
|
|
446
|
+
function resolveRootBoundaryId(routeSegments, layoutTreePositions) {
|
|
447
|
+
const rootLayoutPosition = layoutTreePositions[0];
|
|
448
|
+
if (rootLayoutPosition === void 0) return null;
|
|
449
|
+
return createAppRouteGraphRootBoundaryId(createAppRouteGraphTreePath(routeSegments, rootLayoutPosition));
|
|
450
|
+
}
|
|
451
|
+
function createAppRouteSemanticIds(input) {
|
|
452
|
+
const slots = {};
|
|
453
|
+
for (const slot of input.slots) slots[slot.key] = slot.id;
|
|
454
|
+
return {
|
|
455
|
+
route: createAppRouteGraphRouteId(input.pattern),
|
|
456
|
+
page: input.pagePath ? createAppRouteGraphPageId(input.pattern) : null,
|
|
457
|
+
routeHandler: input.routePath ? createAppRouteGraphRouteHandlerId(input.pattern) : null,
|
|
458
|
+
rootBoundary: resolveRootBoundaryId(input.routeSegments, input.layoutTreePositions),
|
|
459
|
+
layouts: input.layoutTreePositions.map((treePosition) => createAppRouteGraphLayoutId(createAppRouteGraphTreePath(input.routeSegments, treePosition))),
|
|
460
|
+
templates: (input.templateTreePositions ?? []).map((treePosition) => createAppRouteGraphTemplateId(createAppRouteGraphTreePath(input.routeSegments, treePosition))),
|
|
461
|
+
slots
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function createAppRouteGraphTreePath(routeSegments, treePosition) {
|
|
465
|
+
const treePathSegments = routeSegments.slice(0, treePosition);
|
|
466
|
+
if (treePathSegments.length === 0) return "/";
|
|
467
|
+
return `/${treePathSegments.join("/")}`;
|
|
468
|
+
}
|
|
288
469
|
/**
|
|
289
470
|
* Compute the tree position (directory depth from app root) for each layout.
|
|
290
471
|
* Root layout = 0, a layout at app/blog/ = 1, app/blog/(group)/ = 2.
|
|
@@ -545,6 +726,22 @@ function scoreSlotPattern(urlSegments) {
|
|
|
545
726
|
return score;
|
|
546
727
|
}
|
|
547
728
|
/**
|
|
729
|
+
* Map a pattern segment to the tree-node type used by Next.js' route
|
|
730
|
+
* validator. Two segments are structurally equivalent iff they share the
|
|
731
|
+
* same tree-node type.
|
|
732
|
+
*/
|
|
733
|
+
function segmentTreeNodeType(seg) {
|
|
734
|
+
if (!seg.startsWith(":")) return `literal:${seg}`;
|
|
735
|
+
if (seg.endsWith("*")) return "optionalCatchAll";
|
|
736
|
+
if (seg.endsWith("+")) return "catchAll";
|
|
737
|
+
return "dynamic";
|
|
738
|
+
}
|
|
739
|
+
function patternsStructurallyEquivalent(a, b) {
|
|
740
|
+
if (a.length !== b.length) return false;
|
|
741
|
+
for (let i = 0; i < a.length; i++) if (segmentTreeNodeType(a[i]) !== segmentTreeNodeType(b[i])) return false;
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
548
745
|
* Discover parallel route slots (@team, @analytics, etc.) in a directory.
|
|
549
746
|
* Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
|
|
550
747
|
*/
|
|
@@ -560,7 +757,10 @@ function discoverParallelSlots(dir, appDir, matcher) {
|
|
|
560
757
|
const defaultPath = findFile(slotDir, "default", matcher);
|
|
561
758
|
const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
|
|
562
759
|
if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
|
|
760
|
+
const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
|
|
761
|
+
const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
|
|
563
762
|
slots.push({
|
|
763
|
+
id: createAppRouteGraphSlotId(slotName, ownerTreePath),
|
|
564
764
|
key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
|
|
565
765
|
name: slotName,
|
|
566
766
|
ownerDir: slotDir,
|
|
@@ -774,24 +974,27 @@ function convertSegmentsToRouteParts(segments) {
|
|
|
774
974
|
for (let i = 0; i < segments.length; i++) {
|
|
775
975
|
const segment = segments[i];
|
|
776
976
|
if (isInvisibleSegment(segment)) continue;
|
|
777
|
-
const catchAllMatch = segment.match(/^\[\.\.\.([
|
|
977
|
+
const catchAllMatch = segment.match(/^\[\.\.\.([^\]]+)\]$/);
|
|
778
978
|
if (catchAllMatch) {
|
|
779
979
|
if (hasRemainingVisibleSegments(segments, i + 1)) return null;
|
|
980
|
+
if (catchAllMatch[1].endsWith("+") || catchAllMatch[1].endsWith("*")) return null;
|
|
780
981
|
isDynamic = true;
|
|
781
982
|
params.push(catchAllMatch[1]);
|
|
782
983
|
urlSegments.push(`:${catchAllMatch[1]}+`);
|
|
783
984
|
continue;
|
|
784
985
|
}
|
|
785
|
-
const optionalCatchAllMatch = segment.match(/^\[\[\.\.\.([
|
|
986
|
+
const optionalCatchAllMatch = segment.match(/^\[\[\.\.\.([^\]]+)\]\]$/);
|
|
786
987
|
if (optionalCatchAllMatch) {
|
|
787
988
|
if (hasRemainingVisibleSegments(segments, i + 1)) return null;
|
|
989
|
+
if (optionalCatchAllMatch[1].endsWith("+") || optionalCatchAllMatch[1].endsWith("*")) return null;
|
|
788
990
|
isDynamic = true;
|
|
789
991
|
params.push(optionalCatchAllMatch[1]);
|
|
790
992
|
urlSegments.push(`:${optionalCatchAllMatch[1]}*`);
|
|
791
993
|
continue;
|
|
792
994
|
}
|
|
793
|
-
const dynamicMatch = segment.match(/^\[([
|
|
995
|
+
const dynamicMatch = segment.match(/^\[([^\]]+)\]$/);
|
|
794
996
|
if (dynamicMatch) {
|
|
997
|
+
if (dynamicMatch[1].endsWith("+") || dynamicMatch[1].endsWith("*")) return null;
|
|
795
998
|
isDynamic = true;
|
|
796
999
|
params.push(dynamicMatch[1]);
|
|
797
1000
|
urlSegments.push(`:${dynamicMatch[1]}`);
|