cloudflare-next-intl 0.9.43 → 0.9.45
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 +2 -1
- package/dist/src/image_optimizer/types.js +1 -1
- package/dist/src/vite/index.d.ts +1 -0
- package/dist/src/vite/index.js +1 -0
- package/dist/src/vite/plugin.d.ts +2 -0
- package/dist/src/vite/plugin.js +4 -0
- package/dist/src/vite/vinext_route_wiring_fix.d.ts +25 -0
- package/dist/src/vite/vinext_route_wiring_fix.js +268 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -261,7 +261,7 @@ export default defineConfig({
|
|
|
261
261
|
The scan (`checkDynamicPages`, also usable standalone from `cloudflare-next-intl/checkDynamicPages`) is a text heuristic, not a real parser, so it's deliberately conservative and follows a page's own local (relative/`@/`-alias) imports transitively — cycle-safe, capped at 300 files — so a signal several files away (a component's repository calling `cookies()`) still marks the page dynamic, not just literal text in the page file itself. A locally-imported file that opens with a `"use server"` directive is never opened by the scan (its exports are Server Actions, invoked only on explicit call — never merely by being imported), the same treatment a bare npm-package import already gets. Recognized signals: `cookies()`, `headers()`, `searchParams`, `unstable_noStore()`, `connection()`, `cache: "no-store"`, `next: { revalidate: 0 }`, and this package's own `getAuthUser()`/`useAuthUser()`/`withUserDb()` (each of which reads `cookies()` internally) — except a `useAuthUser()` call in a file that opens with `"use client"`, which is this package's client-side hook (a different export under the same name) and contributes no signal. Set `resolveImports: false` on `checkDynamicPages` to restore the original single-file-only scan, or pass `aliases` to override the default `@/` → `<appDir>/..` mapping.
|
|
262
262
|
|
|
263
263
|
Being text-only and transitive, it can over-flag: any local file the page reaches — however many imports away — that merely *calls* a recognized signal counts, even along a branch that never runs in production (a `Config.isDev`-gated `fetch(..., { cache: "no-store" })`) or one that's optional/best-effort (a `try`/`catch`-wrapped `getAuthUser()` used only to tag a log line). It cannot see that a call is conditional or swallowed. For a page whose only reason for being flagged is that kind of optional read — attaching "whoever's signed in, if anyone" to an error report, analytics event, or log line — switch that read to `resolveOptionalAuthUser()` (`cloudflare-next-intl/resolveOptionalAuthUser`, see Firebase Auth below): it wraps `getAuthUser()` the same way but, being an npm-package import, is a boundary the scan doesn't open, so it contributes no signal. If that read lives in a shared `onError` sink used by many pages at once, `resolveErrorReportingUser(useAuthUser?)` (same subpath) is the more precise fix: it's off (`{ user: null }`, no `getAuthUser()` call) by default, and only a `reportError({ ..., useAuthUser: true })` call site that actually wants the user on that report opts in per-call — instead of every page reaching that sink getting flagged.
|
|
264
|
-
2. **Build-Time
|
|
264
|
+
2. **Build-Time Image Optimizer (`imageOptimizer`)**: Automatically scans your image directories (`public/images`, `public/icons`), downscales oversized assets, produces sibling formats (`webp` by default; also supports `avif`, `png`, `jpeg`, `gif`, `tiff`, `heif`, `jp2`, `jxl`), generates 8px `.blur.webp` thumbnails with Next.js-matching SVG Gaussian blur placeholders, and provides transparent `<Image placeholder="blur" />` shimming via virtual modules. When more than one format is generated for an image, the shim renders a `<picture>` with one `<source>` per format — ordered exactly as configured — so the browser picks the best format it supports, with the original untouched file as an `onError` fallback if a generated asset fails to load. When the same image is used at different widths across the codebase, each size gets its own generated variant, and each `<Image>` usage automatically resolves to the closest matching size. Runs on production build only by default (`dev: false`) — downscaling/format conversion is a build concern, and re-scanning on every dev server start slows cold starts for no dev-time benefit; the shim degrades cleanly with no manifest (plain, unoptimized `next/image` rendering, no blur placeholder). Pass `dev: true` to also run it in dev and preview real optimized output/blur.
|
|
265
265
|
3. **Locale File Bundling & Resolution (`localeFiles`)**: Resolves `@locale-file/*` to your `./messages` directory and transforms dynamic imports into `import.meta.glob('/messages/*.json', { eager: true })` for lightning-fast locale loading on Cloudflare Workers.
|
|
266
266
|
4. **User-Agent Stub (`userAgentStub`)**: Prevents Next.js `user-agent` from importing `node:fs` during workerd runtime execution (which otherwise causes runtime 404 / 500 crashes in Workers proxy/middleware).
|
|
267
267
|
5. **Cloudflare Workers Client Stub (`cfWorkersClientStub`)**: Stubs `cloudflare:workers` in client builds so shared modules can be referenced without client bundling errors.
|
|
@@ -278,6 +278,7 @@ export default defineConfig({
|
|
|
278
278
|
plugins: [
|
|
279
279
|
cloudflareNextIntl({
|
|
280
280
|
imageOptimizer: { // Image optimizer configuration (or `false` to disable)
|
|
281
|
+
dev: false, // Also run the scan on dev server start, not just production build (default: false)
|
|
281
282
|
maxWidth: 1920, // Downscale max width limit (default: 1920, or `false`)
|
|
282
283
|
formats: ["avif", "webp"], // Target sibling formats, in browser-preference order (default: ["webp"], or `false`).
|
|
283
284
|
// Also supports: "png", "jpeg", "gif", "tiff", "heif", "jp2", "jxl"
|
|
@@ -15,7 +15,7 @@ export const DEFAULT_OPTIONS = {
|
|
|
15
15
|
formats: ["webp"],
|
|
16
16
|
manifest: "public/generated/images.json",
|
|
17
17
|
blur: DEFAULT_BLUR_OPTIONS,
|
|
18
|
-
dev:
|
|
18
|
+
dev: false,
|
|
19
19
|
cacheDir: "node_modules/.cache/cloudflare-next-intl/image-optimizer",
|
|
20
20
|
onlyUsed: true,
|
|
21
21
|
overrides: {},
|
package/dist/src/vite/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { autoDynamicPagesPlugin, type AutoDynamicPagesPluginOptions } from "./au
|
|
|
2
2
|
export { buildIdAsset } from "./build_id_asset.js";
|
|
3
3
|
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
4
4
|
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
|
5
|
+
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
|
|
5
6
|
export { localeFilePlugin, resolveDefaultIntlConfigPath, type LocaleFilePluginOptions } from "./locale_file_plugin.js";
|
|
6
7
|
export { cloudflareNextIntl, cloudflareNextIntlPlugin, type CloudflareNextIntlOptions, default } from "./plugin.js";
|
|
7
8
|
export { imageOptimizer, imageOptimizerPlugin, VIRTUAL_IMAGE_SHIM_ID, type ImageFormat, type ImageBlurOptions, type ImageOverrideOptions, type ImageOptimizerPluginOptions, type ResolvedBlurOptions, type ResolvedOptions, type ResolvedImageConfig, type OptimizedImage, type ManifestData, type ManifestEntry, } from "../image_optimizer/index.js";
|
package/dist/src/vite/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
|
|
|
2
2
|
export { buildIdAsset } from "./build_id_asset.js";
|
|
3
3
|
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
4
4
|
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
|
5
|
+
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed } from "./vinext_route_wiring_fix.js";
|
|
5
6
|
export { localeFilePlugin, resolveDefaultIntlConfigPath } from "./locale_file_plugin.js";
|
|
6
7
|
export { cloudflareNextIntl, cloudflareNextIntlPlugin, default } from "./plugin.js";
|
|
7
8
|
export { imageOptimizer, imageOptimizerPlugin, VIRTUAL_IMAGE_SHIM_ID, } from "../image_optimizer/index.js";
|
|
@@ -2,6 +2,7 @@ import type { Plugin } from "vite";
|
|
|
2
2
|
import { type LocaleFilePluginOptions } from "./locale_file_plugin.js";
|
|
3
3
|
import { type ImageOptimizerPluginOptions } from "../image_optimizer/index.js";
|
|
4
4
|
import { type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
|
|
5
|
+
import { type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
|
|
5
6
|
export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
6
7
|
autoDynamicPages?: boolean | AutoDynamicPagesPluginOptions;
|
|
7
8
|
buildIdAsset?: boolean | string;
|
|
@@ -9,6 +10,7 @@ export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
|
9
10
|
userAgentStub?: boolean;
|
|
10
11
|
cfWorkersClientStub?: boolean;
|
|
11
12
|
imageOptimizer?: boolean | ImageOptimizerPluginOptions;
|
|
13
|
+
vinextRouteWiringFix?: boolean | VinextRouteWiringFixPluginOptions;
|
|
12
14
|
}
|
|
13
15
|
export declare function cloudflareNextIntl(options?: CloudflareNextIntlOptions): Plugin[];
|
|
14
16
|
export declare const cloudflareNextIntlPlugin: typeof cloudflareNextIntl;
|
package/dist/src/vite/plugin.js
CHANGED
|
@@ -4,6 +4,7 @@ import { cfWorkersClientStubPlugin } from "./cf_workers_client_stub.js";
|
|
|
4
4
|
import { localeFilePlugin } from "./locale_file_plugin.js";
|
|
5
5
|
import { imageOptimizerPlugin } from "../image_optimizer/index.js";
|
|
6
6
|
import { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
|
|
7
|
+
import { vinextRouteWiringFixPlugin } from "./vinext_route_wiring_fix.js";
|
|
7
8
|
export function cloudflareNextIntl(options = {}) {
|
|
8
9
|
const plugins = [];
|
|
9
10
|
if (options.autoDynamicPages !== false) {
|
|
@@ -26,6 +27,9 @@ export function cloudflareNextIntl(options = {}) {
|
|
|
26
27
|
if (options.userAgentStub !== false) {
|
|
27
28
|
plugins.push(userAgentStubPlugin());
|
|
28
29
|
}
|
|
30
|
+
if (options.vinextRouteWiringFix !== false) {
|
|
31
|
+
plugins.push(vinextRouteWiringFixPlugin(typeof options.vinextRouteWiringFix === "object" ? options.vinextRouteWiringFix : {}));
|
|
32
|
+
}
|
|
29
33
|
if (options.localeFiles !== false) {
|
|
30
34
|
plugins.push(localeFilePlugin({
|
|
31
35
|
messagesDir: options.messagesDir,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export declare function isAppPageRouteWiringAlreadyFixed(code: string): boolean;
|
|
3
|
+
export declare function patchAppPageRouteWiring(code: string): string;
|
|
4
|
+
export declare function isRouteMatchingFile(id: string): boolean;
|
|
5
|
+
export declare function isRouteMatchingAlreadyFixed(code: string): boolean;
|
|
6
|
+
export declare function patchRouteMatching(code: string): string;
|
|
7
|
+
export declare function isOptimisticRoutingFile(id: string): boolean;
|
|
8
|
+
export declare function isOptimisticRoutingAlreadyFixed(code: string): boolean;
|
|
9
|
+
export declare function patchOptimisticRouting(code: string): string;
|
|
10
|
+
export declare function isAppPageRouteWiringFile(id: string): boolean;
|
|
11
|
+
export declare function resolveVinextAppPageRouteWiringPath(root?: string): string | null;
|
|
12
|
+
export declare function resolveVinextRouteMatchingPath(root?: string): string | null;
|
|
13
|
+
export declare function resolveVinextOptimisticRoutingPath(root?: string): string | null;
|
|
14
|
+
export interface SyncPatchVinextOnDiskOptions {
|
|
15
|
+
routeWiring?: boolean;
|
|
16
|
+
routeMatching?: boolean;
|
|
17
|
+
optimisticRouting?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function syncPatchVinextOnDisk(root?: string, options?: SyncPatchVinextOnDiskOptions): boolean;
|
|
20
|
+
export interface VinextRouteWiringFixPluginOptions {
|
|
21
|
+
routeWiring?: boolean;
|
|
22
|
+
routeMatching?: boolean;
|
|
23
|
+
optimisticRouting?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function vinextRouteWiringFixPlugin(options?: VinextRouteWiringFixPluginOptions): Plugin;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
const PREFETCH_LOADING_FN_RE = /function\s+getPrefetchLoadingEntry\s*\(\s*route\s*\)\s*\{[\s\S]*?return\s+getDefaultExport\s*\(\s*route\.loading\s*\)\s*\?[\s\S]*?:\s*null\s*;\s*\}/;
|
|
4
|
+
const FIXED_PREFETCH_LOADING_FN = `function getPrefetchLoadingEntry(route) {
|
|
5
|
+
let rootEntry = null;
|
|
6
|
+
let deepestNestedEntry = null;
|
|
7
|
+
for (const [index, loadingModule] of (route.loadings ?? []).entries()) {
|
|
8
|
+
if (!getDefaultExport(loadingModule)) continue;
|
|
9
|
+
const treePosition = route.loadingTreePositions?.[index];
|
|
10
|
+
if (treePosition === void 0) continue;
|
|
11
|
+
if (treePosition === 0) rootEntry ??= {
|
|
12
|
+
loadingModule,
|
|
13
|
+
treePosition
|
|
14
|
+
};
|
|
15
|
+
else if (deepestNestedEntry === null || treePosition > deepestNestedEntry.treePosition) deepestNestedEntry = {
|
|
16
|
+
loadingModule,
|
|
17
|
+
treePosition
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const leafEntry = getDefaultExport(route.loading) ? {
|
|
21
|
+
loadingModule: route.loading,
|
|
22
|
+
treePosition: route.routeSegments?.length ?? 0
|
|
23
|
+
} : null;
|
|
24
|
+
if (leafEntry && (!deepestNestedEntry || leafEntry.treePosition >= deepestNestedEntry.treePosition)) return leafEntry;
|
|
25
|
+
if (deepestNestedEntry) return deepestNestedEntry;
|
|
26
|
+
if (rootEntry) return rootEntry;
|
|
27
|
+
return null;
|
|
28
|
+
}`;
|
|
29
|
+
const ROUTE_LOADING_GUARD_RE = /if\s*\(\s*!isPrefetchLoadingShell\s*&&\s*treePosition\s*<\s*routeSegments\.length\s*\)\s*\{/g;
|
|
30
|
+
const FIXED_ROUTE_LOADING_GUARD = "if (!isPrefetchLoadingShell && treePosition < routeSegments.length && !routeLoadingComponent) {";
|
|
31
|
+
export function isAppPageRouteWiringAlreadyFixed(code) {
|
|
32
|
+
const hasBuggyPrefetch = code.includes("firstNestedEntry") && PREFETCH_LOADING_FN_RE.test(code);
|
|
33
|
+
const hasBuggySuspense = !code.includes("!routeLoadingComponent") && ROUTE_LOADING_GUARD_RE.test(code);
|
|
34
|
+
return !hasBuggyPrefetch && !hasBuggySuspense;
|
|
35
|
+
}
|
|
36
|
+
export function patchAppPageRouteWiring(code) {
|
|
37
|
+
if (isAppPageRouteWiringAlreadyFixed(code)) {
|
|
38
|
+
return code;
|
|
39
|
+
}
|
|
40
|
+
let result = code;
|
|
41
|
+
const hasBuggyPrefetch = code.includes("firstNestedEntry") && PREFETCH_LOADING_FN_RE.test(code);
|
|
42
|
+
if (hasBuggyPrefetch && !result.includes("deepestNestedEntry")) {
|
|
43
|
+
result = result.replace(PREFETCH_LOADING_FN_RE, FIXED_PREFETCH_LOADING_FN);
|
|
44
|
+
}
|
|
45
|
+
const hasBuggySuspense = !result.includes("!routeLoadingComponent") && ROUTE_LOADING_GUARD_RE.test(result);
|
|
46
|
+
if (hasBuggySuspense) {
|
|
47
|
+
result = result.replace(ROUTE_LOADING_GUARD_RE, FIXED_ROUTE_LOADING_GUARD);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
export function isRouteMatchingFile(id) {
|
|
52
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
53
|
+
return cleanId.endsWith("/routing/route-matching.js") || cleanId.endsWith("/routing/route-matching.ts");
|
|
54
|
+
}
|
|
55
|
+
export function isRouteMatchingAlreadyFixed(code) {
|
|
56
|
+
return code.includes("hasLeadingLocaleParam");
|
|
57
|
+
}
|
|
58
|
+
const MATCH_ROUTE_WITH_TRIE_RE = /function\s+matchRouteWithTrie\s*\(\s*url\s*,\s*routes\s*,\s*cache\s*\)\s*\{[\s\S]*?return\s+trieMatch\([\s\S]*?\);\s*\}/;
|
|
59
|
+
const FIXED_MATCH_ROUTE_WITH_TRIE = `function getActiveRouteLocale() {
|
|
60
|
+
return (typeof document !== "undefined" && (document.documentElement?.lang || document.cookie.match(/__user_locale_key__=([^;]+)/)?.[1])) || (typeof window !== "undefined" && window.__VINEXT_LOCALE__) || "en";
|
|
61
|
+
}
|
|
62
|
+
function matchRouteWithTrie(url, routes, cache) {
|
|
63
|
+
const pathname = url.split("?")[0];
|
|
64
|
+
let normalizedUrl = pathname === "/" ? "/" : pathname.replace(/\\/$/, "");
|
|
65
|
+
normalizedUrl = normalizePathnameForRouteMatch(normalizedUrl);
|
|
66
|
+
const urlParts = normalizedUrl.split("/").filter(Boolean);
|
|
67
|
+
const trie = getOrBuildTrie(cache, routes);
|
|
68
|
+
const hasLeadingLocaleParam = routes.some((r) => r.patternParts?.[0] === ":locale");
|
|
69
|
+
if (hasLeadingLocaleParam) {
|
|
70
|
+
const activeLocale = getActiveRouteLocale();
|
|
71
|
+
if (urlParts[0] !== activeLocale) {
|
|
72
|
+
const matchWithLocale = trieMatch(trie, [activeLocale, ...urlParts]);
|
|
73
|
+
if (matchWithLocale) return matchWithLocale;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return trieMatch(trie, urlParts);
|
|
77
|
+
}`;
|
|
78
|
+
const MATCH_ROUTE_WITH_TRIE_RAW_RE = /function\s+matchRouteWithTrieRawPathname\s*\(\s*url\s*,\s*routes\s*,\s*cache\s*\)\s*\{[\s\S]*?return\s+trieMatch\([\s\S]*?\);\s*\}/;
|
|
79
|
+
const FIXED_MATCH_ROUTE_WITH_TRIE_RAW = `function matchRouteWithTrieRawPathname(url, routes, cache) {
|
|
80
|
+
const pathname = url.split("?")[0];
|
|
81
|
+
const urlParts = (pathname === "/" ? "/" : pathname.replace(/\\/$/, "")).split("/").filter(Boolean);
|
|
82
|
+
const trie = getOrBuildTrie(cache, routes);
|
|
83
|
+
const hasLeadingLocaleParam = routes.some((r) => r.patternParts?.[0] === ":locale");
|
|
84
|
+
if (hasLeadingLocaleParam) {
|
|
85
|
+
const activeLocale = getActiveRouteLocale();
|
|
86
|
+
if (urlParts[0] !== activeLocale) {
|
|
87
|
+
const matchWithLocale = trieMatch(trie, [activeLocale, ...urlParts]);
|
|
88
|
+
if (matchWithLocale) return matchWithLocale;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return trieMatch(trie, urlParts);
|
|
92
|
+
}`;
|
|
93
|
+
export function patchRouteMatching(code) {
|
|
94
|
+
if (isRouteMatchingAlreadyFixed(code)) {
|
|
95
|
+
return code;
|
|
96
|
+
}
|
|
97
|
+
let result = code;
|
|
98
|
+
if (MATCH_ROUTE_WITH_TRIE_RE.test(result)) {
|
|
99
|
+
result = result.replace(MATCH_ROUTE_WITH_TRIE_RE, FIXED_MATCH_ROUTE_WITH_TRIE);
|
|
100
|
+
}
|
|
101
|
+
if (MATCH_ROUTE_WITH_TRIE_RAW_RE.test(result)) {
|
|
102
|
+
result = result.replace(MATCH_ROUTE_WITH_TRIE_RAW_RE, FIXED_MATCH_ROUTE_WITH_TRIE_RAW);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
export function isOptimisticRoutingFile(id) {
|
|
107
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
108
|
+
return cleanId.endsWith("/app-optimistic-routing.js") || cleanId.endsWith("/app-optimistic-routing.ts");
|
|
109
|
+
}
|
|
110
|
+
export function isOptimisticRoutingAlreadyFixed(code) {
|
|
111
|
+
const hasLocalePrefixFirst = code.includes("hasLeadingLocaleParam") &&
|
|
112
|
+
code.indexOf("hasLeadingLocaleParam") < code.indexOf("const match = matchNode(trie, urlParts.normalized");
|
|
113
|
+
const hasRawPartsFix = code.includes("options.rawUrlParts[0] !== options.match.params.locale");
|
|
114
|
+
return hasLocalePrefixFirst && hasRawPartsFix;
|
|
115
|
+
}
|
|
116
|
+
const MATCH_OPTIMISTIC_ROUTE_RE = /function\s+matchOptimisticRouteManifestRoute\s*\(\s*options\s*\)\s*\{[\s\S]*?const\s+trie\s*=\s*getRouteTrie\([\s\S]*?\);[\s\S]*?const\s+match\s*=\s*matchNode\([\s\S]*?\);[\s\S]*?return\s+null;\s*\}/;
|
|
117
|
+
const FIXED_MATCH_OPTIMISTIC_ROUTE = `function matchOptimisticRouteManifestRoute(options) {
|
|
118
|
+
const urlParts = hrefToRouteParts(options.href, options.basePath);
|
|
119
|
+
if (urlParts === null) return null;
|
|
120
|
+
const trie = getRouteTrie(options.routeManifest);
|
|
121
|
+
const hasLeadingLocaleParam = Array.from(options.routeManifest?.segmentGraph?.routes?.values() ?? []).some((r) => r.patternParts?.[0] === ":locale");
|
|
122
|
+
if (hasLeadingLocaleParam) {
|
|
123
|
+
const activeLocale = (typeof document !== "undefined" && (document.documentElement?.lang || document.cookie.match(/__user_locale_key__=([^;]+)/)?.[1])) || (typeof window !== "undefined" && window.__VINEXT_LOCALE__) || "en";
|
|
124
|
+
if (urlParts.normalized[0] !== activeLocale) {
|
|
125
|
+
const localeMatch = matchNode(trie, [activeLocale, ...urlParts.normalized], 0, []);
|
|
126
|
+
if (localeMatch !== null) {
|
|
127
|
+
decodeMatchedParams(localeMatch.params);
|
|
128
|
+
return localeMatch;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const match = matchNode(trie, urlParts.normalized, 0, []);
|
|
133
|
+
if (match !== null) {
|
|
134
|
+
decodeMatchedParams(match.params);
|
|
135
|
+
return match;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}`;
|
|
139
|
+
const RESOLVE_OPTIMISTIC_NAV_PARAMS_RE = /function\s+resolveOptimisticNavigationParams\s*\(\s*options\s*\)\s*\{[\s\S]*?const\s+routeParams\s*=\s*extractRawRoutePatternParams\s*\(\s*options\.match\.route\.patternParts\s*,\s*options\.rawUrlParts\s*\);/;
|
|
140
|
+
const FIXED_RESOLVE_OPTIMISTIC_NAV_PARAMS = `function resolveOptimisticNavigationParams(options) {
|
|
141
|
+
const rawParts = (options.match.route.patternParts?.[0] === ":locale" && options.rawUrlParts[0] !== options.match.params.locale)
|
|
142
|
+
? [options.match.params.locale, ...options.rawUrlParts]
|
|
143
|
+
: options.rawUrlParts;
|
|
144
|
+
const routeParams = extractRawRoutePatternParams(options.match.route.patternParts, rawParts);`;
|
|
145
|
+
export function patchOptimisticRouting(code) {
|
|
146
|
+
if (isOptimisticRoutingAlreadyFixed(code)) {
|
|
147
|
+
return code;
|
|
148
|
+
}
|
|
149
|
+
let result = code;
|
|
150
|
+
if (MATCH_OPTIMISTIC_ROUTE_RE.test(result)) {
|
|
151
|
+
result = result.replace(MATCH_OPTIMISTIC_ROUTE_RE, FIXED_MATCH_OPTIMISTIC_ROUTE);
|
|
152
|
+
}
|
|
153
|
+
if (RESOLVE_OPTIMISTIC_NAV_PARAMS_RE.test(result)) {
|
|
154
|
+
result = result.replace(RESOLVE_OPTIMISTIC_NAV_PARAMS_RE, FIXED_RESOLVE_OPTIMISTIC_NAV_PARAMS);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
export function isAppPageRouteWiringFile(id) {
|
|
159
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
160
|
+
return cleanId.endsWith("/app-page-route-wiring.js") || cleanId.endsWith("/app-page-route-wiring.tsx") || cleanId.endsWith("/app-page-route-wiring.ts");
|
|
161
|
+
}
|
|
162
|
+
export function resolveVinextAppPageRouteWiringPath(root = process.cwd()) {
|
|
163
|
+
const directPath = resolve(root, "node_modules/vinext/dist/server/app-page-route-wiring.js");
|
|
164
|
+
return existsSync(directPath) ? directPath : null;
|
|
165
|
+
}
|
|
166
|
+
export function resolveVinextRouteMatchingPath(root = process.cwd()) {
|
|
167
|
+
const directPath = resolve(root, "node_modules/vinext/dist/routing/route-matching.js");
|
|
168
|
+
return existsSync(directPath) ? directPath : null;
|
|
169
|
+
}
|
|
170
|
+
export function resolveVinextOptimisticRoutingPath(root = process.cwd()) {
|
|
171
|
+
const directPath = resolve(root, "node_modules/vinext/dist/server/app-optimistic-routing.js");
|
|
172
|
+
return existsSync(directPath) ? directPath : null;
|
|
173
|
+
}
|
|
174
|
+
export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
|
|
175
|
+
const { routeWiring = true, routeMatching = true, optimisticRouting = true } = options;
|
|
176
|
+
let changed = false;
|
|
177
|
+
const wiringPath = routeWiring ? resolveVinextAppPageRouteWiringPath(root) : null;
|
|
178
|
+
if (wiringPath) {
|
|
179
|
+
try {
|
|
180
|
+
const content = readFileSync(wiringPath, "utf8");
|
|
181
|
+
if (!isAppPageRouteWiringAlreadyFixed(content)) {
|
|
182
|
+
const patched = patchAppPageRouteWiring(content);
|
|
183
|
+
if (patched !== content) {
|
|
184
|
+
writeFileSync(wiringPath, patched, "utf8");
|
|
185
|
+
changed = true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const matchingPath = routeMatching ? resolveVinextRouteMatchingPath(root) : null;
|
|
193
|
+
if (matchingPath) {
|
|
194
|
+
try {
|
|
195
|
+
const content = readFileSync(matchingPath, "utf8");
|
|
196
|
+
if (!isRouteMatchingAlreadyFixed(content)) {
|
|
197
|
+
const patched = patchRouteMatching(content);
|
|
198
|
+
if (patched !== content) {
|
|
199
|
+
writeFileSync(matchingPath, patched, "utf8");
|
|
200
|
+
changed = true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const optimisticPath = optimisticRouting ? resolveVinextOptimisticRoutingPath(root) : null;
|
|
208
|
+
if (optimisticPath) {
|
|
209
|
+
try {
|
|
210
|
+
const content = readFileSync(optimisticPath, "utf8");
|
|
211
|
+
if (!isOptimisticRoutingAlreadyFixed(content)) {
|
|
212
|
+
const patched = patchOptimisticRouting(content);
|
|
213
|
+
if (patched !== content) {
|
|
214
|
+
writeFileSync(optimisticPath, patched, "utf8");
|
|
215
|
+
changed = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return changed;
|
|
223
|
+
}
|
|
224
|
+
export function vinextRouteWiringFixPlugin(options = {}) {
|
|
225
|
+
const routeWiring = options.routeWiring !== false;
|
|
226
|
+
const routeMatching = options.routeMatching !== false;
|
|
227
|
+
const optimisticRouting = options.optimisticRouting !== false;
|
|
228
|
+
return {
|
|
229
|
+
name: "cfni:vinext-route-wiring-fix",
|
|
230
|
+
enforce: "pre",
|
|
231
|
+
configResolved(config) {
|
|
232
|
+
const root = config.root || process.cwd();
|
|
233
|
+
syncPatchVinextOnDisk(root, { routeWiring, routeMatching, optimisticRouting });
|
|
234
|
+
},
|
|
235
|
+
transform(code, id) {
|
|
236
|
+
if (routeWiring && isAppPageRouteWiringFile(id)) {
|
|
237
|
+
if (isAppPageRouteWiringAlreadyFixed(code)) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const patched = patchAppPageRouteWiring(code);
|
|
241
|
+
return {
|
|
242
|
+
code: patched,
|
|
243
|
+
map: null,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (routeMatching && isRouteMatchingFile(id)) {
|
|
247
|
+
if (isRouteMatchingAlreadyFixed(code)) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const patched = patchRouteMatching(code);
|
|
251
|
+
return {
|
|
252
|
+
code: patched,
|
|
253
|
+
map: null,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (optimisticRouting && isOptimisticRoutingFile(id)) {
|
|
257
|
+
if (isOptimisticRoutingAlreadyFixed(code)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const patched = patchOptimisticRouting(code);
|
|
261
|
+
return {
|
|
262
|
+
code: patched,
|
|
263
|
+
map: null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|