@timber-js/app 0.2.0-alpha.147 → 0.2.0-alpha.149

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.
Files changed (277) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{actions-OIZ8siQu.js → actions-a0RczDnB.js} +8 -34
  3. package/dist/_chunks/{actions-OIZ8siQu.js.map → actions-a0RczDnB.js.map} +1 -1
  4. package/dist/_chunks/cache-api-Co-yI-gP.js +424 -0
  5. package/dist/_chunks/cache-api-Co-yI-gP.js.map +1 -0
  6. package/dist/_chunks/{metadata-routes-BjepjefT.js → canonicalize-P41GR6tY.js} +65 -2
  7. package/dist/_chunks/canonicalize-P41GR6tY.js.map +1 -0
  8. package/dist/_chunks/{cli-schema-sync-CQaxupHv.js → cli-schema-sync-DJDdyEae.js} +111 -260
  9. package/dist/_chunks/cli-schema-sync-DJDdyEae.js.map +1 -0
  10. package/dist/_chunks/cli-schema-sync-D_IhHEUW.js +2 -0
  11. package/dist/_chunks/{define-DMVhsipD.js → define-Bssfp6ot.js} +3 -33
  12. package/dist/_chunks/define-Bssfp6ot.js.map +1 -0
  13. package/dist/_chunks/{define-C3RvHQPK.js → define-CFmvb4Bt.js} +15 -7
  14. package/dist/_chunks/define-CFmvb4Bt.js.map +1 -0
  15. package/dist/_chunks/{href-validation-DyZIzfMJ.js → href-validation-FiA_rToW.js} +2 -1
  16. package/dist/_chunks/{href-validation-DyZIzfMJ.js.map → href-validation-FiA_rToW.js.map} +1 -1
  17. package/dist/_chunks/{tracing-M_zKubhk.js → logger-CM0clugc.js} +394 -107
  18. package/dist/_chunks/logger-CM0clugc.js.map +1 -0
  19. package/dist/_chunks/navigation-root-bUDZLw74.js.map +1 -1
  20. package/dist/_chunks/plugin-context-DurqFBuY.js +2 -0
  21. package/dist/_chunks/plugin-context-GfDWsdd2.js +268 -0
  22. package/dist/_chunks/plugin-context-GfDWsdd2.js.map +1 -0
  23. package/dist/_chunks/{resolve-schema-Dj8tQloi.js → resolve-schema-3iUvBV5T.js} +3 -30
  24. package/dist/_chunks/resolve-schema-3iUvBV5T.js.map +1 -0
  25. package/dist/_chunks/{schema-bridge-DK_89h8O.js → schema-bridge-BY3QLBL7.js} +35 -2
  26. package/dist/_chunks/schema-bridge-BY3QLBL7.js.map +1 -0
  27. package/dist/_chunks/tree-match-D2l830j2.js +102 -0
  28. package/dist/_chunks/tree-match-D2l830j2.js.map +1 -0
  29. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +1 -1
  30. package/dist/_chunks/{walkers-Ck4Y45XN.js → walkers-DvWDj453.js} +107 -82
  31. package/dist/_chunks/walkers-DvWDj453.js.map +1 -0
  32. package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
  33. package/dist/adapters/cloudflare-kv-cache.js +36 -14
  34. package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
  35. package/dist/adapters/compress-module.d.ts +5 -4
  36. package/dist/adapters/compress-module.d.ts.map +1 -1
  37. package/dist/adapters/nitro.d.ts +1 -3
  38. package/dist/adapters/nitro.d.ts.map +1 -1
  39. package/dist/adapters/nitro.js +14 -134
  40. package/dist/adapters/nitro.js.map +1 -1
  41. package/dist/cache/cache-api.d.ts.map +1 -1
  42. package/dist/cache/index.d.ts +3 -1
  43. package/dist/cache/index.d.ts.map +1 -1
  44. package/dist/cache/index.js +20 -235
  45. package/dist/cache/index.js.map +1 -1
  46. package/dist/cache/invalidation-epoch.d.ts +43 -0
  47. package/dist/cache/invalidation-epoch.d.ts.map +1 -0
  48. package/dist/cache/redis-handler.d.ts +13 -0
  49. package/dist/cache/redis-handler.d.ts.map +1 -1
  50. package/dist/cache/timber-cache.d.ts +17 -9
  51. package/dist/cache/timber-cache.d.ts.map +1 -1
  52. package/dist/cli-schema-sync.d.ts +21 -1
  53. package/dist/cli-schema-sync.d.ts.map +1 -1
  54. package/dist/cli.d.ts +12 -0
  55. package/dist/cli.d.ts.map +1 -1
  56. package/dist/cli.js +26 -31
  57. package/dist/cli.js.map +1 -1
  58. package/dist/client/browser-entry/hmr.d.ts.map +1 -1
  59. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  60. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  61. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  62. package/dist/client/history.d.ts +10 -17
  63. package/dist/client/history.d.ts.map +1 -1
  64. package/dist/client/index.js +1 -1
  65. package/dist/client/index.js.map +1 -1
  66. package/dist/client/internal.js +30 -33
  67. package/dist/client/internal.js.map +1 -1
  68. package/dist/client/navigation-api.d.ts +9 -2
  69. package/dist/client/navigation-api.d.ts.map +1 -1
  70. package/dist/client/navigation-context.d.ts +0 -11
  71. package/dist/client/navigation-context.d.ts.map +1 -1
  72. package/dist/client/navigation-root.d.ts +0 -5
  73. package/dist/client/navigation-root.d.ts.map +1 -1
  74. package/dist/client/router.d.ts.map +1 -1
  75. package/dist/client/rsc-fetch.d.ts.map +1 -1
  76. package/dist/client/use-search-params.d.ts +10 -5
  77. package/dist/client/use-search-params.d.ts.map +1 -1
  78. package/dist/codec.js +1 -1
  79. package/dist/config-validation.d.ts.map +1 -1
  80. package/dist/cookies/define-cookie.d.ts.map +1 -1
  81. package/dist/cookies/index.js +2 -4
  82. package/dist/cookies/index.js.map +1 -1
  83. package/dist/dev-tools/dev-page-shell.d.ts +1 -0
  84. package/dist/dev-tools/dev-page-shell.d.ts.map +1 -1
  85. package/dist/dev-tools/hydration-overlay.d.ts +11 -0
  86. package/dist/dev-tools/hydration-overlay.d.ts.map +1 -0
  87. package/dist/dev-tools/index.d.ts +1 -2
  88. package/dist/dev-tools/index.d.ts.map +1 -1
  89. package/dist/fonts/ast-walk.d.ts +68 -0
  90. package/dist/fonts/ast-walk.d.ts.map +1 -0
  91. package/dist/fonts/ast.d.ts +23 -11
  92. package/dist/fonts/ast.d.ts.map +1 -1
  93. package/dist/fonts/bundle.d.ts +3 -1
  94. package/dist/fonts/bundle.d.ts.map +1 -1
  95. package/dist/fonts/dev-middleware.d.ts +1 -1
  96. package/dist/fonts/dev-middleware.d.ts.map +1 -1
  97. package/dist/fonts/fallbacks.d.ts +0 -7
  98. package/dist/fonts/fallbacks.d.ts.map +1 -1
  99. package/dist/fonts/google.d.ts +4 -2
  100. package/dist/fonts/google.d.ts.map +1 -1
  101. package/dist/fonts/local.d.ts +3 -3
  102. package/dist/fonts/local.d.ts.map +1 -1
  103. package/dist/fonts/paths.d.ts +43 -0
  104. package/dist/fonts/paths.d.ts.map +1 -0
  105. package/dist/fonts/pipeline.d.ts +7 -21
  106. package/dist/fonts/pipeline.d.ts.map +1 -1
  107. package/dist/fonts/transform.d.ts.map +1 -1
  108. package/dist/fonts/virtual-modules.d.ts +1 -18
  109. package/dist/fonts/virtual-modules.d.ts.map +1 -1
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +610 -406
  112. package/dist/index.js.map +1 -1
  113. package/dist/params/index.js +1 -1
  114. package/dist/params/resolve-schema.d.ts.map +1 -1
  115. package/dist/plugin-context.d.ts +28 -5
  116. package/dist/plugin-context.d.ts.map +1 -1
  117. package/dist/plugins/cache.d.ts +13 -5
  118. package/dist/plugins/cache.d.ts.map +1 -1
  119. package/dist/plugins/dev-server.d.ts.map +1 -1
  120. package/dist/plugins/fonts.d.ts +1 -2
  121. package/dist/plugins/fonts.d.ts.map +1 -1
  122. package/dist/plugins/routing.d.ts.map +1 -1
  123. package/dist/plugins/server-bundle.d.ts.map +1 -1
  124. package/dist/routing/codegen.d.ts.map +1 -1
  125. package/dist/routing/convention-lint.d.ts.map +1 -1
  126. package/dist/routing/file-cache.d.ts +8 -0
  127. package/dist/routing/file-cache.d.ts.map +1 -0
  128. package/dist/routing/index.js +2 -2
  129. package/dist/routing/interception.d.ts +0 -5
  130. package/dist/routing/interception.d.ts.map +1 -1
  131. package/dist/routing/manifest-codegen.d.ts +32 -0
  132. package/dist/routing/manifest-codegen.d.ts.map +1 -0
  133. package/dist/routing/scanner.d.ts.map +1 -1
  134. package/dist/schema-bridge.d.ts +15 -0
  135. package/dist/schema-bridge.d.ts.map +1 -1
  136. package/dist/search-params/define.d.ts.map +1 -1
  137. package/dist/search-params/index.js +7 -3
  138. package/dist/search-params/index.js.map +1 -1
  139. package/dist/search-params/wrappers.d.ts.map +1 -1
  140. package/dist/segment-params/define.d.ts.map +1 -1
  141. package/dist/segment-params/index.js +1 -1
  142. package/dist/server/access-gate.d.ts.map +1 -1
  143. package/dist/server/actions.d.ts +0 -14
  144. package/dist/server/actions.d.ts.map +1 -1
  145. package/dist/server/build-manifest.d.ts +0 -29
  146. package/dist/server/build-manifest.d.ts.map +1 -1
  147. package/dist/server/canonicalize.d.ts +10 -0
  148. package/dist/server/canonicalize.d.ts.map +1 -1
  149. package/dist/server/deny-renderer.d.ts +1 -1
  150. package/dist/server/deny-renderer.d.ts.map +1 -1
  151. package/dist/server/html-injector-core.d.ts +8 -0
  152. package/dist/server/html-injector-core.d.ts.map +1 -1
  153. package/dist/server/html-injectors.d.ts.map +1 -1
  154. package/dist/server/index.js +2 -2
  155. package/dist/server/internal.d.ts +1 -5
  156. package/dist/server/internal.d.ts.map +1 -1
  157. package/dist/server/internal.js +12 -171
  158. package/dist/server/internal.js.map +1 -1
  159. package/dist/server/node-stream-transforms.d.ts +0 -11
  160. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  161. package/dist/server/render-utils.d.ts +25 -0
  162. package/dist/server/render-utils.d.ts.map +1 -0
  163. package/dist/server/route-element-builder.d.ts +11 -25
  164. package/dist/server/route-element-builder.d.ts.map +1 -1
  165. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  166. package/dist/server/rsc-entry/helpers.d.ts +2 -18
  167. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  168. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  169. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  170. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  171. package/dist/server/ssr-render.d.ts.map +1 -1
  172. package/dist/shared/href-validation.d.ts.map +1 -1
  173. package/dist/shims/image.d.ts +15 -15
  174. package/package.json +7 -8
  175. package/src/adapters/cloudflare-kv-cache.ts +56 -19
  176. package/src/adapters/compress-module.ts +27 -118
  177. package/src/adapters/nitro.ts +3 -41
  178. package/src/adapters/raw-imports.d.ts +5 -0
  179. package/src/cache/cache-api.ts +9 -1
  180. package/src/cache/index.ts +5 -2
  181. package/src/cache/invalidation-epoch.ts +93 -0
  182. package/src/cache/redis-handler.ts +33 -2
  183. package/src/cache/timber-cache.ts +109 -53
  184. package/src/cli-schema-sync.ts +123 -30
  185. package/src/cli.ts +33 -37
  186. package/src/client/browser-entry/hmr.ts +0 -11
  187. package/src/client/browser-entry/hydrate.ts +39 -5
  188. package/src/client/browser-entry/post-hydration.ts +6 -15
  189. package/src/client/browser-entry/router-init.ts +98 -3
  190. package/src/client/history.ts +10 -24
  191. package/src/client/navigation-api.ts +14 -9
  192. package/src/client/navigation-context.ts +1 -21
  193. package/src/client/navigation-root.tsx +0 -8
  194. package/src/client/router.ts +18 -8
  195. package/src/client/rsc-fetch.ts +7 -2
  196. package/src/client/use-search-params.ts +10 -5
  197. package/src/config-validation.ts +33 -25
  198. package/src/cookies/define-cookie.ts +2 -18
  199. package/src/dev-tools/dev-page-shell.ts +6 -2
  200. package/src/dev-tools/hydration-overlay.ts +158 -0
  201. package/src/dev-tools/index.ts +3 -16
  202. package/src/fonts/ast-walk.ts +235 -0
  203. package/src/fonts/ast.ts +91 -249
  204. package/src/fonts/bundle.ts +16 -9
  205. package/src/fonts/dev-middleware.ts +16 -5
  206. package/src/fonts/fallbacks.ts +0 -23
  207. package/src/fonts/google.ts +7 -3
  208. package/src/fonts/local.ts +7 -6
  209. package/src/fonts/paths.ts +58 -0
  210. package/src/fonts/pipeline.ts +27 -48
  211. package/src/fonts/transform.ts +27 -0
  212. package/src/fonts/virtual-modules.ts +1 -57
  213. package/src/index.ts +5 -11
  214. package/src/params/resolve-schema.ts +2 -34
  215. package/src/plugin-context.ts +95 -17
  216. package/src/plugins/cache.ts +64 -17
  217. package/src/plugins/dev-server.ts +61 -9
  218. package/src/plugins/fonts.ts +21 -6
  219. package/src/plugins/routing.ts +5 -348
  220. package/src/plugins/server-bundle.ts +1 -0
  221. package/src/routing/codegen.ts +3 -2
  222. package/src/routing/convention-lint.ts +4 -3
  223. package/src/routing/file-cache.ts +23 -0
  224. package/src/routing/interception.ts +2 -21
  225. package/src/routing/manifest-codegen.ts +290 -0
  226. package/src/routing/scanner.ts +5 -17
  227. package/src/routing/schema-validation.ts +2 -2
  228. package/src/schema-bridge.ts +63 -2
  229. package/src/search-params/define.ts +34 -2
  230. package/src/search-params/wrappers.ts +21 -3
  231. package/src/segment-params/define.ts +2 -62
  232. package/src/server/access-gate.tsx +8 -0
  233. package/src/server/actions.ts +8 -31
  234. package/src/server/build-manifest.ts +0 -56
  235. package/src/server/canonicalize.ts +6 -3
  236. package/src/server/deny-renderer.ts +6 -0
  237. package/src/server/html-injector-core.ts +19 -0
  238. package/src/server/html-injectors.ts +14 -4
  239. package/src/server/internal.ts +1 -18
  240. package/src/server/node-stream-transforms.ts +5 -72
  241. package/src/server/render-utils.ts +31 -0
  242. package/src/server/route-element-builder.ts +77 -47
  243. package/src/server/rsc-entry/error-renderer.ts +1 -0
  244. package/src/server/rsc-entry/helpers.ts +6 -34
  245. package/src/server/rsc-entry/index.ts +11 -0
  246. package/src/server/rsc-entry/render-route.ts +7 -9
  247. package/src/server/rsc-entry/rsc-payload.ts +2 -1
  248. package/src/server/rsc-entry/ssr-renderer.ts +2 -2
  249. package/src/server/ssr-render.ts +1 -11
  250. package/src/shared/href-validation.ts +8 -0
  251. package/dist/_chunks/cli-schema-sync-CBiMEtqa.js +0 -2
  252. package/dist/_chunks/cli-schema-sync-CQaxupHv.js.map +0 -1
  253. package/dist/_chunks/define-C3RvHQPK.js.map +0 -1
  254. package/dist/_chunks/define-DMVhsipD.js.map +0 -1
  255. package/dist/_chunks/handler-store-BVDgyTh7.js +0 -78
  256. package/dist/_chunks/handler-store-BVDgyTh7.js.map +0 -1
  257. package/dist/_chunks/logger-Dt-jQaBO.js +0 -291
  258. package/dist/_chunks/logger-Dt-jQaBO.js.map +0 -1
  259. package/dist/_chunks/metadata-routes-BjepjefT.js.map +0 -1
  260. package/dist/_chunks/resolve-schema-Dj8tQloi.js.map +0 -1
  261. package/dist/_chunks/schema-bridge-DK_89h8O.js.map +0 -1
  262. package/dist/_chunks/tracing-M_zKubhk.js.map +0 -1
  263. package/dist/_chunks/walkers-Ck4Y45XN.js.map +0 -1
  264. package/dist/_chunks/warnings-CNPXaypM.js +0 -125
  265. package/dist/_chunks/warnings-CNPXaypM.js.map +0 -1
  266. package/dist/client/link-pending-store.d.ts +0 -65
  267. package/dist/client/link-pending-store.d.ts.map +0 -1
  268. package/dist/client/nav-link-store.d.ts +0 -27
  269. package/dist/client/nav-link-store.d.ts.map +0 -1
  270. package/dist/dev-tools/warnings.d.ts +0 -109
  271. package/dist/dev-tools/warnings.d.ts.map +0 -1
  272. package/dist/server/flush.d.ts +0 -74
  273. package/dist/server/flush.d.ts.map +0 -1
  274. package/src/client/link-pending-store.ts +0 -116
  275. package/src/client/nav-link-store.ts +0 -37
  276. package/src/dev-tools/warnings.ts +0 -238
  277. package/src/server/flush.ts +0 -183
@@ -10,24 +10,25 @@
10
10
 
11
11
  import type { Plugin, ViteDevServer } from 'vite';
12
12
  import { writeFile, mkdir } from 'node:fs/promises';
13
- import { existsSync, readFileSync } from 'node:fs';
14
13
  import { join } from 'node:path';
15
14
  import { scanRoutes } from '../routing/scanner.js';
16
15
  import { generateRouteMap } from '../routing/codegen.js';
17
- import { collectInterceptionRewrites } from '../routing/interception.js';
18
16
  import {
19
17
  buildDirToRouteMap,
20
18
  writeSegmentTypes,
21
19
  type SegmentInfo,
22
20
  } from '../routing/segment-codegen.js';
23
21
  import { validateSchemaAgainstRoutes } from '../routing/schema-validation.js';
24
-
22
+ import {
23
+ generateManifestModule,
24
+ generateSearchParamsRegistryModule,
25
+ generateSchemaModule,
26
+ } from '../routing/manifest-codegen.js';
25
27
  import {
26
28
  lintConventions,
27
29
  checkAppDirExists,
28
30
  formatConventionWarnings,
29
31
  } from '../routing/convention-lint.js';
30
- import type { RouteTree, SegmentNode, RouteFile } from '../routing/types.js';
31
32
  import type { PluginContext } from '../plugin-context.js';
32
33
 
33
34
  const VIRTUAL_MODULE_ID = 'virtual:timber-route-manifest';
@@ -413,347 +414,3 @@ function invalidateManifest(server: ViteDevServer): void {
413
414
  // Trigger HMR full reload — route changes are structural
414
415
  server.hot.send({ type: 'full-reload' });
415
416
  }
416
-
417
- /**
418
- * TIM-830: Walk the route tree and collect every page that exports a
419
- * `searchParams` definition, then emit a module that statically imports
420
- * each export and registers it into the shared search-params registry.
421
- *
422
- * Keys use the un-interpolated route pattern (e.g. '/products/[id]') so
423
- * <Link> can look up the definition from the original `href` prop before
424
- * segmentParams are interpolated.
425
- */
426
- /** Schema file extensions to check, in priority order. */
427
- const SCHEMA_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
428
-
429
- /**
430
- * TIM-931: Generate the virtual:timber-schema module.
431
- *
432
- * When app/schema.{ts,tsx,js,jsx} exists, generates a module that
433
- * imports the schema and resolves its segmentParams codecs into a
434
- * bare-name codec map for runtime coercion.
435
- *
436
- * When no schema exists, exports null.
437
- */
438
- function generateSchemaModule(appDir: string): string {
439
- let schemaPath: string | null = null;
440
- for (const ext of SCHEMA_EXTENSIONS) {
441
- const candidate = join(appDir, `schema${ext}`);
442
- if (existsSync(candidate)) {
443
- schemaPath = candidate;
444
- break;
445
- }
446
- }
447
-
448
- if (!schemaPath) {
449
- return [
450
- '// Auto-generated schema loader — no app/schema.ts found.',
451
- 'export default null;',
452
- ].join('\n');
453
- }
454
-
455
- return [
456
- '// Auto-generated schema loader — do not edit.',
457
- '// Generated by timber-routing plugin (TIM-931).',
458
- '',
459
- `import schema from ${JSON.stringify(schemaPath)};`,
460
- `import { resolveSchemaCodecs } from '@timber-js/app/params';`,
461
- '',
462
- 'const globalCodecs = schema?.segmentParams',
463
- ' ? resolveSchemaCodecs(schema.segmentParams)',
464
- ' : null;',
465
- '',
466
- 'export default globalCodecs;',
467
- ].join('\n');
468
- }
469
-
470
- function generateSearchParamsRegistryModule(tree: RouteTree): string {
471
- const entries: Array<{ urlPath: string; filePath: string }> = [];
472
-
473
- function walk(node: SegmentNode): void {
474
- // params.ts is the canonical location for `export const searchParams`;
475
- // page.tsx is the legacy fallback (see codegen.ts `collectRoutes`).
476
- if (node.page) {
477
- if (node.params && fileHasSearchParamsExport(node.params.filePath)) {
478
- entries.push({ urlPath: node.urlPath, filePath: node.params.filePath });
479
- } else if (fileHasSearchParamsExport(node.page.filePath)) {
480
- entries.push({ urlPath: node.urlPath, filePath: node.page.filePath });
481
- }
482
- }
483
- for (const child of node.children) walk(child);
484
- for (const slot of Object.values(node.slots)) walk(slot);
485
- }
486
- walk(tree.root);
487
-
488
- // Deterministic order for stable output / snapshots
489
- entries.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
490
-
491
- const lines: string[] = [];
492
- lines.push('// Auto-generated search-params registry — do not edit.');
493
- lines.push('// Generated by timber-routing plugin (TIM-830).');
494
- lines.push('');
495
- lines.push("import { registerSearchParams } from '@timber-js/app/search-params';");
496
- lines.push('');
497
-
498
- // Static imports — Vite/Rolldown tree-shakes the page component body out
499
- // of this chunk, leaving only the `searchParams` named export.
500
- entries.forEach((entry, i) => {
501
- lines.push(`import { searchParams as r${i} } from ${JSON.stringify(entry.filePath)};`);
502
- });
503
- lines.push('');
504
-
505
- entries.forEach((entry, i) => {
506
- lines.push(`registerSearchParams(${JSON.stringify(entry.urlPath)}, r${i});`);
507
- });
508
- lines.push('');
509
-
510
- // Exporting an empty object keeps the module a real ESM module even
511
- // when there are zero entries (no top-level imports in that case).
512
- lines.push('export {};');
513
- lines.push('');
514
-
515
- return lines.join('\n');
516
- }
517
-
518
- /**
519
- * Lightweight check for a `searchParams` named export. Mirrors
520
- * `fileHasExport('searchParams')` in routing/codegen.ts so we stay
521
- * in sync with what the type codegen detects.
522
- */
523
- function fileHasSearchParamsExport(filePath: string): boolean {
524
- try {
525
- if (!existsSync(filePath)) return false;
526
- const content = readFileSync(filePath, 'utf-8');
527
- return (
528
- /export\s+(const|let|var)\s+searchParams\b/.test(content) ||
529
- /export\s*\{[^}]*\bsearchParams\b[^}]*\}/.test(content)
530
- );
531
- } catch {
532
- return false;
533
- }
534
- }
535
-
536
- /**
537
- * Generate the virtual module source code for the route manifest.
538
- *
539
- * The output is a default-exported object containing the serialized route tree.
540
- * All file references use absolute paths (required for virtual modules).
541
- */
542
- function generateManifestModule(tree: RouteTree, viteRoot: string): string {
543
- const imports: string[] = [];
544
- let importIndex = 0;
545
-
546
- /**
547
- * Create a lazy import expression for a route file.
548
- * Returns the import variable name.
549
- */
550
- function addImport(file: RouteFile): string {
551
- const varName = `_route${importIndex++}`;
552
- imports.push(`const ${varName} = () => import("${file.filePath}");`);
553
- return varName;
554
- }
555
-
556
- /**
557
- * Serialize a segment node to a JS object literal.
558
- */
559
- function serializeNode(node: SegmentNode, indent: string): string {
560
- const nextIndent = indent + ' ';
561
- const parts: string[] = [];
562
-
563
- parts.push(`${nextIndent}segmentName: ${JSON.stringify(node.segmentName)},`);
564
- parts.push(`${nextIndent}segmentType: ${JSON.stringify(node.segmentType)},`);
565
- parts.push(`${nextIndent}urlPath: ${JSON.stringify(node.urlPath)},`);
566
-
567
- if (node.paramName) {
568
- parts.push(`${nextIndent}paramName: ${JSON.stringify(node.paramName)},`);
569
- }
570
-
571
- if (node.interceptionMarker) {
572
- parts.push(`${nextIndent}interceptionMarker: ${JSON.stringify(node.interceptionMarker)},`);
573
- }
574
- if (node.interceptedSegmentName) {
575
- parts.push(
576
- `${nextIndent}interceptedSegmentName: ${JSON.stringify(node.interceptedSegmentName)},`
577
- );
578
- }
579
-
580
- // File conventions — absolute paths as lazy imports
581
- if (node.page) {
582
- const v = addImport(node.page);
583
- parts.push(
584
- `${nextIndent}page: { load: ${v}, filePath: ${JSON.stringify(node.page.filePath)} },`
585
- );
586
- }
587
- if (node.layout) {
588
- const v = addImport(node.layout);
589
- parts.push(
590
- `${nextIndent}layout: { load: ${v}, filePath: ${JSON.stringify(node.layout.filePath)} },`
591
- );
592
- }
593
- if (node.middleware) {
594
- const v = addImport(node.middleware);
595
- parts.push(
596
- `${nextIndent}middleware: { load: ${v}, filePath: ${JSON.stringify(node.middleware.filePath)} },`
597
- );
598
- }
599
- if (node.access) {
600
- const v = addImport(node.access);
601
- parts.push(
602
- `${nextIndent}access: { load: ${v}, filePath: ${JSON.stringify(node.access.filePath)} },`
603
- );
604
- }
605
- if (node.route) {
606
- const v = addImport(node.route);
607
- parts.push(
608
- `${nextIndent}route: { load: ${v}, filePath: ${JSON.stringify(node.route.filePath)} },`
609
- );
610
- }
611
- if (node.error) {
612
- const v = addImport(node.error);
613
- parts.push(
614
- `${nextIndent}error: { load: ${v}, filePath: ${JSON.stringify(node.error.filePath)} },`
615
- );
616
- }
617
- if (node.default) {
618
- const v = addImport(node.default);
619
- parts.push(
620
- `${nextIndent}default: { load: ${v}, filePath: ${JSON.stringify(node.default.filePath)} },`
621
- );
622
- }
623
- if (node.denied) {
624
- const v = addImport(node.denied);
625
- parts.push(
626
- `${nextIndent}denied: { load: ${v}, filePath: ${JSON.stringify(node.denied.filePath)} },`
627
- );
628
- }
629
- if (node.params) {
630
- const v = addImport(node.params);
631
- parts.push(
632
- `${nextIndent}params: { load: ${v}, filePath: ${JSON.stringify(node.params.filePath)} },`
633
- );
634
- }
635
- // searchParams is now a named export from page.tsx, not a separate file.
636
- // The page module's searchParams export is loaded via the page's lazy import.
637
- // Runtime registration happens in the route loader using the page module.
638
-
639
- // Status-code files
640
- if (node.statusFiles && Object.keys(node.statusFiles).length > 0) {
641
- const statusEntries: string[] = [];
642
- for (const [code, file] of Object.entries(node.statusFiles)) {
643
- const v = addImport(file);
644
- statusEntries.push(
645
- `${nextIndent} ${JSON.stringify(code)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`
646
- );
647
- }
648
- parts.push(`${nextIndent}statusFiles: {\n${statusEntries.join(',\n')}\n${nextIndent}},`);
649
- }
650
-
651
- // JSON status-code files
652
- if (node.jsonStatusFiles && Object.keys(node.jsonStatusFiles).length > 0) {
653
- const jsonEntries: string[] = [];
654
- for (const [code, file] of Object.entries(node.jsonStatusFiles)) {
655
- const v = addImport(file);
656
- jsonEntries.push(
657
- `${nextIndent} ${JSON.stringify(code)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`
658
- );
659
- }
660
- parts.push(`${nextIndent}jsonStatusFiles: {\n${jsonEntries.join(',\n')}\n${nextIndent}},`);
661
- }
662
-
663
- // Legacy status files
664
- if (node.legacyStatusFiles && Object.keys(node.legacyStatusFiles).length > 0) {
665
- const legacyEntries: string[] = [];
666
- for (const [name, file] of Object.entries(node.legacyStatusFiles)) {
667
- const v = addImport(file);
668
- legacyEntries.push(
669
- `${nextIndent} ${JSON.stringify(name)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`
670
- );
671
- }
672
- parts.push(
673
- `${nextIndent}legacyStatusFiles: {\n${legacyEntries.join(',\n')}\n${nextIndent}},`
674
- );
675
- }
676
-
677
- // Metadata route files (sitemap.ts, robots.ts, icon.tsx, etc.)
678
- if (node.metadataRoutes && Object.keys(node.metadataRoutes).length > 0) {
679
- const metaEntries: string[] = [];
680
- for (const [name, file] of Object.entries(node.metadataRoutes)) {
681
- const v = addImport(file);
682
- metaEntries.push(
683
- `${nextIndent} ${JSON.stringify(name)}: { load: ${v}, filePath: ${JSON.stringify(file.filePath)} }`
684
- );
685
- }
686
- parts.push(`${nextIndent}metadataRoutes: {\n${metaEntries.join(',\n')}\n${nextIndent}},`);
687
- }
688
-
689
- // Children
690
- if (node.children.length > 0) {
691
- const childNodes = node.children.map((c) => serializeNode(c, nextIndent));
692
- parts.push(`${nextIndent}children: [\n${childNodes.join(',\n')}\n${nextIndent}],`);
693
- } else {
694
- parts.push(`${nextIndent}children: [],`);
695
- }
696
-
697
- // Parallel slots
698
- const slotKeys = Object.keys(node.slots);
699
- if (slotKeys.length > 0) {
700
- const slotEntries: string[] = [];
701
- for (const slotName of slotKeys) {
702
- const slotNode = node.slots[slotName]!;
703
- slotEntries.push(
704
- `${nextIndent} ${JSON.stringify(slotName)}: ${serializeNode(slotNode, nextIndent + ' ')}`
705
- );
706
- }
707
- parts.push(`${nextIndent}slots: {\n${slotEntries.join(',\n')}\n${nextIndent}},`);
708
- } else {
709
- parts.push(`${nextIndent}slots: {},`);
710
- }
711
-
712
- return `${indent}{\n${parts.join('\n')}\n${indent}}`;
713
- }
714
-
715
- const rootSerialized = serializeNode(tree.root, ' ');
716
-
717
- // Proxy file
718
- let proxyLine = '';
719
- if (tree.proxy) {
720
- const v = addImport(tree.proxy);
721
- proxyLine = ` proxy: { load: ${v}, filePath: ${JSON.stringify(tree.proxy.filePath)} },`;
722
- }
723
-
724
- // Global error page (Tier 2)
725
- let globalErrorLine = '';
726
- if (tree.globalError) {
727
- const v = addImport(tree.globalError);
728
- globalErrorLine = ` globalError: { load: ${v}, filePath: ${JSON.stringify(tree.globalError.filePath)} },`;
729
- }
730
-
731
- // Interception rewrites — computed at build time from the route tree.
732
- // Only interceptedPattern and interceptingPrefix are needed at runtime.
733
- const rewrites = collectInterceptionRewrites(tree.root);
734
- const rewritesLine =
735
- rewrites.length > 0
736
- ? ` interceptionRewrites: ${JSON.stringify(rewrites.map((r) => ({ interceptedPattern: r.interceptedPattern, interceptingPrefix: r.interceptingPrefix })))},`
737
- : '';
738
-
739
- const code = [
740
- '// Auto-generated route manifest — do not edit.',
741
- '// Generated by timber-routing plugin.',
742
- '',
743
- ...imports,
744
- '',
745
- 'const manifest = {',
746
- ` viteRoot: ${JSON.stringify(viteRoot)},`,
747
- proxyLine,
748
- globalErrorLine,
749
- rewritesLine,
750
- ` root: ${rootSerialized},`,
751
- '};',
752
- '',
753
- 'export default manifest;',
754
- ]
755
- .filter((line) => line !== undefined && line !== '')
756
- .join('\n');
757
-
758
- return code;
759
- }
@@ -38,6 +38,7 @@ const NODE_BUILTINS: string[] = builtinModules.flatMap((m) => [m, `node:${m}`]);
38
38
  // Mirrored on both rsc and ssr environments. See TIM-988.
39
39
  const BUILD_ONLY_EXTERNALS: string[] = [
40
40
  ...NODE_BUILTINS,
41
+ 'vite',
41
42
  'nitro',
42
43
  'rollup',
43
44
  '@mdx-js/rollup',
@@ -8,7 +8,7 @@
8
8
  * No runtime overhead — purely static type generation.
9
9
  */
10
10
 
11
- import { existsSync, readFileSync } from 'node:fs';
11
+ import { existsSync } from 'node:fs';
12
12
  import { join, relative } from 'node:path';
13
13
  import type { RouteTree, SegmentNode } from './types.js';
14
14
  import type { ParamEntry, RouteEntry } from './codegen-types.js';
@@ -18,6 +18,7 @@ import {
18
18
  emitResolveSegmentFieldHelper,
19
19
  formatSearchParamsType,
20
20
  } from './codegen-shared.js';
21
+ import { readFileCached } from './file-cache.js';
21
22
  import { formatLinkCatchAllOverloads, formatTypedLinkOverloads } from './link-codegen.js';
22
23
 
23
24
  /** Options for route map generation. */
@@ -257,7 +258,7 @@ function paramTypeForSegment(segmentType: string): ParamEntry['type'] {
257
258
  function fileHasExport(filePath: string, exportName: string): boolean {
258
259
  if (!existsSync(filePath)) return false;
259
260
  try {
260
- const content = readFileSync(filePath, 'utf-8');
261
+ const content = readFileCached(filePath);
261
262
  const e = exportName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
262
263
  return (
263
264
  new RegExp(`export\\s+(const|let|var)\\s+${e}\\b`).test(content) ||
@@ -11,9 +11,10 @@
11
11
  * Design doc: 07-routing.md, 10-error-handling.md
12
12
  */
13
13
 
14
- import { readFileSync, existsSync } from 'node:fs';
14
+ import { existsSync } from 'node:fs';
15
15
  import type { RouteTree, SegmentNode } from './types.js';
16
16
  import { swallow } from '../server/logger.js';
17
+ import { readFileCached } from './file-cache.js';
17
18
 
18
19
  // ─── Types ──────────────────────────────────────────────────────────────────
19
20
 
@@ -141,7 +142,7 @@ function checkRouteExports(node: SegmentNode, warnings: ConventionWarning[]): vo
141
142
  if (node.route) {
142
143
  const filePath = node.route.filePath;
143
144
  try {
144
- const source = readFileSync(filePath, 'utf-8');
145
+ const source = readFileCached(filePath);
145
146
  if (!EXPORT_PATTERN.test(source)) {
146
147
  // Check if there's a default export (common mistake)
147
148
  const hasDefaultExport = /export\s+default\b/.test(source);
@@ -265,7 +266,7 @@ function checkFileDefaultExport(
265
266
  warnings: ConventionWarning[]
266
267
  ): void {
267
268
  try {
268
- const source = readFileSync(filePath, 'utf-8');
269
+ const source = readFileCached(filePath);
269
270
  if (!DEFAULT_EXPORT_PATTERN.test(source)) {
270
271
  warnings.push({
271
272
  id: `NO_DEFAULT_EXPORT:${filePath}`,
@@ -0,0 +1,23 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+
3
+ const cache = new Map<string, { mtimeMs: number; size: number; content: string }>();
4
+
5
+ /**
6
+ * Read a file's text content, returning a cached copy when the file
7
+ * hasn't been modified since the last read (checked via mtime + size).
8
+ *
9
+ * Falls back to a fresh read on any stat/read error.
10
+ */
11
+ export function readFileCached(filePath: string): string {
12
+ try {
13
+ const stat = statSync(filePath);
14
+ const entry = cache.get(filePath);
15
+ if (entry && entry.mtimeMs === stat.mtimeMs && entry.size === stat.size) return entry.content;
16
+ const content = readFileSync(filePath, 'utf-8');
17
+ cache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, content });
18
+ return content;
19
+ } catch {
20
+ cache.delete(filePath);
21
+ return readFileSync(filePath, 'utf-8');
22
+ }
23
+ }
@@ -32,11 +32,6 @@ export interface InterceptionRewrite {
32
32
  * E.g., "/feed" for a (.)photo/[id] inside /feed/@modal/.
33
33
  */
34
34
  interceptingPrefix: string;
35
- /**
36
- * Segments chain from root → intercepting leaf. Used to build the element
37
- * tree when the interception is active.
38
- */
39
- segmentPath: SegmentNode[];
40
35
  }
41
36
 
42
37
  /**
@@ -94,13 +89,7 @@ function collectFromInterceptingNode(
94
89
  interceptedBase === '/' ? `/${segmentName}` : `${interceptedBase}/${segmentName}`;
95
90
 
96
91
  // Find all leaf pages in the intercepting sub-tree
97
- collectLeavesWithRewrites(
98
- interceptingNode,
99
- interceptedUrlBase,
100
- parentUrlPath,
101
- [...ancestors, interceptingNode],
102
- rewrites
103
- );
92
+ collectLeavesWithRewrites(interceptingNode, interceptedUrlBase, parentUrlPath, rewrites);
104
93
  }
105
94
 
106
95
  /**
@@ -111,14 +100,12 @@ function collectLeavesWithRewrites(
111
100
  node: SegmentNode,
112
101
  interceptedUrlPath: string,
113
102
  interceptingPrefix: string,
114
- segmentPath: SegmentNode[],
115
103
  rewrites: InterceptionRewrite[]
116
104
  ): void {
117
105
  if (node.page) {
118
106
  rewrites.push({
119
107
  interceptedPattern: interceptedUrlPath,
120
108
  interceptingPrefix,
121
- segmentPath: [...segmentPath],
122
109
  });
123
110
  }
124
111
 
@@ -127,13 +114,7 @@ function collectLeavesWithRewrites(
127
114
  child.segmentType === 'group'
128
115
  ? interceptedUrlPath
129
116
  : `${interceptedUrlPath}/${child.segmentName}`;
130
- collectLeavesWithRewrites(
131
- child,
132
- childUrl,
133
- interceptingPrefix,
134
- [...segmentPath, child],
135
- rewrites
136
- );
117
+ collectLeavesWithRewrites(child, childUrl, interceptingPrefix, rewrites);
137
118
  }
138
119
  }
139
120