cloudflare-next-intl 0.9.45 → 0.9.46

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.
@@ -22,8 +22,6 @@ function bindingsFromClause(clause) {
22
22
  const namespaceMatch = /^\*\s*as\s+(\w+)$/.exec(clause.trim());
23
23
  if (namespaceMatch)
24
24
  return [namespaceMatch[1]];
25
- if (clause.trim() === '*')
26
- return [];
27
25
  const braceMatch = /\{([^}]*)\}/.exec(clause);
28
26
  if (braceMatch) {
29
27
  for (const rawItem of braceMatch[1].split(',')) {
@@ -7,6 +7,10 @@ export declare function patchRouteMatching(code: string): string;
7
7
  export declare function isOptimisticRoutingFile(id: string): boolean;
8
8
  export declare function isOptimisticRoutingAlreadyFixed(code: string): boolean;
9
9
  export declare function patchOptimisticRouting(code: string): string;
10
+ export declare function isPrefetchLearningFile(id: string): boolean;
11
+ export declare function isPrefetchLearningAlreadyFixed(code: string): boolean;
12
+ export declare function patchPrefetchLearning(code: string): string;
13
+ export declare function resolveVinextBrowserEntryPath(root?: string): string | null;
10
14
  export declare function isAppPageRouteWiringFile(id: string): boolean;
11
15
  export declare function resolveVinextAppPageRouteWiringPath(root?: string): string | null;
12
16
  export declare function resolveVinextRouteMatchingPath(root?: string): string | null;
@@ -15,11 +19,14 @@ export interface SyncPatchVinextOnDiskOptions {
15
19
  routeWiring?: boolean;
16
20
  routeMatching?: boolean;
17
21
  optimisticRouting?: boolean;
22
+ prefetchLearning?: boolean;
18
23
  }
19
24
  export declare function syncPatchVinextOnDisk(root?: string, options?: SyncPatchVinextOnDiskOptions): boolean;
25
+ export declare function bustVinextOptimizeDepsCache(cacheDir: string): boolean;
20
26
  export interface VinextRouteWiringFixPluginOptions {
21
27
  routeWiring?: boolean;
22
28
  routeMatching?: boolean;
23
29
  optimisticRouting?: boolean;
30
+ prefetchLearning?: boolean;
24
31
  }
25
32
  export declare function vinextRouteWiringFixPlugin(options?: VinextRouteWiringFixPluginOptions): Plugin;
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, writeFileSync, rmSync } 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]*?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) {
@@ -113,14 +113,17 @@ export function isOptimisticRoutingAlreadyFixed(code) {
113
113
  const hasRawPartsFix = code.includes("options.rawUrlParts[0] !== options.match.params.locale");
114
114
  return hasLocalePrefixFirst && hasRawPartsFix;
115
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) {
116
+ const MATCH_OPTIMISTIC_ROUTE_RE = /function\s+matchOptimisticRouteManifestRoute\s*\(\s*options\s*\)\s*\{[\s\S]*?getRouteTrie\([\s\S]*?\)[\s\S]*?\n\}/;
117
+ const FIXED_MATCH_OPTIMISTIC_ROUTE = `function getActiveRouteLocale() {
118
+ return (typeof document !== "undefined" && (document.documentElement?.lang || document.cookie.match(/__user_locale_key__=([^;]+)/)?.[1])) || (typeof window !== "undefined" && window.__VINEXT_LOCALE__) || "en";
119
+ }
120
+ function matchOptimisticRouteManifestRoute(options) {
118
121
  const urlParts = hrefToRouteParts(options.href, options.basePath);
119
122
  if (urlParts === null) return null;
120
123
  const trie = getRouteTrie(options.routeManifest);
121
124
  const hasLeadingLocaleParam = Array.from(options.routeManifest?.segmentGraph?.routes?.values() ?? []).some((r) => r.patternParts?.[0] === ":locale");
122
125
  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";
126
+ const activeLocale = getActiveRouteLocale();
124
127
  if (urlParts.normalized[0] !== activeLocale) {
125
128
  const localeMatch = matchNode(trie, [activeLocale, ...urlParts.normalized], 0, []);
126
129
  if (localeMatch !== null) {
@@ -147,14 +150,85 @@ export function patchOptimisticRouting(code) {
147
150
  return code;
148
151
  }
149
152
  let result = code;
150
- if (MATCH_OPTIMISTIC_ROUTE_RE.test(result)) {
153
+ if (!result.includes("hasLeadingLocaleParam") && MATCH_OPTIMISTIC_ROUTE_RE.test(result)) {
151
154
  result = result.replace(MATCH_OPTIMISTIC_ROUTE_RE, FIXED_MATCH_OPTIMISTIC_ROUTE);
152
155
  }
153
- if (RESOLVE_OPTIMISTIC_NAV_PARAMS_RE.test(result)) {
156
+ if (!result.includes("options.rawUrlParts[0] !== options.match.params.locale") && RESOLVE_OPTIMISTIC_NAV_PARAMS_RE.test(result)) {
154
157
  result = result.replace(RESOLVE_OPTIMISTIC_NAV_PARAMS_RE, FIXED_RESOLVE_OPTIMISTIC_NAV_PARAMS);
155
158
  }
156
159
  return result;
157
160
  }
161
+ export function isPrefetchLearningFile(id) {
162
+ const cleanId = id.split("?")[0].replace(/\\/g, "/");
163
+ return cleanId.endsWith("/app-browser-entry.js") || cleanId.endsWith("/app-browser-entry.ts");
164
+ }
165
+ export function isPrefetchLearningAlreadyFixed(code) {
166
+ const hasFixedFunction = code.includes("isPendingNavigationTarget");
167
+ const hasFixedCallSite = code.includes("targetRscUrl: rscUrl,");
168
+ return hasFixedFunction && hasFixedCallSite;
169
+ }
170
+ const LEARN_TEMPLATES_FN_RE = /async\s+function\s+learnOptimisticRouteTemplatesFromPrefetchCache\s*\(\s*options\s*\)\s*\{[\s\S]*?await\s+Promise\.allSettled\(\s*learning\s*\);\s*\}/;
171
+ const FIXED_LEARN_TEMPLATES_FN = `async function learnOptimisticRouteTemplatesFromPrefetchCache(options) {
172
+ if (options.routeManifest === null) return;
173
+ const learning = [...optimisticRouteTemplateLearning.values()];
174
+ for (const [cacheKey, entry] of getPrefetchCache()) {
175
+ const sourceKey = getOptimisticPrefetchSourceKey({
176
+ cacheKey,
177
+ interceptionContext: options.interceptionContext,
178
+ mountedSlotsHeader: options.mountedSlotsHeader
179
+ });
180
+ if (optimisticRouteTemplateSources.has(sourceKey)) continue;
181
+ if (optimisticRouteTemplateLearning.has(sourceKey)) continue;
182
+ if (entry.prefetchKind === "route-tree") continue;
183
+ const isPendingNavigationTarget = !isSettledPrefetchCacheEntry(entry) && entry.pending !== void 0 && options.targetRscUrl !== void 0 && parsePrefetchCacheKey(cacheKey).rscUrl === options.targetRscUrl;
184
+ if (!isSettledPrefetchCacheEntry(entry) && !isPendingNavigationTarget) continue;
185
+ const promise = (async () => {
186
+ let settledEntry = entry;
187
+ if (!isSettledPrefetchCacheEntry(settledEntry)) {
188
+ await Promise.race([
189
+ settledEntry.pending?.catch(() => {}),
190
+ new Promise((resolve) => setTimeout(resolve, 3000))
191
+ ]);
192
+ settledEntry = getPrefetchCache().get(cacheKey) ?? settledEntry;
193
+ if (!isSettledPrefetchCacheEntry(settledEntry)) return;
194
+ }
195
+ return learnOptimisticRouteTemplateFromPrefetch({
196
+ cacheKey,
197
+ entry: settledEntry,
198
+ interceptionContext: options.interceptionContext,
199
+ mountedSlotsHeader: options.mountedSlotsHeader,
200
+ routeManifest: options.routeManifest
201
+ });
202
+ })().then((learned) => {
203
+ if (learned) optimisticRouteTemplateSources.add(sourceKey);
204
+ }).finally(() => {
205
+ optimisticRouteTemplateLearning.delete(sourceKey);
206
+ });
207
+ optimisticRouteTemplateLearning.set(sourceKey, promise);
208
+ learning.push(promise);
209
+ }
210
+ if (learning.length === 0) return;
211
+ await Promise.allSettled(learning);
212
+ }`;
213
+ const LEARN_TEMPLATES_CALL_RE = /(await\s+learnOptimisticRouteTemplatesFromPrefetchCache\(\s*\{\s*)interceptionContext:\s*requestInterceptionContext,/;
214
+ const FIXED_LEARN_TEMPLATES_CALL = "$1interceptionContext: requestInterceptionContext,\n\t\t\t\t\t\t\ttargetRscUrl: rscUrl,";
215
+ export function patchPrefetchLearning(code) {
216
+ if (isPrefetchLearningAlreadyFixed(code)) {
217
+ return code;
218
+ }
219
+ let result = code;
220
+ if (!result.includes("isPendingNavigationTarget") && LEARN_TEMPLATES_FN_RE.test(result)) {
221
+ result = result.replace(LEARN_TEMPLATES_FN_RE, FIXED_LEARN_TEMPLATES_FN);
222
+ }
223
+ if (!result.includes("targetRscUrl: rscUrl,") && LEARN_TEMPLATES_CALL_RE.test(result)) {
224
+ result = result.replace(LEARN_TEMPLATES_CALL_RE, FIXED_LEARN_TEMPLATES_CALL);
225
+ }
226
+ return result;
227
+ }
228
+ export function resolveVinextBrowserEntryPath(root = process.cwd()) {
229
+ const directPath = resolve(root, "node_modules/vinext/dist/server/app-browser-entry.js");
230
+ return existsSync(directPath) ? directPath : null;
231
+ }
158
232
  export function isAppPageRouteWiringFile(id) {
159
233
  const cleanId = id.split("?")[0].replace(/\\/g, "/");
160
234
  return cleanId.endsWith("/app-page-route-wiring.js") || cleanId.endsWith("/app-page-route-wiring.tsx") || cleanId.endsWith("/app-page-route-wiring.ts");
@@ -172,7 +246,7 @@ export function resolveVinextOptimisticRoutingPath(root = process.cwd()) {
172
246
  return existsSync(directPath) ? directPath : null;
173
247
  }
174
248
  export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
175
- const { routeWiring = true, routeMatching = true, optimisticRouting = true } = options;
249
+ const { routeWiring = true, routeMatching = true, optimisticRouting = true, prefetchLearning = true } = options;
176
250
  let changed = false;
177
251
  const wiringPath = routeWiring ? resolveVinextAppPageRouteWiringPath(root) : null;
178
252
  if (wiringPath) {
@@ -184,6 +258,9 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
184
258
  writeFileSync(wiringPath, patched, "utf8");
185
259
  changed = true;
186
260
  }
261
+ else {
262
+ console.warn(`[cfni:vinext-route-wiring-fix] ${wiringPath} does not match the expected shape for patchAppPageRouteWiring — this vinext version may have changed; the route-wiring fix was NOT applied.`);
263
+ }
187
264
  }
188
265
  }
189
266
  catch {
@@ -199,6 +276,9 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
199
276
  writeFileSync(matchingPath, patched, "utf8");
200
277
  changed = true;
201
278
  }
279
+ else {
280
+ console.warn(`[cfni:vinext-route-wiring-fix] ${matchingPath} does not match the expected shape for patchRouteMatching — this vinext version may have changed; the route-matching fix was NOT applied.`);
281
+ }
202
282
  }
203
283
  }
204
284
  catch {
@@ -214,6 +294,27 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
214
294
  writeFileSync(optimisticPath, patched, "utf8");
215
295
  changed = true;
216
296
  }
297
+ else {
298
+ console.warn(`[cfni:vinext-route-wiring-fix] ${optimisticPath} does not match the expected shape for patchOptimisticRouting — this vinext version may have changed; the optimistic-routing fix was NOT applied.`);
299
+ }
300
+ }
301
+ }
302
+ catch {
303
+ }
304
+ }
305
+ const browserEntryPath = prefetchLearning ? resolveVinextBrowserEntryPath(root) : null;
306
+ if (browserEntryPath) {
307
+ try {
308
+ const content = readFileSync(browserEntryPath, "utf8");
309
+ if (!isPrefetchLearningAlreadyFixed(content)) {
310
+ const patched = patchPrefetchLearning(content);
311
+ if (patched !== content) {
312
+ writeFileSync(browserEntryPath, patched, "utf8");
313
+ changed = true;
314
+ }
315
+ else {
316
+ console.warn(`[cfni:vinext-route-wiring-fix] ${browserEntryPath} does not match the expected shape for patchPrefetchLearning — this vinext version may have changed; the prefetch-learning fix was NOT applied.`);
317
+ }
217
318
  }
218
319
  }
219
320
  catch {
@@ -221,16 +322,39 @@ export function syncPatchVinextOnDisk(root = process.cwd(), options = {}) {
221
322
  }
222
323
  return changed;
223
324
  }
325
+ export function bustVinextOptimizeDepsCache(cacheDir) {
326
+ let removed = false;
327
+ for (const sub of ["deps", "deps_ssr", "deps_rsc"]) {
328
+ const dir = resolve(cacheDir, sub);
329
+ if (!existsSync(dir))
330
+ continue;
331
+ try {
332
+ rmSync(dir, { recursive: true, force: true });
333
+ removed = true;
334
+ }
335
+ catch {
336
+ }
337
+ }
338
+ return removed;
339
+ }
224
340
  export function vinextRouteWiringFixPlugin(options = {}) {
225
341
  const routeWiring = options.routeWiring !== false;
226
342
  const routeMatching = options.routeMatching !== false;
227
343
  const optimisticRouting = options.optimisticRouting !== false;
344
+ const prefetchLearning = options.prefetchLearning !== false;
228
345
  return {
229
346
  name: "cfni:vinext-route-wiring-fix",
230
347
  enforce: "pre",
231
348
  configResolved(config) {
232
349
  const root = config.root || process.cwd();
233
- syncPatchVinextOnDisk(root, { routeWiring, routeMatching, optimisticRouting });
350
+ const changed = syncPatchVinextOnDisk(root, { routeWiring, routeMatching, optimisticRouting, prefetchLearning });
351
+ if (changed) {
352
+ const cacheDir = config.cacheDir || resolve(root, "node_modules/.vite");
353
+ const busted = bustVinextOptimizeDepsCache(cacheDir);
354
+ if (busted) {
355
+ 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.");
356
+ }
357
+ }
234
358
  },
235
359
  transform(code, id) {
236
360
  if (routeWiring && isAppPageRouteWiringFile(id)) {
@@ -253,6 +377,16 @@ export function vinextRouteWiringFixPlugin(options = {}) {
253
377
  map: null,
254
378
  };
255
379
  }
380
+ if (prefetchLearning && isPrefetchLearningFile(id)) {
381
+ if (isPrefetchLearningAlreadyFixed(code)) {
382
+ return;
383
+ }
384
+ const patched = patchPrefetchLearning(code);
385
+ return {
386
+ code: patched,
387
+ map: null,
388
+ };
389
+ }
256
390
  if (optimisticRouting && isOptimisticRoutingFile(id)) {
257
391
  if (isOptimisticRoutingAlreadyFixed(code)) {
258
392
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.9.45",
3
+ "version": "0.9.46",
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",