cloudflare-next-intl 0.9.50 → 0.9.52
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 +25 -6
- package/bin/check_layout_queries.mjs +16 -0
- package/dist/src/firebase_auth/client/firebase_client.js +1 -1
- package/dist/src/layout_queries_check/check_layout_queries.d.ts +23 -0
- package/dist/src/layout_queries_check/check_layout_queries.js +172 -0
- package/dist/src/layout_queries_check/index.d.ts +1 -0
- package/dist/src/layout_queries_check/index.js +1 -0
- package/dist/src/server/components/helper_script.js +22 -1
- package/dist/src/vite/index.d.ts +2 -1
- package/dist/src/vite/index.js +2 -1
- package/dist/src/vite/layout_queries_plugin.d.ts +7 -0
- package/dist/src/vite/layout_queries_plugin.js +42 -0
- package/dist/src/vite/plugin.d.ts +2 -0
- package/dist/src/vite/plugin.js +7 -1
- package/dist/src/vite/vinext_route_wiring_fix.d.ts +58 -2
- package/dist/src/vite/vinext_route_wiring_fix.js +471 -25
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -267,8 +267,9 @@ export default defineConfig({
|
|
|
267
267
|
6. **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).
|
|
268
268
|
7. **Cloudflare Workers Client Stub (`cfWorkersClientStub`)**: Stubs `cloudflare:workers` in client builds so shared modules can be referenced without client bundling errors.
|
|
269
269
|
8. **Build ID Asset Emission (`buildIdAsset`)**: Emits `BUILD_ID` static asset in the client build directory from `process.env.__VINEXT_SHARED_BUILD_ID` or `process.env.__VINEXT_BUILD_ID`.
|
|
270
|
-
9. **Vinext Route Wiring & Optimistic Prefetch Fix (`vinextRouteWiringFix`)**:
|
|
271
|
-
10. **
|
|
270
|
+
9. **Vinext Route Wiring & Optimistic Prefetch Fix (`vinextRouteWiringFix`)**: ⚠️ Monkey-patches Vinext's compiled runtime on disk (`node_modules/vinext/dist/**`) — **on by default** since 0.9.51 via `experimentalRouteLoadingFixes` (also defaulting to on). Fixes route wiring (a route-specific `loading.tsx` losing to an ancestor/root skeleton), route matching and optimistic (client-side) routing for leading `:locale` segments, prefetch learning (an in-flight prefetch losing the race so the previous page stays on screen), and a `refresh()` navigation cancelling an in-flight page-to-page navigation because both share the same counter. Every sub-patch is regex-based against vinext's exact compiled shape and checks that the vinext-internal identifiers it relies on (`makeThenableParams`, `trieMatch`, `getPrefetchCache`, …) are still present before touching a file — a future vinext release that renames or removes one is left unpatched (with a console warning) rather than being patched into a broken half-state. Set `experimentalRouteLoadingFixes: false` (or `vinextRouteWiringFix: false`) to opt out entirely; see **Plugin Options** below for the individual sub-patch flags (`routeWiring`, `routeMatching`, `optimisticRouting`, `prefetchLearning`, `suspenseProbe`, `renderDependency`, `optimisticLearningTimeout`, `pageInvokerSuspensionRelease`, `refreshDeferral`, `unblockRenderDependencies`, `unblockPageElementDependencies`).
|
|
271
|
+
10. **Layout DB Query Check (`layoutQueriesCheck`)**: Scans each `layout.tsx`/`layout.ts` and its reachable server-component import tree (also usable standalone from `cloudflare-next-intl/checkLayoutQueries`, or via the `cfni-check-layout-queries` CLI bin) for blocking `withUserDb()` / `withPublicDb()` calls. A layout re-renders on every route transition within its group, so a DB query anywhere in that tree blocks every page switch under it — the scan stops at Client Components (`'use client'`), which don't block server layout streaming. Prints a visible terminal warning with concrete fixes (move to a Client Component, wrap in `unstable_cache`, or move the query out of the shared layout into the page) by default; pass `{ strict: true }` to fail the build instead, or `false` to disable.
|
|
272
|
+
11. **Lucide & Next.js Specifier Optimizer (`lucideOptimizer`)**: Auto-detects `lucide-react` in project dependencies, rewrites named imports to direct deep icon paths (`lucide-react/dist/esm/icons/<icon>.mjs`) to avoid browser socket exhaustion (`ERR_INSUFFICIENT_RESOURCES`), and normalizes Next.js `.js` specifiers (`next/dynamic.js` -> `next/dynamic`) to prevent mid-session Vite re-optimization and React dispatcher splitting.
|
|
272
273
|
|
|
273
274
|
##### Plugin Options
|
|
274
275
|
All features are enabled by default, and can be individually configured or toggled off:
|
|
@@ -299,22 +300,40 @@ export default defineConfig({
|
|
|
299
300
|
localeParam: "locale", // Route param name to read (default: "locale")
|
|
300
301
|
skip: ["src/app/[locale]/(marketing)/**"], // Glob(s) to exclude from the scan
|
|
301
302
|
},
|
|
303
|
+
layoutQueriesCheck: { // Flag blocking DB queries in the layout tree (or `false` to disable)
|
|
304
|
+
strict: false, // Fail the build on violations instead of just warning (default: false)
|
|
305
|
+
runOnDev: true, // Also run on `vite dev`, not just build (default: true)
|
|
306
|
+
},
|
|
302
307
|
messagesDir: "./messages", // Path to locale JSON files (default: './messages')
|
|
303
308
|
intlConfigPath: "./src/l18n/intl_config.ts", // Path to intl config (auto-detected if omitted)
|
|
304
309
|
buildIdAsset: true, // Emit BUILD_ID asset (or custom string filename, default: true)
|
|
305
310
|
localeFiles: true, // Enable @locale-file & glob bundling (default: true)
|
|
306
311
|
userAgentStub: true, // Enable regex-based user-agent stub (default: true)
|
|
307
312
|
cfWorkersClientStub: true, // Enable client cloudflare:workers stub (default: true)
|
|
308
|
-
vinextRouteWiringFix:
|
|
309
|
-
|
|
313
|
+
vinextRouteWiringFix: { // ⚠️ DANGER: Monkey-patches vinext on disk (default: follows experimentalRouteLoadingFixes, i.e. true; or options object)
|
|
314
|
+
routeWiring: true, // Route-specific loading.tsx wins over ancestor/root skeletons (default: true)
|
|
315
|
+
routeMatching: true, // Leading `:locale` segment tried against active locale first (default: true)
|
|
316
|
+
optimisticRouting: true, // Same fix client-side for optimistic (instant) navigation (default: true)
|
|
317
|
+
prefetchLearning: true, // Wait for an in-flight prefetch of the nav target instead of giving up (default: true)
|
|
318
|
+
optimisticLearningTimeout: true, // Lower the fixed safety-cap on that wait; pass a number for a custom ms cap (default: true → 200ms)
|
|
319
|
+
suspenseProbe: true, // Respect <Suspense> boundaries when probing for async page dependencies (default: true)
|
|
320
|
+
renderDependency: true, // Release a render dependency when its component suspends (default: true)
|
|
321
|
+
pageInvokerSuspensionRelease: true, // Same release, unconditionally, for the page component's own barrier (default: true)
|
|
322
|
+
refreshDeferral: true, // Defer a refresh() navigation while a normal navigation is in flight (default: true)
|
|
323
|
+
unblockRenderDependencies: true, // Strip layout/template/slot/route dependency ordering (default: true)
|
|
324
|
+
unblockPageElementDependencies: false, // Also strip it from the PAGE element itself (default: false — see warning below)
|
|
325
|
+
},
|
|
326
|
+
experimentalRouteLoadingFixes: true, // ⚠️ DANGER: Unified switch enabling both vinextRouteWiringFix and SSG on loading.* (default: true)
|
|
310
327
|
lucideOptimizer: true, // Auto-optimize lucide-react deep imports and normalize next/*.js (default: true, or options object)
|
|
311
328
|
}),
|
|
312
329
|
],
|
|
313
330
|
});
|
|
314
331
|
```
|
|
315
332
|
|
|
333
|
+
> **Note:** `unblockPageElementDependencies: true` was measured, in a real app, to make page-to-page navigation noticeably slower rather than faster — it strips ordering vinext's own page-element wiring relies on. Leave it off unless you've verified otherwise for your app.
|
|
334
|
+
|
|
316
335
|
Individual standalone plugins are also exported if you only need a specific feature:
|
|
317
|
-
`imageOptimizerPlugin` (or `imageOptimizer`), `autoLocaleParamsPlugin`, `buildIdAsset`, `localeFilePlugin`, `userAgentStubPlugin`, `cfWorkersClientStubPlugin`, `vinextRouteWiringFixPlugin`, `lucideOptimizerPlugin`.
|
|
336
|
+
`imageOptimizerPlugin` (or `imageOptimizer`), `autoLocaleParamsPlugin`, `layoutQueriesPlugin` (or `layoutQueriesCheck`), `buildIdAsset`, `localeFilePlugin`, `userAgentStubPlugin`, `cfWorkersClientStubPlugin`, `vinextRouteWiringFixPlugin`, `lucideOptimizerPlugin`.
|
|
318
337
|
|
|
319
338
|
##### Per-Image Optimizer Settings
|
|
320
339
|
|
|
@@ -671,7 +690,7 @@ through `@intl-config`.
|
|
|
671
690
|
|
|
672
691
|
When a new version of your application is deployed to Cloudflare Workers, users on older client sessions may encounter `ChunkLoadError` or failed dynamic imports when requesting outdated chunks.
|
|
673
692
|
|
|
674
|
-
`IntlHelperScript` renders an early-catch `<script>` (production only, id `stale-deploy-early-catch`) that runs before hydration and listens for `window.error`/`unhandledrejection` events matching the same patterns as `isStaleDeployError` (inlined as JSON, so it stays in sync with `staleDeployPatterns` config), then force-reloads once per build id. This covers the case a React-level recovery (`useStaleDeployRecovery` below) cannot: when the chunk that failed to load is part of your own error boundary/global-error bundle, React never gets a chance to render the recovery UI. Both layers share the same `sessionStorage['stale-deploy-recovery-reloaded']` marker keyed by build id, so they can't double-reload each other. No setup beyond rendering `<IntlHelperScript />` is required.
|
|
693
|
+
`IntlHelperScript` renders an early-catch `<script>` (production only, id `stale-deploy-early-catch`) that runs before hydration and listens for `window.error`/`unhandledrejection` events matching the same patterns as `isStaleDeployError` (inlined as JSON, so it stays in sync with `staleDeployPatterns` config), then force-reloads once per build id (throttled to once per 15s so a later build-id marker can re-arm recovery instead of being blocked indefinitely). It also listens for `error` events during the capture phase to catch resource-load failures (a chunk `<script>`/`<link>` 404ing or served with a disallowed MIME type), which fire a non-bubbling, message-less `error` event on the element itself rather than surfacing as a catchable message. This covers the case a React-level recovery (`useStaleDeployRecovery` below) cannot: when the chunk that failed to load is part of your own error boundary/global-error bundle, React never gets a chance to render the recovery UI. Both layers share the same `sessionStorage['stale-deploy-recovery-reloaded']` marker keyed by build id, so they can't double-reload each other. No setup beyond rendering `<IntlHelperScript />` is required.
|
|
675
694
|
|
|
676
695
|
For errors that don't crash the module graph itself (a normal thrown error reaching an error boundary), use `isStaleDeployError` and `clearClientCache` in error boundaries or global error handlers to automatically recover:
|
|
677
696
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { checkLayoutQueries } from "../dist/src/layout_queries_check/index.js";
|
|
3
|
+
|
|
4
|
+
const isStrict = process.argv.includes("--strict");
|
|
5
|
+
const report = checkLayoutQueries({
|
|
6
|
+
rootDir: process.cwd(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
if (!report.valid) {
|
|
10
|
+
console.error(report.formattedMessage);
|
|
11
|
+
if (isStrict) {
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
console.log("✅ [cloudflare-next-intl] No blocking database queries found in layout tree.");
|
|
16
|
+
}
|
|
@@ -157,7 +157,7 @@ export async function getFirebaseAuthClient() {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
if (perfModule) {
|
|
160
|
-
cachedPerformance = perfModule.
|
|
160
|
+
cachedPerformance = perfModule.initializePerformance(app, { instrumentationEnabled: false });
|
|
161
161
|
}
|
|
162
162
|
const auth = getAuth(app);
|
|
163
163
|
cached = { app, auth };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface LayoutDbViolation {
|
|
2
|
+
layoutFile: string;
|
|
3
|
+
sourceFile: string;
|
|
4
|
+
lineNumber: number;
|
|
5
|
+
lineContent: string;
|
|
6
|
+
signal: string;
|
|
7
|
+
importTrace: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface CheckLayoutQueriesOptions {
|
|
10
|
+
appDir?: string;
|
|
11
|
+
rootDir?: string;
|
|
12
|
+
aliases?: Record<string, string>;
|
|
13
|
+
maxDepth?: number;
|
|
14
|
+
throwOnError?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface CheckLayoutQueriesReport {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
violations: LayoutDbViolation[];
|
|
19
|
+
formattedMessage: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function findLayoutFiles(dir: string): string[];
|
|
22
|
+
export declare function formatLayoutDbViolationMessage(violations: LayoutDbViolation[]): string;
|
|
23
|
+
export declare function checkLayoutQueries(options?: CheckLayoutQueriesOptions): CheckLayoutQueriesReport;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
const DB_SIGNALS = [
|
|
4
|
+
{ name: "withUserDb()", pattern: /\bwithUserDb\s*\(/ },
|
|
5
|
+
{ name: "withPublicDb()", pattern: /\bwithPublicDb\s*\(/ },
|
|
6
|
+
];
|
|
7
|
+
const SUPPORTED_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs"];
|
|
8
|
+
function isClientComponent(content) {
|
|
9
|
+
return /^\s*["']use client["']/m.test(content);
|
|
10
|
+
}
|
|
11
|
+
export function findLayoutFiles(dir) {
|
|
12
|
+
const results = [];
|
|
13
|
+
if (!existsSync(dir))
|
|
14
|
+
return results;
|
|
15
|
+
function walk(currentDir) {
|
|
16
|
+
const entries = readdirSync(currentDir);
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (entry.startsWith(".") || entry === "node_modules")
|
|
19
|
+
continue;
|
|
20
|
+
const fullPath = join(currentDir, entry);
|
|
21
|
+
const stat = statSync(fullPath);
|
|
22
|
+
if (stat.isDirectory()) {
|
|
23
|
+
walk(fullPath);
|
|
24
|
+
}
|
|
25
|
+
else if (/^layout\.(tsx|ts|jsx|js)$/.test(entry)) {
|
|
26
|
+
results.push(fullPath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
walk(dir);
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
function resolveImportPath(specifier, importerFile, rootDir, aliases) {
|
|
34
|
+
for (const [alias, target] of Object.entries(aliases)) {
|
|
35
|
+
if (specifier === alias || specifier.startsWith(alias + "/")) {
|
|
36
|
+
const remainder = specifier.slice(alias.length).replace(/^\//, "");
|
|
37
|
+
const basePath = resolve(rootDir, target, remainder);
|
|
38
|
+
return probeExtensions(basePath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
42
|
+
const basePath = resolve(dirname(importerFile), specifier);
|
|
43
|
+
return probeExtensions(basePath);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function probeExtensions(basePath) {
|
|
48
|
+
if (existsSync(basePath) && statSync(basePath).isFile()) {
|
|
49
|
+
return basePath;
|
|
50
|
+
}
|
|
51
|
+
for (const ext of SUPPORTED_EXTENSIONS) {
|
|
52
|
+
const withExt = basePath + ext;
|
|
53
|
+
if (existsSync(withExt) && statSync(withExt).isFile()) {
|
|
54
|
+
return withExt;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const ext of SUPPORTED_EXTENSIONS) {
|
|
58
|
+
const indexFile = join(basePath, "index" + ext);
|
|
59
|
+
if (existsSync(indexFile) && statSync(indexFile).isFile()) {
|
|
60
|
+
return indexFile;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function extractImports(content) {
|
|
66
|
+
const imports = [];
|
|
67
|
+
const importRegex = /(?:import\s+(?:[\w*\s{},]*\s+from\s+)?|import\s*\(\s*)["']([^"']+)["']/g;
|
|
68
|
+
let match;
|
|
69
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
70
|
+
if (match[1]) {
|
|
71
|
+
imports.push(match[1]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return imports;
|
|
75
|
+
}
|
|
76
|
+
export function formatLayoutDbViolationMessage(violations) {
|
|
77
|
+
if (violations.length === 0)
|
|
78
|
+
return "";
|
|
79
|
+
const separator = "=".repeat(84);
|
|
80
|
+
const thinSeparator = "-".repeat(84);
|
|
81
|
+
const lines = [
|
|
82
|
+
"",
|
|
83
|
+
separator,
|
|
84
|
+
"🚨 [cloudflare-next-intl] BLOCKING DATABASE QUERY DETECTED IN LAYOUT",
|
|
85
|
+
separator,
|
|
86
|
+
"",
|
|
87
|
+
"⚠️ WHY THIS IS DANGEROUS:",
|
|
88
|
+
" Layouts are re-evaluated by the server on every route transition within their group.",
|
|
89
|
+
" Executing database queries (`withUserDb` / `withPublicDb`) inside the layout tree",
|
|
90
|
+
" blocks the entire RSC response on every page switch. This causes page navigation",
|
|
91
|
+
" to freeze/hang and completely breaks instant client transitions.",
|
|
92
|
+
"",
|
|
93
|
+
"📋 VIOLATIONS FOUND (" + violations.length + "):",
|
|
94
|
+
thinSeparator,
|
|
95
|
+
];
|
|
96
|
+
violations.forEach((v, i) => {
|
|
97
|
+
lines.push(` [${i + 1}] Signal: ${v.signal}`);
|
|
98
|
+
lines.push(` Layout: ${v.layoutFile}`);
|
|
99
|
+
lines.push(` File: ${v.sourceFile}:${v.lineNumber}`);
|
|
100
|
+
lines.push(` Line: ${v.lineContent.trim()}`);
|
|
101
|
+
if (v.importTrace.length > 1) {
|
|
102
|
+
lines.push(` Trace: ${v.importTrace.join(" -> ")}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push(thinSeparator);
|
|
105
|
+
});
|
|
106
|
+
lines.push("", "💡 HOW TO FIX:", " 1. Move to a Client Component ('use client'):", " Fetch the data in useEffect / SWR / React Query or a Supabase Realtime subscription.", " Client components do not block server layout streaming during navigation.", "", " 2. Use Cross-Request Caching (`unstable_cache`):", " Wrap the database query with `unstable_cache` (from 'next/cache') and a cache tag.", " This caches results in Cloudflare KV so layout renders in 0ms without hitting Postgres.", "", " 3. Move out of Shared Layout:", " If only a specific route needs this data, move the component from layout.tsx into that", " page's page.tsx.", separator, "");
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
export function checkLayoutQueries(options = {}) {
|
|
110
|
+
const rootDir = options.rootDir ?? process.cwd();
|
|
111
|
+
const appDir = options.appDir ?? resolve(rootDir, "src/app");
|
|
112
|
+
const aliases = options.aliases ?? {
|
|
113
|
+
"@": resolve(rootDir, "src"),
|
|
114
|
+
};
|
|
115
|
+
const maxDepth = options.maxDepth ?? 20;
|
|
116
|
+
const layoutFiles = findLayoutFiles(appDir);
|
|
117
|
+
const violations = [];
|
|
118
|
+
for (const layoutFile of layoutFiles) {
|
|
119
|
+
const visited = new Set();
|
|
120
|
+
function traverse(file, trace, depth) {
|
|
121
|
+
if (depth > maxDepth || visited.has(file))
|
|
122
|
+
return;
|
|
123
|
+
visited.add(file);
|
|
124
|
+
let content;
|
|
125
|
+
try {
|
|
126
|
+
content = readFileSync(file, "utf-8");
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (isClientComponent(content)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const lines = content.split("\n");
|
|
135
|
+
lines.forEach((line, index) => {
|
|
136
|
+
const trimmed = line.trim();
|
|
137
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*"))
|
|
138
|
+
return;
|
|
139
|
+
for (const signal of DB_SIGNALS) {
|
|
140
|
+
if (signal.pattern.test(line)) {
|
|
141
|
+
violations.push({
|
|
142
|
+
layoutFile,
|
|
143
|
+
sourceFile: file,
|
|
144
|
+
lineNumber: index + 1,
|
|
145
|
+
lineContent: line,
|
|
146
|
+
signal: signal.name,
|
|
147
|
+
importTrace: [...trace, file],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const imports = extractImports(content);
|
|
153
|
+
for (const imp of imports) {
|
|
154
|
+
const resolved = resolveImportPath(imp, file, rootDir, aliases);
|
|
155
|
+
if (resolved) {
|
|
156
|
+
traverse(resolved, [...trace, file], depth + 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
traverse(layoutFile, [], 0);
|
|
161
|
+
}
|
|
162
|
+
const valid = violations.length === 0;
|
|
163
|
+
const formattedMessage = formatLayoutDbViolationMessage(violations);
|
|
164
|
+
if (!valid && options.throwOnError) {
|
|
165
|
+
throw new Error(formattedMessage);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
valid,
|
|
169
|
+
violations,
|
|
170
|
+
formattedMessage,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkLayoutQueries, formatLayoutDbViolationMessage, findLayoutFiles, type LayoutDbViolation, type CheckLayoutQueriesOptions, type CheckLayoutQueriesReport, } from "./check_layout_queries.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkLayoutQueries, formatLayoutDbViolationMessage, findLayoutFiles, } from "./check_layout_queries.js";
|
|
@@ -17,6 +17,8 @@ export default function HelperScript() {
|
|
|
17
17
|
try {
|
|
18
18
|
var patterns = ${JSON.stringify(defaultStaleDeployPatterns)};
|
|
19
19
|
var key = 'stale-deploy-recovery-reloaded';
|
|
20
|
+
var timeKey = 'stale-deploy-recovery-time';
|
|
21
|
+
var throttleMs = 15000;
|
|
20
22
|
var attemptedThisLoad = false;
|
|
21
23
|
function isStale(msg) {
|
|
22
24
|
if (msg === undefined || msg === null) return true;
|
|
@@ -34,12 +36,16 @@ export default function HelperScript() {
|
|
|
34
36
|
if (!stale) return;
|
|
35
37
|
var buildId = localStorage.getItem('buildId') || 'unknown';
|
|
36
38
|
var marker = sessionStorage.getItem(key);
|
|
37
|
-
|
|
39
|
+
var lastRaw = sessionStorage.getItem(timeKey);
|
|
40
|
+
var last = lastRaw ? Number(lastRaw) : null;
|
|
41
|
+
var throttled = last !== null && (Date.now() - last) < throttleMs;
|
|
42
|
+
if (marker === buildId && throttled) {
|
|
38
43
|
console.warn('[StaleDeploy early-catch] Skipping reload, already attempted for buildId:', buildId);
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
41
46
|
attemptedThisLoad = true;
|
|
42
47
|
sessionStorage.setItem(key, buildId);
|
|
48
|
+
sessionStorage.setItem(timeKey, String(Date.now()));
|
|
43
49
|
try {
|
|
44
50
|
if (document.documentElement) {
|
|
45
51
|
document.documentElement.style.backgroundColor = '#ffffff';
|
|
@@ -61,6 +67,21 @@ export default function HelperScript() {
|
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
window.addEventListener('error', function(e) { recover(e.message, 'error-event'); });
|
|
70
|
+
// Resource-load failures (a chunk 404ing or served with a
|
|
71
|
+
// disallowed MIME type) fire a non-bubbling 'error' event on the
|
|
72
|
+
// element itself, so they only reach window during capture, and
|
|
73
|
+
// they carry no message. Treat a failed script/link as stale.
|
|
74
|
+
window.addEventListener('error', function(e) {
|
|
75
|
+
try {
|
|
76
|
+
var el = e.target;
|
|
77
|
+
if (!el || el === window) return;
|
|
78
|
+
var tag = (el.tagName || '').toLowerCase();
|
|
79
|
+
if (tag !== 'script' && tag !== 'link') return;
|
|
80
|
+
var src = el.src || el.href || '';
|
|
81
|
+
if (!src) return;
|
|
82
|
+
recover('chunk resource failed to load: ' + src, 'resource-error');
|
|
83
|
+
} catch (err) {}
|
|
84
|
+
}, true);
|
|
64
85
|
window.addEventListener('unhandledrejection', function(e) {
|
|
65
86
|
recover(e.reason && (e.reason.message || e.reason), 'unhandledrejection');
|
|
66
87
|
});
|
package/dist/src/vite/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export { autoDynamicPagesPlugin, type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
|
|
2
2
|
export { autoLocaleParamsPlugin, type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
|
|
3
|
+
export { layoutQueriesPlugin, type LayoutQueriesPluginOptions } from "./layout_queries_plugin.js";
|
|
3
4
|
export { buildIdAsset } from "./build_id_asset.js";
|
|
4
5
|
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
5
6
|
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
|
6
|
-
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk, type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
|
|
7
|
+
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk, patchAppPageProbe, isAppPageProbeFile, isAppPageProbeAlreadyFixed, isVinextAppPageProbeSafeOnDisk, type VinextRouteWiringFixPluginOptions, } from "./vinext_route_wiring_fix.js";
|
|
7
8
|
export { localeFilePlugin, resolveDefaultIntlConfigPath, type LocaleFilePluginOptions } from "./locale_file_plugin.js";
|
|
8
9
|
export { lucideOptimizerPlugin, detectLucideReact, resolveLucideEsmEntry, parseLucideIconMap, transformLucideImports, transformNextJsImports, type LucideOptimizerPluginOptions, } from "./lucide_optimizer_plugin.js";
|
|
9
10
|
export { cloudflareNextIntl, cloudflareNextIntlPlugin, type CloudflareNextIntlOptions, default } from "./plugin.js";
|
package/dist/src/vite/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
|
|
2
2
|
export { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
|
|
3
|
+
export { layoutQueriesPlugin } from "./layout_queries_plugin.js";
|
|
3
4
|
export { buildIdAsset } from "./build_id_asset.js";
|
|
4
5
|
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
5
6
|
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
|
6
|
-
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk } from "./vinext_route_wiring_fix.js";
|
|
7
|
+
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk, patchAppPageProbe, isAppPageProbeFile, isAppPageProbeAlreadyFixed, isVinextAppPageProbeSafeOnDisk, } from "./vinext_route_wiring_fix.js";
|
|
7
8
|
export { localeFilePlugin, resolveDefaultIntlConfigPath } from "./locale_file_plugin.js";
|
|
8
9
|
export { lucideOptimizerPlugin, detectLucideReact, resolveLucideEsmEntry, parseLucideIconMap, transformLucideImports, transformNextJsImports, } from "./lucide_optimizer_plugin.js";
|
|
9
10
|
export { cloudflareNextIntl, cloudflareNextIntlPlugin, default } from "./plugin.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { type CheckLayoutQueriesOptions } from "../layout_queries_check/index.js";
|
|
3
|
+
export interface LayoutQueriesPluginOptions extends CheckLayoutQueriesOptions {
|
|
4
|
+
strict?: boolean;
|
|
5
|
+
runOnDev?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function layoutQueriesPlugin(options?: LayoutQueriesPluginOptions): Plugin;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { checkLayoutQueries } from "../layout_queries_check/index.js";
|
|
4
|
+
export function layoutQueriesPlugin(options = {}) {
|
|
5
|
+
let ran = false;
|
|
6
|
+
return {
|
|
7
|
+
name: "cloudflare-next-intl-layout-queries-check",
|
|
8
|
+
enforce: "pre",
|
|
9
|
+
configResolved(config) {
|
|
10
|
+
if (ran)
|
|
11
|
+
return;
|
|
12
|
+
const isBuild = config.command === "build";
|
|
13
|
+
const isDev = config.command === "serve";
|
|
14
|
+
const runOnDev = options.runOnDev ?? true;
|
|
15
|
+
if (!isBuild && !(isDev && runOnDev))
|
|
16
|
+
return;
|
|
17
|
+
const root = config.root || process.cwd();
|
|
18
|
+
const candidateAppDirs = [
|
|
19
|
+
options.appDir,
|
|
20
|
+
resolve(root, "src/app"),
|
|
21
|
+
resolve(root, "app"),
|
|
22
|
+
].filter((dir) => !!dir && existsSync(dir));
|
|
23
|
+
const appDir = candidateAppDirs[0];
|
|
24
|
+
if (!appDir)
|
|
25
|
+
return;
|
|
26
|
+
ran = true;
|
|
27
|
+
const report = checkLayoutQueries({
|
|
28
|
+
appDir,
|
|
29
|
+
rootDir: root,
|
|
30
|
+
aliases: options.aliases,
|
|
31
|
+
maxDepth: options.maxDepth,
|
|
32
|
+
throwOnError: false,
|
|
33
|
+
});
|
|
34
|
+
if (!report.valid) {
|
|
35
|
+
console.warn(report.formattedMessage);
|
|
36
|
+
if (options.strict) {
|
|
37
|
+
throw new Error("[cloudflare-next-intl] Build failed: Blocking database queries detected in layout tree. See details above.");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -3,6 +3,7 @@ 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
5
|
import { type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
|
|
6
|
+
import { type LayoutQueriesPluginOptions } from "./layout_queries_plugin.js";
|
|
6
7
|
import { type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
|
|
7
8
|
import { type LucideOptimizerPluginOptions } from "./lucide_optimizer_plugin.js";
|
|
8
9
|
export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
@@ -15,6 +16,7 @@ export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
|
15
16
|
imageOptimizer?: boolean | ImageOptimizerPluginOptions;
|
|
16
17
|
vinextRouteWiringFix?: boolean | VinextRouteWiringFixPluginOptions;
|
|
17
18
|
autoLocaleParams?: boolean | AutoLocaleParamsPluginOptions;
|
|
19
|
+
layoutQueriesCheck?: boolean | LayoutQueriesPluginOptions;
|
|
18
20
|
experimentalRouteLoadingFixes?: boolean;
|
|
19
21
|
}
|
|
20
22
|
export declare function cloudflareNextIntl(options?: CloudflareNextIntlOptions): Plugin[];
|
package/dist/src/vite/plugin.js
CHANGED
|
@@ -5,16 +5,22 @@ 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
7
|
import { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
|
|
8
|
+
import { layoutQueriesPlugin } from "./layout_queries_plugin.js";
|
|
8
9
|
import { vinextRouteWiringFixPlugin } from "./vinext_route_wiring_fix.js";
|
|
9
10
|
import { lucideOptimizerPlugin } from "./lucide_optimizer_plugin.js";
|
|
10
11
|
export function cloudflareNextIntl(options = {}) {
|
|
11
12
|
const plugins = [];
|
|
13
|
+
if (options.layoutQueriesCheck !== false) {
|
|
14
|
+
plugins.push(layoutQueriesPlugin(typeof options.layoutQueriesCheck === "object"
|
|
15
|
+
? options.layoutQueriesCheck
|
|
16
|
+
: undefined));
|
|
17
|
+
}
|
|
12
18
|
if (options.lucideOptimizer !== false) {
|
|
13
19
|
plugins.push(lucideOptimizerPlugin(typeof options.lucideOptimizer === "object"
|
|
14
20
|
? options.lucideOptimizer
|
|
15
21
|
: { root: options.root }));
|
|
16
22
|
}
|
|
17
|
-
const enableRouteLoadingFixes = options.experimentalRouteLoadingFixes
|
|
23
|
+
const enableRouteLoadingFixes = options.experimentalRouteLoadingFixes !== false;
|
|
18
24
|
const shouldEnableVinextFix = options.vinextRouteWiringFix !== undefined
|
|
19
25
|
? Boolean(options.vinextRouteWiringFix)
|
|
20
26
|
: enableRouteLoadingFixes;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
declare const REQUIRED_SYMBOLS: {
|
|
3
|
+
readonly routeWiring: readonly ["makeThenableParams", "resolveAppPageSegmentParams", "routeLoadingComponent", "ancestorLoadingEntry", "slotParams", "ownerLoadingEntry"];
|
|
4
|
+
readonly routeMatching: readonly ["trieMatch", "getOrBuildTrie", "normalizePathnameForRouteMatch"];
|
|
5
|
+
readonly optimisticRouting: readonly ["getRouteTrie", "matchNode", "decodeMatchedParams", "hrefToRouteParts"];
|
|
6
|
+
readonly prefetchLearning: readonly ["resolveOptimisticNavigationPayload", "__basePath", "optimisticRouteTemplates", "optimisticRouteTemplateSources", "optimisticRouteTemplateLearning", "getOptimisticPrefetchSourceKey", "parsePrefetchCacheKey", "getPrefetchCache", "isSettledPrefetchCacheEntry", "learnOptimisticRouteTemplateFromPrefetch", "currentHref", "rscUrl"];
|
|
7
|
+
readonly suspenseProbe: readonly ["REACT_CLIENT_REFERENCE_TYPE"];
|
|
8
|
+
};
|
|
9
|
+
export type VinextPatchName = keyof typeof REQUIRED_SYMBOLS;
|
|
10
|
+
export declare function hasRequiredSymbols(code: string, patchName: VinextPatchName): boolean;
|
|
11
|
+
export declare function missingRequiredSymbols(code: string, patchName: VinextPatchName): string[];
|
|
12
|
+
export interface AppPageRouteWiringPatchOptions {
|
|
13
|
+
unblockRenderDependencies?: boolean;
|
|
14
|
+
unblockPageElementDependencies?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function isAppPageRouteWiringAlreadyFixed(code: string, options?: AppPageRouteWiringPatchOptions): boolean;
|
|
17
|
+
export declare function patchAppPageRouteWiring(code: string, options?: AppPageRouteWiringPatchOptions): string;
|
|
4
18
|
export declare function isRouteMatchingFile(id: string): boolean;
|
|
5
19
|
export declare function isRouteMatchingAlreadyFixed(code: string): boolean;
|
|
6
20
|
export declare function patchRouteMatching(code: string): string;
|
|
@@ -11,6 +25,32 @@ export declare function isPrefetchLearningFile(id: string): boolean;
|
|
|
11
25
|
export declare function isPrefetchLearningAlreadyFixed(code: string): boolean;
|
|
12
26
|
export declare function patchPrefetchLearning(code: string): string;
|
|
13
27
|
export declare function resolveVinextBrowserEntryPath(root?: string): string | null;
|
|
28
|
+
export declare function isAppPageProbeFile(id: string): boolean;
|
|
29
|
+
export declare function isAppPageProbeAlreadyFixed(code: string): boolean;
|
|
30
|
+
export declare function patchAppPageProbe(code: string): string;
|
|
31
|
+
export declare function resolveVinextAppPageProbePath(root?: string): string | null;
|
|
32
|
+
export declare function isVinextAppPageProbeSafeOnDisk(root?: string): boolean;
|
|
33
|
+
export declare function isRenderDependencyFile(id: string): boolean;
|
|
34
|
+
export declare function isRenderDependencyAlreadyFixed(code: string): boolean;
|
|
35
|
+
export declare function patchRenderDependency(code: string): string;
|
|
36
|
+
export declare function resolveVinextRenderDependencyPath(root?: string): string | null;
|
|
37
|
+
export declare function isVinextRenderDependencySafeOnDisk(root?: string): boolean;
|
|
38
|
+
export declare function isOptimisticLearningTimeoutFile(id: string): boolean;
|
|
39
|
+
export declare function isOptimisticLearningTimeoutAlreadyFixed(code: string): boolean;
|
|
40
|
+
export declare function patchOptimisticLearningTimeout(code: string, timeoutMs?: number): string;
|
|
41
|
+
export declare function resolveVinextOptimisticLearningTimeoutPath(root?: string): string | null;
|
|
42
|
+
export declare function isVinextOptimisticLearningTimeoutSafeOnDisk(root?: string): boolean;
|
|
43
|
+
export declare function isPageInvokerSuspensionReleaseFile(id: string): boolean;
|
|
44
|
+
export declare function isPageInvokerSuspensionReleaseAlreadyFixed(code: string): boolean;
|
|
45
|
+
export declare function patchPageInvokerSuspensionRelease(code: string): string;
|
|
46
|
+
export declare function resolveVinextPageInvokerSuspensionReleasePath(root?: string): string | null;
|
|
47
|
+
export declare function isVinextPageInvokerSuspensionReleaseSafeOnDisk(root?: string): boolean;
|
|
48
|
+
export declare function isRefreshDeferralNavControllerFile(id: string): boolean;
|
|
49
|
+
export declare function isRefreshDeferralNavControllerAlreadyFixed(code: string): boolean;
|
|
50
|
+
export declare function patchRefreshDeferralNavController(code: string): string;
|
|
51
|
+
export declare function isRefreshDeferralEntryAlreadyFixed(code: string): boolean;
|
|
52
|
+
export declare function patchRefreshDeferralEntry(code: string): string;
|
|
53
|
+
export declare function resolveVinextNavControllerPath(root?: string): string | null;
|
|
14
54
|
export declare function isAppPageRouteWiringFile(id: string): boolean;
|
|
15
55
|
export declare function resolveVinextAppPageRouteWiringPath(root?: string): string | null;
|
|
16
56
|
export declare function isVinextAppPageRouteWiringSafeOnDisk(root?: string): boolean;
|
|
@@ -18,16 +58,32 @@ export declare function resolveVinextRouteMatchingPath(root?: string): string |
|
|
|
18
58
|
export declare function resolveVinextOptimisticRoutingPath(root?: string): string | null;
|
|
19
59
|
export interface SyncPatchVinextOnDiskOptions {
|
|
20
60
|
routeWiring?: boolean;
|
|
61
|
+
unblockRenderDependencies?: boolean;
|
|
62
|
+
unblockPageElementDependencies?: boolean;
|
|
63
|
+
refreshDeferral?: boolean;
|
|
21
64
|
routeMatching?: boolean;
|
|
22
65
|
optimisticRouting?: boolean;
|
|
23
66
|
prefetchLearning?: boolean;
|
|
67
|
+
suspenseProbe?: boolean;
|
|
68
|
+
renderDependency?: boolean;
|
|
69
|
+
optimisticLearningTimeout?: boolean | number;
|
|
70
|
+
pageInvokerSuspensionRelease?: boolean;
|
|
24
71
|
}
|
|
25
72
|
export declare function syncPatchVinextOnDisk(root?: string, options?: SyncPatchVinextOnDiskOptions): boolean;
|
|
26
73
|
export declare function bustVinextOptimizeDepsCache(cacheDir: string): boolean;
|
|
74
|
+
export declare function isVinextOptimizeDepsCacheStale(root: string, cacheDir: string): boolean;
|
|
27
75
|
export interface VinextRouteWiringFixPluginOptions {
|
|
28
76
|
routeWiring?: boolean;
|
|
29
77
|
routeMatching?: boolean;
|
|
30
78
|
optimisticRouting?: boolean;
|
|
31
79
|
prefetchLearning?: boolean;
|
|
80
|
+
suspenseProbe?: boolean;
|
|
81
|
+
renderDependency?: boolean;
|
|
82
|
+
optimisticLearningTimeout?: boolean | number;
|
|
83
|
+
pageInvokerSuspensionRelease?: boolean;
|
|
84
|
+
unblockRenderDependencies?: boolean;
|
|
85
|
+
unblockPageElementDependencies?: boolean;
|
|
86
|
+
refreshDeferral?: boolean;
|
|
32
87
|
}
|
|
33
88
|
export declare function vinextRouteWiringFixPlugin(options?: VinextRouteWiringFixPluginOptions): Plugin;
|
|
89
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, rmSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, rmSync, statSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
const PREFETCH_LOADING_FN_RE = /function\s+getPrefetchLoadingEntry\s*\(\s*route\s*\)\s*\{[\s\S]*?firstNestedEntry[\s\S]*?route\.loadings[\s\S]*?return\s+getDefaultExport\s*\(\s*route\.loading\s*\)\s*\?[\s\S]*?:\s*null\s*;\s*\}/;
|
|
4
4
|
const FIXED_PREFETCH_LOADING_FN = `function getPrefetchLoadingEntry(route) {
|
|
@@ -26,7 +26,7 @@ const FIXED_PREFETCH_LOADING_FN = `function getPrefetchLoadingEntry(route) {
|
|
|
26
26
|
if (rootEntry) return rootEntry;
|
|
27
27
|
return null;
|
|
28
28
|
}`;
|
|
29
|
-
const ROUTE_LOADING_GUARD_RE = /if\s*\(\s*!isPrefetchLoadingShell\s*&&\s*treePosition\s*<\s*routeSegments\.length\s*\)\s*\{
|
|
29
|
+
const ROUTE_LOADING_GUARD_RE = /if\s*\(\s*!isPrefetchLoadingShell\s*&&\s*treePosition\s*<\s*routeSegments\.length\s*\)\s*\{/;
|
|
30
30
|
const FIXED_ROUTE_LOADING_GUARD = "if (!isPrefetchLoadingShell && treePosition < routeSegments.length && !routeLoadingComponent) {";
|
|
31
31
|
const PAGE_LOADING_FALLBACK_RE = /fallback:\s*\/\*\s*@__PURE__\s*\*\/\s*jsx\s*\(\s*PageLoadingComponent\s*,\s*\{\s*\}\s*\)/;
|
|
32
32
|
const FIXED_PAGE_LOADING_FALLBACK = "fallback: /* @__PURE__ */ jsx(PageLoadingComponent, { params: options.makeThenableParams(options.matchedParams) })";
|
|
@@ -44,7 +44,45 @@ const SEGMENT_LOADING_FALLBACK_RE = /fallback:\s*\/\*\s*@__PURE__\s*\*\/\s*jsx\s
|
|
|
44
44
|
const FIXED_SEGMENT_LOADING_FALLBACK = "fallback: /* @__PURE__ */ jsx(segmentLoadingComponent, { params: options.makeThenableParams(resolveAppPageSegmentParams(options.route.routeSegments, treePosition, options.matchedParams)) })";
|
|
45
45
|
const PREFETCH_SLOT_LOADING_CALL_RE = /slotElement\s*=\s*\/\*\s*@__PURE__\s*\*\/\s*jsx\s*\(\s*getDefaultExport\s*\(\s*prefetchSlotLoadingEntry\.loadingModule\s*\)\s*,\s*\{\s*\}\s*\)/;
|
|
46
46
|
const FIXED_PREFETCH_SLOT_LOADING_CALL = "slotElement = /* @__PURE__ */ jsx(getDefaultExport(prefetchSlotLoadingEntry.loadingModule), { params: options.makeThenableParams(slotParams) })";
|
|
47
|
-
|
|
47
|
+
const PAGE_RESULT_DEPS_RE = /pageRenderDependency\?\.setResultDependencies\(\s*pageDependencies\s*\);/;
|
|
48
|
+
const PAGE_ELEMENT_BLOCKING_DEP_RE = /elements\[pageElementId\]\s*=\s*isPrefetchLoadingShell\s*\?\s*null\s*:\s*pageRenderDependency\s*\?\s*pageElement\s*:\s*renderAfterAppDependencies\s*\(\s*pageElement\s*,\s*pageDependencies\s*\);/;
|
|
49
|
+
const REQUIRED_SYMBOLS = {
|
|
50
|
+
routeWiring: [
|
|
51
|
+
"makeThenableParams",
|
|
52
|
+
"resolveAppPageSegmentParams",
|
|
53
|
+
"routeLoadingComponent",
|
|
54
|
+
"ancestorLoadingEntry",
|
|
55
|
+
"slotParams",
|
|
56
|
+
"ownerLoadingEntry",
|
|
57
|
+
],
|
|
58
|
+
routeMatching: ["trieMatch", "getOrBuildTrie", "normalizePathnameForRouteMatch"],
|
|
59
|
+
optimisticRouting: ["getRouteTrie", "matchNode", "decodeMatchedParams", "hrefToRouteParts"],
|
|
60
|
+
prefetchLearning: [
|
|
61
|
+
"resolveOptimisticNavigationPayload",
|
|
62
|
+
"__basePath",
|
|
63
|
+
"optimisticRouteTemplates",
|
|
64
|
+
"optimisticRouteTemplateSources",
|
|
65
|
+
"optimisticRouteTemplateLearning",
|
|
66
|
+
"getOptimisticPrefetchSourceKey",
|
|
67
|
+
"parsePrefetchCacheKey",
|
|
68
|
+
"getPrefetchCache",
|
|
69
|
+
"isSettledPrefetchCacheEntry",
|
|
70
|
+
"learnOptimisticRouteTemplateFromPrefetch",
|
|
71
|
+
"currentHref",
|
|
72
|
+
"rscUrl",
|
|
73
|
+
],
|
|
74
|
+
suspenseProbe: ["REACT_CLIENT_REFERENCE_TYPE"],
|
|
75
|
+
};
|
|
76
|
+
export function hasRequiredSymbols(code, patchName) {
|
|
77
|
+
return REQUIRED_SYMBOLS[patchName].every((symbol) => code.includes(symbol));
|
|
78
|
+
}
|
|
79
|
+
export function missingRequiredSymbols(code, patchName) {
|
|
80
|
+
return REQUIRED_SYMBOLS[patchName].filter((symbol) => !code.includes(symbol));
|
|
81
|
+
}
|
|
82
|
+
function warnIncompatible(filePath, patchName, code) {
|
|
83
|
+
console.warn(`[cfni:vinext-route-wiring-fix] ${filePath} no longer exposes ${missingRequiredSymbols(code, patchName).join(", ")} — this vinext version may have changed; the ${patchName} fix was NOT applied.`);
|
|
84
|
+
}
|
|
85
|
+
export function isAppPageRouteWiringAlreadyFixed(code, options = {}) {
|
|
48
86
|
const hasBuggyPrefetch = code.includes("firstNestedEntry") &&
|
|
49
87
|
PREFETCH_LOADING_FN_RE.test(code);
|
|
50
88
|
const hasBuggySuspense = !code.includes("!routeLoadingComponent") && ROUTE_LOADING_GUARD_RE.test(code);
|
|
@@ -56,10 +94,14 @@ export function isAppPageRouteWiringAlreadyFixed(code) {
|
|
|
56
94
|
ROUTE_LOADING_FALLBACK_RE.test(code) ||
|
|
57
95
|
SEGMENT_LOADING_FALLBACK_RE.test(code) ||
|
|
58
96
|
PREFETCH_SLOT_LOADING_CALL_RE.test(code);
|
|
59
|
-
|
|
97
|
+
const hasBlockingLayoutDependencies = options.unblockRenderDependencies === true &&
|
|
98
|
+
code.includes("elements[layoutEntry.id] = renderAfterAppDependencies(layoutElement, [...pageRenderDependency");
|
|
99
|
+
const hasBlockingPageDeps = options.unblockPageElementDependencies === true &&
|
|
100
|
+
(PAGE_RESULT_DEPS_RE.test(code) || PAGE_ELEMENT_BLOCKING_DEP_RE.test(code));
|
|
101
|
+
return !hasBuggyPrefetch && !hasBuggySuspense && !hasEmptyLoadingProps && !hasBlockingLayoutDependencies && !hasBlockingPageDeps;
|
|
60
102
|
}
|
|
61
|
-
export function patchAppPageRouteWiring(code) {
|
|
62
|
-
if (isAppPageRouteWiringAlreadyFixed(code)) {
|
|
103
|
+
export function patchAppPageRouteWiring(code, options = {}) {
|
|
104
|
+
if (isAppPageRouteWiringAlreadyFixed(code, options)) {
|
|
63
105
|
return code;
|
|
64
106
|
}
|
|
65
107
|
let result = code;
|
|
@@ -97,6 +139,30 @@ export function patchAppPageRouteWiring(code) {
|
|
|
97
139
|
if (PREFETCH_SLOT_LOADING_CALL_RE.test(result)) {
|
|
98
140
|
result = result.replace(PREFETCH_SLOT_LOADING_CALL_RE, FIXED_PREFETCH_SLOT_LOADING_CALL);
|
|
99
141
|
}
|
|
142
|
+
if (options.unblockRenderDependencies === true) {
|
|
143
|
+
const LAYOUT_DEP_RE = /elements\[layoutEntry\.id\]\s*=\s*renderAfterAppDependencies\s*\(\s*layoutElement\s*,\s*\[\s*\.\.\.pageRenderDependency\s*\?\s*\[pageRenderDependency\]\s*:\s*\[\]\s*,\s*\.\.\.layoutDependenciesBefore\[index\]\s*\?\?\s*\[\]\s*\]\s*\);/;
|
|
144
|
+
if (LAYOUT_DEP_RE.test(result)) {
|
|
145
|
+
result = result.replace(LAYOUT_DEP_RE, "elements[layoutEntry.id] = renderAfterAppDependencies(layoutElement, layoutDependenciesBefore[index] ?? []);");
|
|
146
|
+
}
|
|
147
|
+
const TEMPLATE_DEP_RE = /elements\[templateEntry\.id\]\s*=\s*renderAfterAppDependencies\s*\(\s*templateElement\s*,\s*\[\s*\.\.\.pageRenderDependency\s*\?\s*\[pageRenderDependency\]\s*:\s*\[\]\s*,\s*\.\.\.templateDependenciesBeforeById\.get\(templateEntry\.id\)\s*\?\?\s*\[\]\s*\]\s*\);/;
|
|
148
|
+
if (TEMPLATE_DEP_RE.test(result)) {
|
|
149
|
+
result = result.replace(TEMPLATE_DEP_RE, "elements[templateEntry.id] = renderAfterAppDependencies(templateElement, templateDependenciesBeforeById.get(templateEntry.id) ?? []);");
|
|
150
|
+
}
|
|
151
|
+
const SLOT_DEP_RE = /elements\[slotId\]\s*=\s*renderAfterAppDependencies\s*\(\s*slotElement\s*,\s*\[\s*\.\.\.pageRenderDependency\s*\?\s*\[pageRenderDependency\]\s*:\s*\[\]\s*,\s*\.\.\.targetIndex\s*>=\s*0\s*\?\s*slotDependenciesByLayoutIndex\[targetIndex\]\s*\?\?\s*\[\]\s*:\s*\[\]\s*\]\s*\);/;
|
|
152
|
+
if (SLOT_DEP_RE.test(result)) {
|
|
153
|
+
result = result.replace(SLOT_DEP_RE, "elements[slotId] = renderAfterAppDependencies(slotElement, targetIndex >= 0 ? slotDependenciesByLayoutIndex[targetIndex] ?? [] : []);");
|
|
154
|
+
}
|
|
155
|
+
const ROUTE_DEP_RE = /elements\[routeId\]\s*=\s*pageRenderDependency\s*\?\s*renderAfterAppDependencies\s*\(\s*routeElement\s*,\s*\[pageRenderDependency\]\s*\)\s*:\s*routeElement;/;
|
|
156
|
+
if (ROUTE_DEP_RE.test(result)) {
|
|
157
|
+
result = result.replace(ROUTE_DEP_RE, "elements[routeId] = routeElement;");
|
|
158
|
+
}
|
|
159
|
+
if (options.unblockPageElementDependencies === true && PAGE_RESULT_DEPS_RE.test(result)) {
|
|
160
|
+
result = result.replace(PAGE_RESULT_DEPS_RE, "pageRenderDependency?.setResultDependencies([]);");
|
|
161
|
+
}
|
|
162
|
+
if (options.unblockPageElementDependencies === true && PAGE_ELEMENT_BLOCKING_DEP_RE.test(result)) {
|
|
163
|
+
result = result.replace(PAGE_ELEMENT_BLOCKING_DEP_RE, "elements[pageElementId] = isPrefetchLoadingShell ? null : pageElement;");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
100
166
|
return result;
|
|
101
167
|
}
|
|
102
168
|
export function isRouteMatchingFile(id) {
|
|
@@ -153,13 +219,13 @@ export function patchRouteMatching(code) {
|
|
|
153
219
|
return code;
|
|
154
220
|
}
|
|
155
221
|
let result = code;
|
|
156
|
-
if (
|
|
222
|
+
if (MATCH_ROUTE_WITH_TRIE_RE.test(result)) {
|
|
157
223
|
const replacement = result.includes("function getActiveRouteLocale")
|
|
158
224
|
? FIXED_MATCH_ROUTE_WITH_TRIE_BODY
|
|
159
225
|
: `${GET_ACTIVE_ROUTE_LOCALE_FN}${FIXED_MATCH_ROUTE_WITH_TRIE_BODY}`;
|
|
160
226
|
result = result.replace(MATCH_ROUTE_WITH_TRIE_RE, replacement);
|
|
161
227
|
}
|
|
162
|
-
if (
|
|
228
|
+
if (MATCH_ROUTE_WITH_TRIE_RAW_RE.test(result)) {
|
|
163
229
|
const replacement = result.includes("function getActiveRouteLocale")
|
|
164
230
|
? FIXED_MATCH_ROUTE_WITH_TRIE_RAW
|
|
165
231
|
: `${GET_ACTIVE_ROUTE_LOCALE_FN}${FIXED_MATCH_ROUTE_WITH_TRIE_RAW}`;
|
|
@@ -336,6 +402,169 @@ export function resolveVinextBrowserEntryPath(root = process.cwd()) {
|
|
|
336
402
|
const directPath = resolve(root, "node_modules/vinext/dist/server/app-browser-entry.js");
|
|
337
403
|
return existsSync(directPath) ? directPath : null;
|
|
338
404
|
}
|
|
405
|
+
export function isAppPageProbeFile(id) {
|
|
406
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
407
|
+
return cleanId.endsWith("/app-page-probe.js") || cleanId.endsWith("/app-page-probe.ts");
|
|
408
|
+
}
|
|
409
|
+
export function isAppPageProbeAlreadyFixed(code) {
|
|
410
|
+
return code.includes("react.suspense") && code.includes("REACT_SUSPENSE_TYPE");
|
|
411
|
+
}
|
|
412
|
+
const REACT_SUSPENSE_DECL_RE = /const\s+REACT_CLIENT_REFERENCE_TYPE\s*=\s*Symbol\.for\(["']react\.client\.reference["']\);/;
|
|
413
|
+
const PROBE_VISIT_FRAGMENT_RE = /if\s*\(\s*value\.type\s*===\s*Fragment\s*\|\|\s*typeof\s+value\.type\s*===\s*["']string["']\s*\)\s*\{/;
|
|
414
|
+
export function patchAppPageProbe(code) {
|
|
415
|
+
if (isAppPageProbeAlreadyFixed(code)) {
|
|
416
|
+
return code;
|
|
417
|
+
}
|
|
418
|
+
let result = code;
|
|
419
|
+
if (!result.includes("REACT_SUSPENSE_TYPE") && REACT_SUSPENSE_DECL_RE.test(result)) {
|
|
420
|
+
result = result.replace(REACT_SUSPENSE_DECL_RE, 'const REACT_CLIENT_REFERENCE_TYPE = Symbol.for("react.client.reference");\nconst REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");');
|
|
421
|
+
}
|
|
422
|
+
if (result.includes("REACT_SUSPENSE_TYPE") && PROBE_VISIT_FRAGMENT_RE.test(result)) {
|
|
423
|
+
result = result.replace(PROBE_VISIT_FRAGMENT_RE, `if (value.type === Symbol.for("react.suspense") || value.type === REACT_SUSPENSE_TYPE) {\n\t\t\tif (value.props && "fallback" in value.props) await visit(value.props.fallback, depth + 1);\n\t\t\treturn;\n\t\t}\n\t\tif (value.type === Fragment || typeof value.type === "string") {`);
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
export function resolveVinextAppPageProbePath(root = process.cwd()) {
|
|
428
|
+
const directPath = resolve(root, "node_modules/vinext/dist/server/app-page-probe.js");
|
|
429
|
+
return existsSync(directPath) ? directPath : null;
|
|
430
|
+
}
|
|
431
|
+
export function isVinextAppPageProbeSafeOnDisk(root = process.cwd()) {
|
|
432
|
+
const filePath = resolveVinextAppPageProbePath(root);
|
|
433
|
+
if (!filePath)
|
|
434
|
+
return false;
|
|
435
|
+
try {
|
|
436
|
+
const content = readFileSync(filePath, "utf8");
|
|
437
|
+
return isAppPageProbeAlreadyFixed(content);
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const SUSPENSION_GUARDED_RELEASE_RE = /if\s*\(\s*!isAppRenderSuspension\(\s*error\s*\)\s*\)\s*dependency\.release\(\);/;
|
|
444
|
+
export function isRenderDependencyFile(id) {
|
|
445
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
446
|
+
return cleanId.endsWith("/app-render-dependency.js") || cleanId.endsWith("/app-render-dependency.tsx") || cleanId.endsWith("/app-render-dependency.ts");
|
|
447
|
+
}
|
|
448
|
+
export function isRenderDependencyAlreadyFixed(code) {
|
|
449
|
+
if (!code.includes("renderAppComponentWithDependencyBarrier"))
|
|
450
|
+
return true;
|
|
451
|
+
return !SUSPENSION_GUARDED_RELEASE_RE.test(code);
|
|
452
|
+
}
|
|
453
|
+
export function patchRenderDependency(code) {
|
|
454
|
+
if (!SUSPENSION_GUARDED_RELEASE_RE.test(code))
|
|
455
|
+
return code;
|
|
456
|
+
return code.replace(SUSPENSION_GUARDED_RELEASE_RE, "dependency.release();");
|
|
457
|
+
}
|
|
458
|
+
export function resolveVinextRenderDependencyPath(root = process.cwd()) {
|
|
459
|
+
const directPath = resolve(root, "node_modules/vinext/dist/server/app-render-dependency.js");
|
|
460
|
+
return existsSync(directPath) ? directPath : null;
|
|
461
|
+
}
|
|
462
|
+
export function isVinextRenderDependencySafeOnDisk(root = process.cwd()) {
|
|
463
|
+
const filePath = resolveVinextRenderDependencyPath(root);
|
|
464
|
+
if (!filePath)
|
|
465
|
+
return false;
|
|
466
|
+
try {
|
|
467
|
+
return isRenderDependencyAlreadyFixed(readFileSync(filePath, "utf8"));
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const OPTIMISTIC_LEARNING_TIMEOUT_MS = 200;
|
|
474
|
+
const OPTIMISTIC_LEARNING_BLOCKING_TIMEOUT_RE = /(new Promise\(\(resolve\)\s*=>\s*setTimeout\(resolve,\s*)3000(\)\))/;
|
|
475
|
+
export function isOptimisticLearningTimeoutFile(id) {
|
|
476
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
477
|
+
return cleanId.endsWith("/app-browser-entry.js") || cleanId.endsWith("/app-browser-entry.ts") || cleanId.endsWith("/app-browser-entry.tsx");
|
|
478
|
+
}
|
|
479
|
+
export function isOptimisticLearningTimeoutAlreadyFixed(code) {
|
|
480
|
+
return !OPTIMISTIC_LEARNING_BLOCKING_TIMEOUT_RE.test(code);
|
|
481
|
+
}
|
|
482
|
+
export function patchOptimisticLearningTimeout(code, timeoutMs = OPTIMISTIC_LEARNING_TIMEOUT_MS) {
|
|
483
|
+
if (!OPTIMISTIC_LEARNING_BLOCKING_TIMEOUT_RE.test(code))
|
|
484
|
+
return code;
|
|
485
|
+
return code.replace(OPTIMISTIC_LEARNING_BLOCKING_TIMEOUT_RE, `$1${timeoutMs}$2`);
|
|
486
|
+
}
|
|
487
|
+
export function resolveVinextOptimisticLearningTimeoutPath(root = process.cwd()) {
|
|
488
|
+
const directPath = resolve(root, "node_modules/vinext/dist/server/app-browser-entry.js");
|
|
489
|
+
return existsSync(directPath) ? directPath : null;
|
|
490
|
+
}
|
|
491
|
+
export function isVinextOptimisticLearningTimeoutSafeOnDisk(root = process.cwd()) {
|
|
492
|
+
const filePath = resolveVinextOptimisticLearningTimeoutPath(root);
|
|
493
|
+
if (!filePath)
|
|
494
|
+
return false;
|
|
495
|
+
try {
|
|
496
|
+
return isOptimisticLearningTimeoutAlreadyFixed(readFileSync(filePath, "utf8"));
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const PAGE_INVOKER_GUARDED_RELEASE_RE = /if\s*\(renderDependency\s*&&\s*hasPageLoadingBoundary\)\s*Promise\.resolve\(\)\.then\(\(\)\s*=>\s*renderDependency\.release\(\)\);/;
|
|
503
|
+
export function isPageInvokerSuspensionReleaseFile(id) {
|
|
504
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
505
|
+
return cleanId.endsWith("/app-page-element-builder.js") || cleanId.endsWith("/app-page-element-builder.ts") || cleanId.endsWith("/app-page-element-builder.tsx");
|
|
506
|
+
}
|
|
507
|
+
export function isPageInvokerSuspensionReleaseAlreadyFixed(code) {
|
|
508
|
+
return !PAGE_INVOKER_GUARDED_RELEASE_RE.test(code);
|
|
509
|
+
}
|
|
510
|
+
export function patchPageInvokerSuspensionRelease(code) {
|
|
511
|
+
if (!PAGE_INVOKER_GUARDED_RELEASE_RE.test(code))
|
|
512
|
+
return code;
|
|
513
|
+
return code.replace(PAGE_INVOKER_GUARDED_RELEASE_RE, "if (renderDependency) Promise.resolve().then(() => renderDependency.release());");
|
|
514
|
+
}
|
|
515
|
+
export function resolveVinextPageInvokerSuspensionReleasePath(root = process.cwd()) {
|
|
516
|
+
const directPath = resolve(root, "node_modules/vinext/dist/server/app-page-element-builder.js");
|
|
517
|
+
return existsSync(directPath) ? directPath : null;
|
|
518
|
+
}
|
|
519
|
+
export function isVinextPageInvokerSuspensionReleaseSafeOnDisk(root = process.cwd()) {
|
|
520
|
+
const filePath = resolveVinextPageInvokerSuspensionReleasePath(root);
|
|
521
|
+
if (!filePath)
|
|
522
|
+
return false;
|
|
523
|
+
try {
|
|
524
|
+
return isPageInvokerSuspensionReleaseAlreadyFixed(readFileSync(filePath, "utf8"));
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const NAV_CONTROLLER_BEGIN_NAVIGATION_RE = /function\s+beginNavigation\(\s*\)\s*\{(\n([\t ]*))latestHmrUpdateId\s*\+=\s*1;/;
|
|
531
|
+
const NAV_CONTROLLER_STATE_DECL_RE = /(\n\s*let\s+latestHmrUpdateId\s*=\s*0;)/;
|
|
532
|
+
const NAV_CONTROLLER_RETURN_RE = /(\n\s*return\s*\{\s*\n\s*beginNavigation,)/;
|
|
533
|
+
const NAV_ENTRY_NAVIGATE_FN_RE = /(navigate:\s*async function navigateRsc\([^)]*\)\s*\{)(\n([\t ]*))/;
|
|
534
|
+
const NAV_ENTRY_BEGIN_NAVIGATION_RE = /(\n[\t ]*const navId = browserNavigationController\.)beginNavigation\(\);/;
|
|
535
|
+
const REFRESH_DEFERRAL_WINDOW_MS = 600;
|
|
536
|
+
export function isRefreshDeferralNavControllerFile(id) {
|
|
537
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
538
|
+
return cleanId.endsWith("/app-browser-navigation-controller.js") || cleanId.endsWith("/app-browser-navigation-controller.ts");
|
|
539
|
+
}
|
|
540
|
+
export function isRefreshDeferralNavControllerAlreadyFixed(code) {
|
|
541
|
+
return code.includes("isRecentNonRefreshNavigationInFlight") || !NAV_CONTROLLER_BEGIN_NAVIGATION_RE.test(code);
|
|
542
|
+
}
|
|
543
|
+
export function patchRefreshDeferralNavController(code) {
|
|
544
|
+
if (isRefreshDeferralNavControllerAlreadyFixed(code))
|
|
545
|
+
return code;
|
|
546
|
+
if (!NAV_CONTROLLER_STATE_DECL_RE.test(code) || !NAV_CONTROLLER_RETURN_RE.test(code))
|
|
547
|
+
return code;
|
|
548
|
+
let result = code.replace(NAV_CONTROLLER_STATE_DECL_RE, "\n\tlet lastNonRefreshNavigationStartedAt = 0;$1");
|
|
549
|
+
result = result.replace(NAV_CONTROLLER_BEGIN_NAVIGATION_RE, (_m, _nl, indent) => `function beginNavigation(kind) {\n${indent}latestHmrUpdateId += 1;\n${indent}if (kind !== "refresh") lastNonRefreshNavigationStartedAt = Date.now();`);
|
|
550
|
+
result = result.replace(/(\n\s*function getActiveNavigationId\(\) \{)/, `\n\tconst REFRESH_DEFERRAL_WINDOW_MS = ${REFRESH_DEFERRAL_WINDOW_MS};\n\tfunction isRecentNonRefreshNavigationInFlight() {\n\t\treturn lastNonRefreshNavigationStartedAt !== 0 && Date.now() - lastNonRefreshNavigationStartedAt < REFRESH_DEFERRAL_WINDOW_MS;\n\t}$1`);
|
|
551
|
+
result = result.replace(NAV_CONTROLLER_RETURN_RE, "$1\n\t\tisRecentNonRefreshNavigationInFlight,");
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
export function isRefreshDeferralEntryAlreadyFixed(code) {
|
|
555
|
+
return code.includes("isRecentNonRefreshNavigationInFlight()") || !NAV_ENTRY_NAVIGATE_FN_RE.test(code);
|
|
556
|
+
}
|
|
557
|
+
export function patchRefreshDeferralEntry(code) {
|
|
558
|
+
if (isRefreshDeferralEntryAlreadyFixed(code))
|
|
559
|
+
return code;
|
|
560
|
+
let result = code.replace(NAV_ENTRY_NAVIGATE_FN_RE, (_m, signature, _nl, indent) => `${signature}\n${indent}if (navigationKind === "refresh") {\n${indent}\twhile (browserNavigationController.isRecentNonRefreshNavigationInFlight()) await new Promise((resolve) => setTimeout(resolve, 50));\n${indent}}\n${indent}`);
|
|
561
|
+
result = result.replace(NAV_ENTRY_BEGIN_NAVIGATION_RE, "$1beginNavigation(navigationKind);");
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
export function resolveVinextNavControllerPath(root = process.cwd()) {
|
|
565
|
+
const directPath = resolve(root, "node_modules/vinext/dist/server/app-browser-navigation-controller.js");
|
|
566
|
+
return existsSync(directPath) ? directPath : null;
|
|
567
|
+
}
|
|
339
568
|
export function isAppPageRouteWiringFile(id) {
|
|
340
569
|
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
341
570
|
return cleanId.endsWith("/app-page-route-wiring.js") || cleanId.endsWith("/app-page-route-wiring.tsx") || cleanId.endsWith("/app-page-route-wiring.ts");
|
|
@@ -365,14 +594,18 @@ export function resolveVinextOptimisticRoutingPath(root = process.cwd()) {
|
|
|
365
594
|
return existsSync(directPath) ? directPath : null;
|
|
366
595
|
}
|
|
367
596
|
export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
|
|
368
|
-
const { routeWiring = true, routeMatching = true, optimisticRouting = true, prefetchLearning = true } = options;
|
|
597
|
+
const { routeWiring = true, routeMatching = true, optimisticRouting = true, prefetchLearning = true, suspenseProbe = true, renderDependency = true, optimisticLearningTimeout = true, pageInvokerSuspensionRelease = true, unblockRenderDependencies = true, unblockPageElementDependencies = false, refreshDeferral = true } = options;
|
|
598
|
+
const optimisticLearningTimeoutMs = typeof optimisticLearningTimeout === "number" ? optimisticLearningTimeout : undefined;
|
|
369
599
|
let changed = false;
|
|
370
600
|
const wiringPath = routeWiring ? resolveVinextAppPageRouteWiringPath(root) : null;
|
|
371
601
|
if (wiringPath) {
|
|
372
602
|
try {
|
|
373
603
|
const content = readFileSync(wiringPath, "utf8");
|
|
374
|
-
if (!isAppPageRouteWiringAlreadyFixed(content)) {
|
|
375
|
-
|
|
604
|
+
if (!isAppPageRouteWiringAlreadyFixed(content, { unblockRenderDependencies, unblockPageElementDependencies }) && !hasRequiredSymbols(content, "routeWiring")) {
|
|
605
|
+
warnIncompatible(wiringPath, "routeWiring", content);
|
|
606
|
+
}
|
|
607
|
+
else if (!isAppPageRouteWiringAlreadyFixed(content, { unblockRenderDependencies, unblockPageElementDependencies })) {
|
|
608
|
+
const patched = patchAppPageRouteWiring(content, { unblockRenderDependencies, unblockPageElementDependencies });
|
|
376
609
|
if (patched !== content) {
|
|
377
610
|
writeFileSync(wiringPath, patched, "utf8");
|
|
378
611
|
changed = true;
|
|
@@ -389,7 +622,10 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
|
|
|
389
622
|
if (matchingPath) {
|
|
390
623
|
try {
|
|
391
624
|
const content = readFileSync(matchingPath, "utf8");
|
|
392
|
-
if (!isRouteMatchingAlreadyFixed(content)) {
|
|
625
|
+
if (!isRouteMatchingAlreadyFixed(content) && !hasRequiredSymbols(content, "routeMatching")) {
|
|
626
|
+
warnIncompatible(matchingPath, "routeMatching", content);
|
|
627
|
+
}
|
|
628
|
+
else if (!isRouteMatchingAlreadyFixed(content)) {
|
|
393
629
|
const patched = patchRouteMatching(content);
|
|
394
630
|
if (patched !== content) {
|
|
395
631
|
writeFileSync(matchingPath, patched, "utf8");
|
|
@@ -407,7 +643,10 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
|
|
|
407
643
|
if (optimisticPath) {
|
|
408
644
|
try {
|
|
409
645
|
const content = readFileSync(optimisticPath, "utf8");
|
|
410
|
-
if (!isOptimisticRoutingAlreadyFixed(content)) {
|
|
646
|
+
if (!isOptimisticRoutingAlreadyFixed(content) && !hasRequiredSymbols(content, "optimisticRouting")) {
|
|
647
|
+
warnIncompatible(optimisticPath, "optimisticRouting", content);
|
|
648
|
+
}
|
|
649
|
+
else if (!isOptimisticRoutingAlreadyFixed(content)) {
|
|
411
650
|
const patched = patchOptimisticRouting(content);
|
|
412
651
|
if (patched !== content) {
|
|
413
652
|
writeFileSync(optimisticPath, patched, "utf8");
|
|
@@ -425,7 +664,10 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
|
|
|
425
664
|
if (browserEntryPath) {
|
|
426
665
|
try {
|
|
427
666
|
const content = readFileSync(browserEntryPath, "utf8");
|
|
428
|
-
if (!isPrefetchLearningAlreadyFixed(content)) {
|
|
667
|
+
if (!isPrefetchLearningAlreadyFixed(content) && !hasRequiredSymbols(content, "prefetchLearning")) {
|
|
668
|
+
warnIncompatible(browserEntryPath, "prefetchLearning", content);
|
|
669
|
+
}
|
|
670
|
+
else if (!isPrefetchLearningAlreadyFixed(content)) {
|
|
429
671
|
const patched = patchPrefetchLearning(content);
|
|
430
672
|
if (patched !== content) {
|
|
431
673
|
writeFileSync(browserEntryPath, patched, "utf8");
|
|
@@ -439,6 +681,111 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
|
|
|
439
681
|
catch {
|
|
440
682
|
}
|
|
441
683
|
}
|
|
684
|
+
const probePath = suspenseProbe ? resolveVinextAppPageProbePath(root) : null;
|
|
685
|
+
if (probePath) {
|
|
686
|
+
try {
|
|
687
|
+
const content = readFileSync(probePath, "utf8");
|
|
688
|
+
if (!isAppPageProbeAlreadyFixed(content) && !hasRequiredSymbols(content, "suspenseProbe")) {
|
|
689
|
+
warnIncompatible(probePath, "suspenseProbe", content);
|
|
690
|
+
}
|
|
691
|
+
else if (!isAppPageProbeAlreadyFixed(content)) {
|
|
692
|
+
const patched = patchAppPageProbe(content);
|
|
693
|
+
if (patched !== content) {
|
|
694
|
+
writeFileSync(probePath, patched, "utf8");
|
|
695
|
+
changed = true;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
console.warn(`[cfni:vinext-route-wiring-fix] ${probePath} does not match the expected shape for patchAppPageProbe — this vinext version may have changed; the suspense-probe fix was NOT applied.`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const renderDependencyPath = renderDependency ? resolveVinextRenderDependencyPath(root) : null;
|
|
706
|
+
if (renderDependencyPath) {
|
|
707
|
+
try {
|
|
708
|
+
const content = readFileSync(renderDependencyPath, "utf8");
|
|
709
|
+
if (!isRenderDependencyAlreadyFixed(content)) {
|
|
710
|
+
const patched = patchRenderDependency(content);
|
|
711
|
+
if (patched !== content) {
|
|
712
|
+
writeFileSync(renderDependencyPath, patched, "utf8");
|
|
713
|
+
changed = true;
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
console.warn(`[cfni:vinext-route-wiring-fix] ${renderDependencyPath} does not match the expected shape for patchRenderDependency — this vinext version may have changed; the render-dependency fix was NOT applied.`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const optimisticLearningTimeoutEnabled = optimisticLearningTimeout !== false && optimisticLearningTimeout !== undefined;
|
|
724
|
+
const optimisticLearningTimeoutPath = optimisticLearningTimeoutEnabled ? resolveVinextOptimisticLearningTimeoutPath(root) : null;
|
|
725
|
+
if (optimisticLearningTimeoutPath) {
|
|
726
|
+
try {
|
|
727
|
+
const content = readFileSync(optimisticLearningTimeoutPath, "utf8");
|
|
728
|
+
if (!isOptimisticLearningTimeoutAlreadyFixed(content)) {
|
|
729
|
+
const patched = patchOptimisticLearningTimeout(content, optimisticLearningTimeoutMs);
|
|
730
|
+
if (patched !== content) {
|
|
731
|
+
writeFileSync(optimisticLearningTimeoutPath, patched, "utf8");
|
|
732
|
+
changed = true;
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
console.warn(`[cfni:vinext-route-wiring-fix] ${optimisticLearningTimeoutPath} does not match the expected shape for patchOptimisticLearningTimeout — this vinext version may have changed; the optimistic-learning-timeout fix was NOT applied.`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const pageInvokerSuspensionReleasePath = pageInvokerSuspensionRelease ? resolveVinextPageInvokerSuspensionReleasePath(root) : null;
|
|
743
|
+
if (pageInvokerSuspensionReleasePath) {
|
|
744
|
+
try {
|
|
745
|
+
const content = readFileSync(pageInvokerSuspensionReleasePath, "utf8");
|
|
746
|
+
if (!isPageInvokerSuspensionReleaseAlreadyFixed(content)) {
|
|
747
|
+
const patched = patchPageInvokerSuspensionRelease(content);
|
|
748
|
+
if (patched !== content) {
|
|
749
|
+
writeFileSync(pageInvokerSuspensionReleasePath, patched, "utf8");
|
|
750
|
+
changed = true;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
console.warn(`[cfni:vinext-route-wiring-fix] ${pageInvokerSuspensionReleasePath} does not match the expected shape for patchPageInvokerSuspensionRelease — this vinext version may have changed; the page-invoker-suspension-release fix was NOT applied.`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const navControllerPath = refreshDeferral ? resolveVinextNavControllerPath(root) : null;
|
|
761
|
+
if (navControllerPath) {
|
|
762
|
+
try {
|
|
763
|
+
const content = readFileSync(navControllerPath, "utf8");
|
|
764
|
+
if (!isRefreshDeferralNavControllerAlreadyFixed(content)) {
|
|
765
|
+
const patched = patchRefreshDeferralNavController(content);
|
|
766
|
+
if (patched !== content) {
|
|
767
|
+
writeFileSync(navControllerPath, patched, "utf8");
|
|
768
|
+
changed = true;
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
console.warn(`[cfni:vinext-route-wiring-fix] ${navControllerPath} does not match the expected shape for patchRefreshDeferralNavController — this vinext version may have changed; the refresh-deferral fix was NOT applied.`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const entryPath = resolveVinextBrowserEntryPath(root);
|
|
775
|
+
if (entryPath && isRefreshDeferralNavControllerAlreadyFixed(readFileSync(navControllerPath, "utf8"))) {
|
|
776
|
+
const entryContent = readFileSync(entryPath, "utf8");
|
|
777
|
+
if (!isRefreshDeferralEntryAlreadyFixed(entryContent)) {
|
|
778
|
+
const patchedEntry = patchRefreshDeferralEntry(entryContent);
|
|
779
|
+
if (patchedEntry !== entryContent) {
|
|
780
|
+
writeFileSync(entryPath, patchedEntry, "utf8");
|
|
781
|
+
changed = true;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
}
|
|
788
|
+
}
|
|
442
789
|
return changed;
|
|
443
790
|
}
|
|
444
791
|
export function bustVinextOptimizeDepsCache(cacheDir) {
|
|
@@ -456,20 +803,61 @@ export function bustVinextOptimizeDepsCache(cacheDir) {
|
|
|
456
803
|
}
|
|
457
804
|
return removed;
|
|
458
805
|
}
|
|
806
|
+
export function isVinextOptimizeDepsCacheStale(root, cacheDir) {
|
|
807
|
+
const patchedFiles = [
|
|
808
|
+
resolveVinextAppPageRouteWiringPath(root),
|
|
809
|
+
resolveVinextRouteMatchingPath(root),
|
|
810
|
+
resolveVinextOptimisticRoutingPath(root),
|
|
811
|
+
resolveVinextBrowserEntryPath(root),
|
|
812
|
+
resolveVinextAppPageProbePath(root),
|
|
813
|
+
resolveVinextRenderDependencyPath(root),
|
|
814
|
+
resolveVinextPageInvokerSuspensionReleasePath(root),
|
|
815
|
+
].filter((filePath) => filePath !== null);
|
|
816
|
+
let newestPatch = 0;
|
|
817
|
+
for (const filePath of patchedFiles) {
|
|
818
|
+
try {
|
|
819
|
+
newestPatch = Math.max(newestPatch, statSync(filePath).mtimeMs);
|
|
820
|
+
}
|
|
821
|
+
catch {
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
if (newestPatch === 0)
|
|
825
|
+
return false;
|
|
826
|
+
for (const sub of ["deps", "deps_ssr", "deps_rsc"]) {
|
|
827
|
+
const dir = resolve(cacheDir, sub);
|
|
828
|
+
if (!existsSync(dir))
|
|
829
|
+
continue;
|
|
830
|
+
try {
|
|
831
|
+
if (statSync(dir).mtimeMs < newestPatch)
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
459
839
|
export function vinextRouteWiringFixPlugin(options = {}) {
|
|
460
840
|
console.warn("[cloudflare-next-intl] WARNING: vinextRouteWiringFix is enabled. Monkey-patching vinext on disk is dangerous and can break routing or upstream compatibility.");
|
|
461
841
|
const routeWiring = options.routeWiring !== false;
|
|
462
842
|
const routeMatching = options.routeMatching !== false;
|
|
463
843
|
const optimisticRouting = options.optimisticRouting !== false;
|
|
464
844
|
const prefetchLearning = options.prefetchLearning !== false;
|
|
845
|
+
const suspenseProbe = options.suspenseProbe !== false;
|
|
846
|
+
const renderDependency = options.renderDependency !== false;
|
|
847
|
+
const optimisticLearningTimeout = options.optimisticLearningTimeout !== false;
|
|
848
|
+
const optimisticLearningTimeoutMs = typeof options.optimisticLearningTimeout === "number" ? options.optimisticLearningTimeout : undefined;
|
|
849
|
+
const pageInvokerSuspensionRelease = options.pageInvokerSuspensionRelease !== false;
|
|
850
|
+
const unblockRenderDependencies = options.unblockRenderDependencies !== false;
|
|
851
|
+
const unblockPageElementDependencies = options.unblockPageElementDependencies === true;
|
|
852
|
+
const refreshDeferral = options.refreshDeferral !== false;
|
|
465
853
|
return {
|
|
466
854
|
name: "cfni:vinext-route-wiring-fix",
|
|
467
855
|
enforce: "pre",
|
|
468
856
|
configResolved(config) {
|
|
469
857
|
const root = config.root || process.cwd();
|
|
470
|
-
const changed = syncPatchVinextOnDisk(root, { routeWiring, routeMatching, optimisticRouting, prefetchLearning });
|
|
471
|
-
|
|
472
|
-
|
|
858
|
+
const changed = syncPatchVinextOnDisk(root, { routeWiring, routeMatching, optimisticRouting, prefetchLearning, suspenseProbe, renderDependency, optimisticLearningTimeout: options.optimisticLearningTimeout ?? true, pageInvokerSuspensionRelease, unblockRenderDependencies, unblockPageElementDependencies, refreshDeferral });
|
|
859
|
+
const cacheDir = config.cacheDir || resolve(root, "node_modules/.vite");
|
|
860
|
+
if (changed || isVinextOptimizeDepsCacheStale(root, cacheDir)) {
|
|
473
861
|
const busted = bustVinextOptimizeDepsCache(cacheDir);
|
|
474
862
|
if (busted) {
|
|
475
863
|
console.log("[cfni:vinext-route-wiring-fix] patched vinext on disk and cleared its stale Vite optimizeDeps cache — dependencies will re-bundle on next request.");
|
|
@@ -478,10 +866,10 @@ export function vinextRouteWiringFixPlugin(options = {}) {
|
|
|
478
866
|
},
|
|
479
867
|
transform(code, id) {
|
|
480
868
|
if (routeWiring && isAppPageRouteWiringFile(id)) {
|
|
481
|
-
if (isAppPageRouteWiringAlreadyFixed(code)) {
|
|
869
|
+
if (isAppPageRouteWiringAlreadyFixed(code, { unblockRenderDependencies, unblockPageElementDependencies }) || !hasRequiredSymbols(code, "routeWiring")) {
|
|
482
870
|
return;
|
|
483
871
|
}
|
|
484
|
-
const patched = patchAppPageRouteWiring(code);
|
|
872
|
+
const patched = patchAppPageRouteWiring(code, { unblockRenderDependencies, unblockPageElementDependencies });
|
|
485
873
|
if (patched === code) {
|
|
486
874
|
return;
|
|
487
875
|
}
|
|
@@ -491,7 +879,7 @@ export function vinextRouteWiringFixPlugin(options = {}) {
|
|
|
491
879
|
};
|
|
492
880
|
}
|
|
493
881
|
if (routeMatching && isRouteMatchingFile(id)) {
|
|
494
|
-
if (isRouteMatchingAlreadyFixed(code)) {
|
|
882
|
+
if (isRouteMatchingAlreadyFixed(code) || !hasRequiredSymbols(code, "routeMatching")) {
|
|
495
883
|
return;
|
|
496
884
|
}
|
|
497
885
|
const patched = patchRouteMatching(code);
|
|
@@ -503,11 +891,17 @@ export function vinextRouteWiringFixPlugin(options = {}) {
|
|
|
503
891
|
map: null,
|
|
504
892
|
};
|
|
505
893
|
}
|
|
506
|
-
if (prefetchLearning && isPrefetchLearningFile(id)) {
|
|
507
|
-
|
|
508
|
-
|
|
894
|
+
if ((prefetchLearning && isPrefetchLearningFile(id)) || (optimisticLearningTimeout && isOptimisticLearningTimeoutFile(id)) || (refreshDeferral && isPrefetchLearningFile(id))) {
|
|
895
|
+
let patched = code;
|
|
896
|
+
if (prefetchLearning && isPrefetchLearningFile(id) && !isPrefetchLearningAlreadyFixed(patched) && hasRequiredSymbols(patched, "prefetchLearning")) {
|
|
897
|
+
patched = patchPrefetchLearning(patched);
|
|
898
|
+
}
|
|
899
|
+
if (optimisticLearningTimeout && isOptimisticLearningTimeoutFile(id)) {
|
|
900
|
+
patched = patchOptimisticLearningTimeout(patched, optimisticLearningTimeoutMs);
|
|
901
|
+
}
|
|
902
|
+
if (refreshDeferral) {
|
|
903
|
+
patched = patchRefreshDeferralEntry(patched);
|
|
509
904
|
}
|
|
510
|
-
const patched = patchPrefetchLearning(code);
|
|
511
905
|
if (patched === code) {
|
|
512
906
|
return;
|
|
513
907
|
}
|
|
@@ -517,7 +911,7 @@ export function vinextRouteWiringFixPlugin(options = {}) {
|
|
|
517
911
|
};
|
|
518
912
|
}
|
|
519
913
|
if (optimisticRouting && isOptimisticRoutingFile(id)) {
|
|
520
|
-
if (isOptimisticRoutingAlreadyFixed(code)) {
|
|
914
|
+
if (isOptimisticRoutingAlreadyFixed(code) || !hasRequiredSymbols(code, "optimisticRouting")) {
|
|
521
915
|
return;
|
|
522
916
|
}
|
|
523
917
|
const patched = patchOptimisticRouting(code);
|
|
@@ -529,6 +923,58 @@ export function vinextRouteWiringFixPlugin(options = {}) {
|
|
|
529
923
|
map: null,
|
|
530
924
|
};
|
|
531
925
|
}
|
|
926
|
+
if (renderDependency && isRenderDependencyFile(id)) {
|
|
927
|
+
if (isRenderDependencyAlreadyFixed(code)) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
const patched = patchRenderDependency(code);
|
|
931
|
+
if (patched === code) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
code: patched,
|
|
936
|
+
map: null,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
if (pageInvokerSuspensionRelease && isPageInvokerSuspensionReleaseFile(id)) {
|
|
940
|
+
if (isPageInvokerSuspensionReleaseAlreadyFixed(code)) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const patched = patchPageInvokerSuspensionRelease(code);
|
|
944
|
+
if (patched === code) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
code: patched,
|
|
949
|
+
map: null,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
if (refreshDeferral && isRefreshDeferralNavControllerFile(id)) {
|
|
953
|
+
if (isRefreshDeferralNavControllerAlreadyFixed(code)) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const patched = patchRefreshDeferralNavController(code);
|
|
957
|
+
if (patched === code) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
code: patched,
|
|
962
|
+
map: null,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
if (suspenseProbe && isAppPageProbeFile(id)) {
|
|
966
|
+
if (isAppPageProbeAlreadyFixed(code) || !hasRequiredSymbols(code, "suspenseProbe")) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const patched = patchAppPageProbe(code);
|
|
970
|
+
if (patched === code) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
return {
|
|
974
|
+
code: patched,
|
|
975
|
+
map: null,
|
|
976
|
+
};
|
|
977
|
+
}
|
|
532
978
|
},
|
|
533
979
|
};
|
|
534
980
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.52",
|
|
4
4
|
"description": "Optimized Next Intl Package Special for App Router and Cloudflare",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"cfni-image-optimizer": "bin/image_optimizer.mjs",
|
|
13
13
|
"optimize-images": "bin/image_optimizer.mjs",
|
|
14
14
|
"cfni-check-dynamic-pages": "bin/check_dynamic_pages.mjs",
|
|
15
|
-
"cfni-check-locale-params": "bin/check_locale_params.mjs"
|
|
15
|
+
"cfni-check-locale-params": "bin/check_locale_params.mjs",
|
|
16
|
+
"cfni-check-layout-queries": "bin/check_layout_queries.mjs"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
18
19
|
"dist",
|
|
@@ -243,6 +244,10 @@
|
|
|
243
244
|
"types": "./dist/src/locale_params_check/index.d.ts",
|
|
244
245
|
"import": "./dist/src/locale_params_check/index.js"
|
|
245
246
|
},
|
|
247
|
+
"./checkLayoutQueries": {
|
|
248
|
+
"types": "./dist/src/layout_queries_check/index.d.ts",
|
|
249
|
+
"import": "./dist/src/layout_queries_check/index.js"
|
|
250
|
+
},
|
|
246
251
|
"./db": {
|
|
247
252
|
"types": "./dist/src/db/index.d.ts",
|
|
248
253
|
"import": "./dist/src/db/index.js"
|