vinext 1.2.1 → 1.3.0

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 (212) hide show
  1. package/README.md +12 -9
  2. package/dist/build/client-build-config.d.ts +64 -3
  3. package/dist/build/client-build-config.js +131 -4
  4. package/dist/build/client-reference-groups.d.ts +163 -0
  5. package/dist/build/client-reference-groups.js +534 -0
  6. package/dist/build/module-dependency-cache.d.ts +31 -0
  7. package/dist/build/module-dependency-cache.js +51 -1
  8. package/dist/build/next-client-runtime-manifests.d.ts +10 -1
  9. package/dist/build/next-client-runtime-manifests.js +19 -4
  10. package/dist/build/nitro-public-files.d.ts +24 -0
  11. package/dist/build/nitro-public-files.js +60 -0
  12. package/dist/build/pages-client-assets-module.js +1 -1
  13. package/dist/build/prerender-paths.js +5 -2
  14. package/dist/build/server-actions-encryption-key.d.ts +23 -0
  15. package/dist/build/server-actions-encryption-key.js +101 -0
  16. package/dist/build/ssr-manifest.d.ts +3 -0
  17. package/dist/build/ssr-manifest.js +10 -1
  18. package/dist/client/client-rewrite-params.d.ts +9 -0
  19. package/dist/client/client-rewrite-params.js +20 -0
  20. package/dist/client/dev-error-overlay.js +2 -1
  21. package/dist/client/navigation-runtime.d.ts +25 -2
  22. package/dist/client/navigation-runtime.js +42 -11
  23. package/dist/client/vinext-next-data.d.ts +0 -1
  24. package/dist/config/config-matchers.d.ts +15 -40
  25. package/dist/config/config-matchers.js +183 -255
  26. package/dist/config/config-source.d.ts +34 -0
  27. package/dist/config/config-source.js +141 -0
  28. package/dist/config/header-params.d.ts +29 -0
  29. package/dist/config/header-params.js +106 -0
  30. package/dist/config/next-config.d.ts +7 -5
  31. package/dist/config/next-config.js +120 -7
  32. package/dist/entries/app-browser-entry.d.ts +14 -1
  33. package/dist/entries/app-browser-entry.js +26 -36
  34. package/dist/entries/app-rsc-entry.d.ts +2 -0
  35. package/dist/entries/app-rsc-entry.js +92 -57
  36. package/dist/entries/app-rsc-manifest.d.ts +10 -0
  37. package/dist/entries/app-rsc-manifest.js +20 -1
  38. package/dist/entries/app-ssr-entry.d.ts +4 -1
  39. package/dist/entries/app-ssr-entry.js +9 -2
  40. package/dist/entries/pages-client-entry.d.ts +1 -0
  41. package/dist/entries/pages-client-entry.js +6 -6
  42. package/dist/entries/pages-server-entry.js +17 -14
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.js +239 -83
  45. package/dist/init.js +17 -0
  46. package/dist/plugins/optimize-imports.d.ts +25 -2
  47. package/dist/plugins/optimize-imports.js +106 -20
  48. package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
  49. package/dist/plugins/rsc-client-reference-loaders.js +336 -18
  50. package/dist/plugins/use-cache-callable.d.ts +5 -0
  51. package/dist/plugins/use-cache-callable.js +30 -8
  52. package/dist/plugins/worker-image-imports.d.ts +1 -0
  53. package/dist/plugins/worker-image-imports.js +1 -1
  54. package/dist/routing/app-route-graph.d.ts +1 -1
  55. package/dist/routing/browser-route-manifest.d.ts +43 -0
  56. package/dist/routing/browser-route-manifest.js +195 -0
  57. package/dist/routing/hashed-route-patterns.d.ts +4 -0
  58. package/dist/routing/hashed-route-patterns.js +22 -0
  59. package/dist/routing/optimistic-route-trie.d.ts +27 -0
  60. package/dist/routing/optimistic-route-trie.js +102 -0
  61. package/dist/routing/route-validation.d.ts +6 -1
  62. package/dist/routing/route-validation.js +26 -10
  63. package/dist/server/app-action-forwarding.d.ts +8 -0
  64. package/dist/server/app-action-forwarding.js +15 -1
  65. package/dist/server/app-browser-entry.js +147 -60
  66. package/dist/server/app-browser-error.d.ts +3 -1
  67. package/dist/server/app-browser-error.js +7 -2
  68. package/dist/server/app-browser-interception-context.d.ts +2 -2
  69. package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
  70. package/dist/server/app-browser-mpa-navigation.js +6 -5
  71. package/dist/server/app-browser-navigation-controller.d.ts +17 -6
  72. package/dist/server/app-browser-navigation-controller.js +88 -26
  73. package/dist/server/app-browser-state.d.ts +2 -2
  74. package/dist/server/app-browser-visible-commit.d.ts +4 -4
  75. package/dist/server/app-client-reference-preloader.d.ts +23 -0
  76. package/dist/server/app-client-reference-preloader.js +30 -7
  77. package/dist/server/app-fallback-renderer.js +2 -1
  78. package/dist/server/app-middleware.d.ts +6 -0
  79. package/dist/server/app-middleware.js +8 -10
  80. package/dist/server/app-optimistic-routing.d.ts +8 -10
  81. package/dist/server/app-optimistic-routing.js +4 -95
  82. package/dist/server/app-page-boundary-render.js +8 -1
  83. package/dist/server/app-page-cache-render.js +1 -0
  84. package/dist/server/app-page-dispatch.js +2 -0
  85. package/dist/server/app-page-render.js +3 -1
  86. package/dist/server/app-page-rsc-render-status.d.ts +1 -0
  87. package/dist/server/app-page-rsc-render-status.js +1 -0
  88. package/dist/server/app-page-search-params-observation.d.ts +1 -4
  89. package/dist/server/app-page-search-params-observation.js +8 -10
  90. package/dist/server/app-page-stream.d.ts +7 -0
  91. package/dist/server/app-page-stream.js +1 -0
  92. package/dist/server/app-pages-bridge.d.ts +2 -1
  93. package/dist/server/app-pages-bridge.js +11 -7
  94. package/dist/server/app-prerender-static-params.js +3 -2
  95. package/dist/server/app-render-dependency.js +42 -2
  96. package/dist/server/app-request-stage-independent-entry.js +2 -2
  97. package/dist/server/app-response-stage-entry.js +3 -3
  98. package/dist/server/app-route-handler-dispatch.js +3 -1
  99. package/dist/server/app-route-handler-execution.d.ts +2 -0
  100. package/dist/server/app-route-handler-execution.js +9 -2
  101. package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
  102. package/dist/server/app-route-handler-middleware-context.js +3 -3
  103. package/dist/server/app-route-handler-response.d.ts +2 -1
  104. package/dist/server/app-route-handler-response.js +2 -2
  105. package/dist/server/app-route-handler-runtime.d.ts +2 -0
  106. package/dist/server/app-route-handler-runtime.js +20 -3
  107. package/dist/server/app-route-module-loader.js +7 -6
  108. package/dist/server/app-route-tree.d.ts +20 -0
  109. package/dist/server/app-route-tree.js +18 -0
  110. package/dist/server/app-router-entry.js +2 -2
  111. package/dist/server/app-rsc-combined-handler.js +8 -1
  112. package/dist/server/app-rsc-errors.d.ts +7 -0
  113. package/dist/server/app-rsc-errors.js +9 -2
  114. package/dist/server/app-rsc-handler.d.ts +4 -1
  115. package/dist/server/app-rsc-handler.js +146 -130
  116. package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
  117. package/dist/server/app-rsc-response-finalizer.js +4 -1
  118. package/dist/server/app-rsc-response-stage.js +3 -1
  119. package/dist/server/app-rsc-route-matching.d.ts +1 -0
  120. package/dist/server/app-rsc-route-matching.js +1 -1
  121. package/dist/server/app-server-action-execution.d.ts +23 -6
  122. package/dist/server/app-server-action-execution.js +50 -56
  123. package/dist/server/app-ssr-entry.d.ts +6 -0
  124. package/dist/server/app-ssr-entry.js +16 -7
  125. package/dist/server/app-ssr-stream.d.ts +7 -0
  126. package/dist/server/app-ssr-stream.js +13 -1
  127. package/dist/server/config-headers.js +12 -3
  128. package/dist/server/isolate-shared-promise.d.ts +10 -0
  129. package/dist/server/isolate-shared-promise.js +33 -0
  130. package/dist/server/isr-cache.js +3 -2
  131. package/dist/server/middleware-matcher-pattern.d.ts +0 -1
  132. package/dist/server/middleware-matcher-pattern.js +11 -140
  133. package/dist/server/middleware-matcher.js +5 -6
  134. package/dist/server/middleware-response-headers.d.ts +31 -0
  135. package/dist/server/middleware-response-headers.js +57 -1
  136. package/dist/server/middleware-runtime.js +26 -13
  137. package/dist/server/navigation-planner.d.ts +2 -2
  138. package/dist/server/nitro-public-files-plugin.d.ts +38 -0
  139. package/dist/server/nitro-public-files-plugin.js +106 -0
  140. package/dist/server/nitro-public-files.d.ts +21 -0
  141. package/dist/server/nitro-public-files.js +39 -0
  142. package/dist/server/pages-asset-tags.d.ts +5 -0
  143. package/dist/server/pages-asset-tags.js +9 -2
  144. package/dist/server/pages-client-assets.d.ts +4 -1
  145. package/dist/server/pages-i18n.d.ts +18 -0
  146. package/dist/server/pages-i18n.js +18 -2
  147. package/dist/server/pages-node-compat.d.ts +5 -3
  148. package/dist/server/pages-node-compat.js +35 -16
  149. package/dist/server/pages-request-pipeline.d.ts +6 -1
  150. package/dist/server/pages-request-pipeline.js +31 -35
  151. package/dist/server/pages-request-stage-entry.js +16 -6
  152. package/dist/server/pages-response-stage-entry.js +1 -1
  153. package/dist/server/pages-revalidate.js +1 -0
  154. package/dist/server/pages-router-entry.js +1 -1
  155. package/dist/server/prod-server.d.ts +2 -2
  156. package/dist/server/prod-server.js +71 -33
  157. package/dist/server/proxy-trust.d.ts +6 -0
  158. package/dist/server/proxy-trust.js +12 -6
  159. package/dist/server/request-pipeline.d.ts +5 -0
  160. package/dist/server/request-pipeline.js +12 -1
  161. package/dist/server/server-reference-loader.js +10 -1
  162. package/dist/server/static-file-cache.js +15 -3
  163. package/dist/server/worker-utils.js +1 -1
  164. package/dist/shims/cache-handler.js +3 -2
  165. package/dist/shims/cache-request-state.d.ts +40 -3
  166. package/dist/shims/cache-request-state.js +70 -5
  167. package/dist/shims/cache-runtime.js +47 -19
  168. package/dist/shims/cache.d.ts +2 -2
  169. package/dist/shims/cache.js +17 -8
  170. package/dist/shims/cdn-purge-routes.d.ts +24 -0
  171. package/dist/shims/cdn-purge-routes.js +77 -0
  172. package/dist/shims/dynamic.js +17 -4
  173. package/dist/shims/form.js +8 -3
  174. package/dist/shims/image-props.d.ts +93 -0
  175. package/dist/shims/image-props.js +323 -0
  176. package/dist/shims/image.d.ts +4 -18
  177. package/dist/shims/image.js +4 -319
  178. package/dist/shims/internal/headers-state.d.ts +7 -0
  179. package/dist/shims/internal/headers-state.js +14 -2
  180. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
  181. package/dist/shims/internal/hybrid-client-route-owner-direct.js +31 -14
  182. package/dist/shims/internal/hybrid-client-route-owner.js +8 -8
  183. package/dist/shims/link.js +5 -3
  184. package/dist/shims/navigation.d.ts +1 -1
  185. package/dist/shims/navigation.js +17 -5
  186. package/dist/shims/og.js +35 -10
  187. package/dist/shims/request-context.d.ts +13 -0
  188. package/dist/shims/router.js +19 -40
  189. package/dist/shims/server.d.ts +31 -4
  190. package/dist/shims/server.js +58 -10
  191. package/dist/shims/thenable-params.d.ts +6 -1
  192. package/dist/shims/thenable-params.js +11 -2
  193. package/dist/shims/unified-request-context.d.ts +5 -0
  194. package/dist/shims/unified-request-context.js +2 -0
  195. package/dist/utils/asset-prefix.d.ts +7 -0
  196. package/dist/utils/asset-prefix.js +10 -1
  197. package/dist/utils/client-build-manifest.js +12 -5
  198. package/dist/utils/client-runtime-metadata.d.ts +1 -0
  199. package/dist/utils/client-runtime-metadata.js +15 -2
  200. package/dist/utils/lazy-chunks.d.ts +4 -0
  201. package/dist/utils/manifest-paths.d.ts +14 -0
  202. package/dist/utils/manifest-paths.js +22 -1
  203. package/dist/utils/path-to-regexp.d.ts +21 -0
  204. package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
  205. package/dist/utils/plugin-react-version.d.ts +15 -0
  206. package/dist/utils/plugin-react-version.js +64 -0
  207. package/dist/utils/public-routes.d.ts +2 -0
  208. package/dist/utils/public-routes.js +18 -7
  209. package/package.json +1 -1
  210. package/dist/server/middleware-path-to-regexp.d.ts +0 -13
  211. package/dist/utils/regex-safety.d.ts +0 -17
  212. package/dist/utils/regex-safety.js +0 -789
@@ -0,0 +1,534 @@
1
+ import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
+ import { parseModuleRecord } from "../plugins/ast-utils.js";
3
+ import { actionOwnerRouteEntryIds } from "./action-owner-manifest.js";
4
+ import { createHash } from "node:crypto";
5
+ //#region src/build/client-reference-groups.ts
6
+ /**
7
+ * Client references whose estimated group cost (see
8
+ * {@link measureClientReferenceGroupCosts}) is larger than this many
9
+ * transformed source bytes keep their own lazy chunk instead of joining a route
10
+ * group. Grouping makes every member load with the group, so a heavy component
11
+ * that a route can reach but rarely renders must not be pulled into that
12
+ * route's startup bundle.
13
+ */
14
+ const CLIENT_REFERENCE_GROUP_MAX_COST_BYTES = 102400;
15
+ const SEGMENT_FILE_ORDER = [
16
+ "layout",
17
+ "template",
18
+ "error",
19
+ "unauthorized",
20
+ "forbidden",
21
+ "not-found",
22
+ "loading",
23
+ "default",
24
+ "page"
25
+ ];
26
+ /**
27
+ * Order route files as they render: outer segments first and, within a
28
+ * segment, each file before the ones it wraps. A group evaluates its members in
29
+ * the order the walk reaches them, so this keeps an outer layout's top-level
30
+ * side effects and CSS ahead of its page's, as Next.js evaluates client
31
+ * references in render order.
32
+ */
33
+ function inRenderOrder(files) {
34
+ const rank = (file) => {
35
+ const index = SEGMENT_FILE_ORDER.indexOf(path.basename(file).split(".")[0]);
36
+ return index === -1 ? SEGMENT_FILE_ORDER.length : index;
37
+ };
38
+ return files.map((file) => ({
39
+ file,
40
+ depth: path.dirname(file).split("/").length,
41
+ rank: rank(file)
42
+ })).sort((a, b) => a.depth - b.depth || a.rank - b.rank).map(({ file }) => file);
43
+ }
44
+ function interceptEntryIds(intercept) {
45
+ return [
46
+ intercept.pagePath,
47
+ ...intercept.layoutPaths,
48
+ ...intercept.loadingPaths ?? [],
49
+ intercept.notFoundPath
50
+ ].filter((value) => typeof value === "string");
51
+ }
52
+ /**
53
+ * Map each client reference reachable from App Router route files to a
54
+ * signature naming the owners that reach it. A route owns its page, layouts,
55
+ * templates, boundaries and parallel slots, which render with it. Intercepting
56
+ * routes (which only render on soft navigation), global-error and
57
+ * global-not-found render on their own, so each is owned by its file, named by
58
+ * its path relative to `root`. References with the same signature are needed
59
+ * together, mirroring Next.js' per-route client entries. The map is in the
60
+ * order a depth-first walk in import order first reaches each reference from
61
+ * the route files in render order, which follows ES module evaluation order.
62
+ *
63
+ * References reachable from no route file (for example vinext's internal
64
+ * runtime references) are omitted and keep their own lazy chunk. So are
65
+ * references that route code reaches through a dynamic import (`next/dynamic`,
66
+ * `React.lazy`), directly or through the dynamically imported module's graph.
67
+ */
68
+ function collectClientReferenceRouteSignatures(options) {
69
+ const canonicalize = options.canonicalizeModuleId ?? ((id) => id);
70
+ const moduleInfoCache = /* @__PURE__ */ new Map();
71
+ const getModuleInfo = (id) => {
72
+ let info = moduleInfoCache.get(id);
73
+ if (info === void 0) {
74
+ info = options.getModuleInfo(id);
75
+ moduleInfoCache.set(id, info);
76
+ }
77
+ return info;
78
+ };
79
+ const lazyReferences = /* @__PURE__ */ new Set();
80
+ const lazyVisited = /* @__PURE__ */ new Set();
81
+ const markLazy = (root) => {
82
+ const stack = [root];
83
+ while (stack.length > 0) {
84
+ const id = stack.pop();
85
+ if (lazyVisited.has(id)) continue;
86
+ lazyVisited.add(id);
87
+ if (options.clientReferenceIds.has(id)) {
88
+ lazyReferences.add(id);
89
+ continue;
90
+ }
91
+ const info = getModuleInfo(id);
92
+ if (info) stack.push(...info.importedIds, ...info.dynamicallyImportedIds);
93
+ }
94
+ };
95
+ const referencesByFile = /* @__PURE__ */ new Map();
96
+ const referencesReachedFrom = (file) => {
97
+ let references = referencesByFile.get(file);
98
+ if (references) return references;
99
+ references = /* @__PURE__ */ new Set();
100
+ const visited = /* @__PURE__ */ new Set();
101
+ const stack = [canonicalize(file)];
102
+ while (stack.length > 0) {
103
+ const id = stack.pop();
104
+ if (visited.has(id)) continue;
105
+ visited.add(id);
106
+ if (options.clientReferenceIds.has(id)) {
107
+ references.add(id);
108
+ continue;
109
+ }
110
+ const info = getModuleInfo(id);
111
+ if (!info) continue;
112
+ for (const importedId of info.dynamicallyImportedIds) markLazy(importedId);
113
+ for (let index = info.importedIds.length - 1; index >= 0; index--) stack.push(info.importedIds[index]);
114
+ }
115
+ referencesByFile.set(file, references);
116
+ return references;
117
+ };
118
+ const owners = /* @__PURE__ */ new Map();
119
+ const addOwner = (owner, files) => {
120
+ for (const file of files) for (const reference of referencesReachedFrom(file)) {
121
+ let referenceOwners = owners.get(reference);
122
+ if (!referenceOwners) {
123
+ referenceOwners = /* @__PURE__ */ new Set();
124
+ owners.set(reference, referenceOwners);
125
+ }
126
+ referenceOwners.add(owner);
127
+ }
128
+ };
129
+ const fileOwner = (file) => path.relative(options.root, file);
130
+ for (const route of options.routes) {
131
+ addOwner(route.pattern, inRenderOrder(actionOwnerRouteEntryIds({
132
+ ...route,
133
+ parallelSlots: route.parallelSlots.map((slot) => ({
134
+ ...slot,
135
+ interceptingRoutes: []
136
+ })),
137
+ siblingIntercepts: []
138
+ })));
139
+ const intercepts = [...route.parallelSlots.flatMap((slot) => slot.interceptingRoutes), ...route.siblingIntercepts];
140
+ for (const intercept of intercepts) addOwner(fileOwner(intercept.pagePath), inRenderOrder(interceptEntryIds(intercept)));
141
+ }
142
+ for (const file of options.sharedRoots ?? []) addOwner(fileOwner(file), [file]);
143
+ const signatures = /* @__PURE__ */ new Map();
144
+ for (const [reference, referenceOwners] of owners) {
145
+ if (lazyReferences.has(reference)) continue;
146
+ signatures.set(reference, [...referenceOwners].sort().join("\n"));
147
+ }
148
+ return signatures;
149
+ }
150
+ /**
151
+ * Plan which client references SSR loads before rendering each route. A route
152
+ * preloads every client reference its page, layouts, templates, boundaries and
153
+ * parallel slots reach, statically or through dynamic imports (a
154
+ * `next/dynamic` component renders on the server), plus the `always` set.
155
+ * Intercepting routes only render on soft navigation, which SSR never
156
+ * renders, so they add to no set. `entryIds` are the RSC graph's entries:
157
+ * walking them up to route files finds what renders outside any route.
158
+ */
159
+ function collectClientReferencePreloadPlan(options) {
160
+ const canonicalize = options.canonicalizeModuleId ?? ((id) => id);
161
+ const moduleInfoCache = /* @__PURE__ */ new Map();
162
+ const getModuleInfo = (id) => {
163
+ let info = moduleInfoCache.get(id);
164
+ if (info === void 0) {
165
+ info = options.getModuleInfo(id);
166
+ moduleInfoCache.set(id, info);
167
+ }
168
+ return info;
169
+ };
170
+ const reached = /* @__PURE__ */ new Set();
171
+ const referencesReachedFrom = (roots, stopAt) => {
172
+ const references = [];
173
+ const visited = /* @__PURE__ */ new Set();
174
+ const stack = roots.map(canonicalize).reverse();
175
+ while (stack.length > 0) {
176
+ const id = stack.pop();
177
+ if (visited.has(id)) continue;
178
+ visited.add(id);
179
+ if (stopAt?.has(id)) continue;
180
+ if (options.clientReferenceIds.has(id)) {
181
+ references.push(id);
182
+ reached.add(id);
183
+ continue;
184
+ }
185
+ const info = getModuleInfo(id);
186
+ if (!info) continue;
187
+ const imports = [...info.importedIds, ...info.dynamicallyImportedIds];
188
+ for (let index = imports.length - 1; index >= 0; index--) stack.push(imports[index]);
189
+ }
190
+ return references;
191
+ };
192
+ const files = [];
193
+ const fileIndices = /* @__PURE__ */ new Map();
194
+ const fileIndex = (file) => {
195
+ const id = canonicalize(file);
196
+ let index = fileIndices.get(id);
197
+ if (index === void 0) {
198
+ index = files.length;
199
+ fileIndices.set(id, index);
200
+ files.push(referencesReachedFrom([id]));
201
+ }
202
+ return index;
203
+ };
204
+ const routes = /* @__PURE__ */ new Map();
205
+ const interceptFiles = [];
206
+ for (const route of options.routes) {
207
+ const indices = inRenderOrder(actionOwnerRouteEntryIds({
208
+ ...route,
209
+ parallelSlots: route.parallelSlots.map((slot) => ({
210
+ ...slot,
211
+ interceptingRoutes: []
212
+ })),
213
+ siblingIntercepts: []
214
+ })).map(fileIndex);
215
+ routes.set(route.pattern, [.../* @__PURE__ */ new Set([...routes.get(route.pattern) ?? [], ...indices])]);
216
+ for (const intercept of [...route.parallelSlots.flatMap((slot) => slot.interceptingRoutes), ...route.siblingIntercepts]) interceptFiles.push(...interceptEntryIds(intercept));
217
+ }
218
+ const routeless = [...new Set((options.routelessFiles ?? []).map(fileIndex))];
219
+ referencesReachedFrom(interceptFiles);
220
+ const alwaysRoots = options.alwaysRoots ?? [];
221
+ const stopAt = /* @__PURE__ */ new Set([...fileIndices.keys(), ...[...interceptFiles, ...alwaysRoots].map(canonicalize)]);
222
+ const always = /* @__PURE__ */ new Set([...referencesReachedFrom(options.entryIds, stopAt), ...referencesReachedFrom(alwaysRoots)]);
223
+ for (const id of options.clientReferenceIds) if (!reached.has(id)) always.add(id);
224
+ return {
225
+ always: [...always],
226
+ files,
227
+ routes,
228
+ routeless
229
+ };
230
+ }
231
+ function importedName(kind, name) {
232
+ if (kind === "Name") return name;
233
+ return kind === "Default" ? "default" : "*";
234
+ }
235
+ /**
236
+ * Read a module's static imports and re-exports for
237
+ * {@link measureClientReferenceGroupCosts}. Returns undefined when the code does
238
+ * not parse or a specifier does not resolve to one of `importedIds`.
239
+ */
240
+ async function readClientModuleBindings(options) {
241
+ const { code } = options;
242
+ const record = parseModuleRecord(code, "js");
243
+ if (!record) return void 0;
244
+ const specifiers = /* @__PURE__ */ new Set([...record.staticImports.map((statement) => statement.moduleRequest.value), ...record.staticExports.flatMap((statement) => statement.entries.flatMap((entry) => entry.moduleRequest ? [entry.moduleRequest.value] : []))]);
245
+ const importedIds = new Set(options.importedIds);
246
+ const ids = /* @__PURE__ */ new Map();
247
+ for (const [specifier, id] of await Promise.all([...specifiers].map(async (specifier) => [specifier, await options.resolve(specifier)]))) {
248
+ if (id === null || !importedIds.has(id)) return void 0;
249
+ ids.set(specifier, id);
250
+ }
251
+ const isIndirect = (statement) => code.startsWith("import", statement.start);
252
+ const exportList = /export\s*[{*]/y;
253
+ const ranges = [
254
+ ...record.staticImports,
255
+ ...record.staticExports.filter((statement) => {
256
+ exportList.lastIndex = statement.start;
257
+ return exportList.test(code);
258
+ }),
259
+ ...record.staticExports.filter(isIndirect).flatMap((statement) => statement.entries)
260
+ ].sort((a, b) => a.start - b.start);
261
+ let body = "";
262
+ let offset = 0;
263
+ for (const range of ranges) {
264
+ if (range.start > offset) body += code.slice(offset, range.start);
265
+ offset = Math.max(offset, range.end);
266
+ }
267
+ body += code.slice(offset);
268
+ const reexports = [];
269
+ const localExports = /* @__PURE__ */ new Map();
270
+ const addLocalExport = (key, exportName) => localExports.set(key, [...localExports.get(key) ?? [], exportName]);
271
+ for (const statement of record.staticExports) for (const entry of statement.entries) {
272
+ const exportName = importedName(entry.exportName.kind, entry.exportName.name);
273
+ const importName = importedName(entry.importName.kind, entry.importName.name);
274
+ if (entry.moduleRequest && isIndirect(statement)) addLocalExport(`\0${entry.importName.start}`, exportName);
275
+ else if (entry.moduleRequest) reexports.push({
276
+ id: ids.get(entry.moduleRequest.value),
277
+ exportName,
278
+ importName
279
+ });
280
+ else if (entry.localName.name !== null) addLocalExport(entry.localName.name, exportName);
281
+ }
282
+ const imports = [];
283
+ for (const statement of record.staticImports) {
284
+ const id = ids.get(statement.moduleRequest.value);
285
+ imports.push({
286
+ id,
287
+ names: /* @__PURE__ */ new Set()
288
+ });
289
+ for (const entry of statement.entries) {
290
+ if (entry.isType) continue;
291
+ const name = importedName(entry.importName.kind, entry.importName.name);
292
+ const local = entry.localName.value;
293
+ const exportNames = [...localExports.get(local) ?? [], ...entry.importName.start !== null && localExports.get(`\0${entry.importName.start}`) || []];
294
+ if (exportNames.length > 0 && !new RegExp(`(?<![\\w$])${local.replaceAll("$", "\\$")}(?![\\w$])`).test(body)) for (const exportName of exportNames) reexports.push({
295
+ id,
296
+ exportName,
297
+ importName: name
298
+ });
299
+ else imports.push({
300
+ id,
301
+ names: name === "*" ? "*" : /* @__PURE__ */ new Set([name])
302
+ });
303
+ }
304
+ }
305
+ const covered = new Set(ids.values());
306
+ for (const id of options.importedIds) if (!covered.has(id)) imports.push({
307
+ id,
308
+ names: "*"
309
+ });
310
+ return {
311
+ bytes: Buffer.byteLength(body, "utf8"),
312
+ imports,
313
+ reexports
314
+ };
315
+ }
316
+ /**
317
+ * Estimate, in transformed source bytes, how much code each client reference
318
+ * would add to a route group's chunk: the modules only it uses, plus the cost
319
+ * of every client reference it statically imports (grouping a reference pulls
320
+ * those along). Modules shared with other client references are not charged
321
+ * to any one of them, so shared libraries do not block grouping. With
322
+ * {@link readClientModuleBindings}, a side-effect-free module only charges the
323
+ * re-exports that are imported from it, so a reference that imports one icon
324
+ * from a barrel is not charged for the whole library.
325
+ *
326
+ * `references` maps each client reference id to its module id in the client
327
+ * graph. `isSideEffectFree` reports a module's package `sideEffects` flag as
328
+ * the bundler resolved it. Traversal stops at modules for which `isExcluded`
329
+ * returns true (CSS, always-loaded framework chunks, virtual modules that must
330
+ * not be loaded re-entrantly). A reference whose import graph cannot be fully
331
+ * loaded is reported as `null` so callers keep it out of groups.
332
+ */
333
+ async function measureClientReferenceGroupCosts(options) {
334
+ const loaded = /* @__PURE__ */ new Map();
335
+ const load = (id) => {
336
+ let module = loaded.get(id);
337
+ if (!module) {
338
+ module = options.loadModule(id).catch(() => null);
339
+ loaded.set(id, module);
340
+ }
341
+ return module;
342
+ };
343
+ const referenceByModuleId = /* @__PURE__ */ new Map();
344
+ for (const [referenceId, moduleId] of options.references) referenceByModuleId.set(moduleId, referenceId);
345
+ const reexportIndexes = /* @__PURE__ */ new Map();
346
+ const indexReexports = (id, bindings) => {
347
+ let index = reexportIndexes.get(id);
348
+ if (index) return index;
349
+ index = {
350
+ byName: /* @__PURE__ */ new Map(),
351
+ stars: [],
352
+ sideEffectTargets: /* @__PURE__ */ new Set()
353
+ };
354
+ for (const edge of bindings.reexports) {
355
+ if (edge.exportName === "*") {
356
+ index.stars.push(edge);
357
+ continue;
358
+ }
359
+ const edges = index.byName.get(edge.exportName);
360
+ if (edges) edges.push(edge);
361
+ else index.byName.set(edge.exportName, [edge]);
362
+ if (!options.isSideEffectFree(edge.id)) index.sideEffectTargets.add(edge.id);
363
+ }
364
+ reexportIndexes.set(id, index);
365
+ return index;
366
+ };
367
+ const noNames = /* @__PURE__ */ new Set();
368
+ const importedNames = (edge) => edge.importName === "*" ? "*" : /* @__PURE__ */ new Set([edge.importName]);
369
+ const slots = /* @__PURE__ */ new Map();
370
+ const moduleBytes = /* @__PURE__ */ new Map();
371
+ let frontier = [];
372
+ const slot = (id, name) => {
373
+ const key = `${name}\0${id}`;
374
+ let found = slots.get(key);
375
+ if (!found) {
376
+ found = {
377
+ id,
378
+ name,
379
+ children: [],
380
+ references: [],
381
+ failed: false
382
+ };
383
+ slots.set(key, found);
384
+ frontier.push(found);
385
+ }
386
+ return found;
387
+ };
388
+ const link = (from, id, names) => {
389
+ if (options.isExcluded(id)) return;
390
+ const reference = referenceByModuleId.get(id);
391
+ if (reference !== void 0) from.references.push(reference);
392
+ else if (names === "*" || !options.isSideEffectFree(id)) from.children.push(slot(id, "*"));
393
+ else for (const name of names) from.children.push(slot(id, name));
394
+ };
395
+ const rootSlots = /* @__PURE__ */ new Map();
396
+ for (const [referenceId, rootModuleId] of options.references) rootSlots.set(referenceId, slot(rootModuleId, "*"));
397
+ while (frontier.length > 0) {
398
+ const current = frontier;
399
+ frontier = [];
400
+ const modules = await Promise.all(current.map((from) => load(from.id)));
401
+ for (let index = 0; index < modules.length; index++) {
402
+ const module = modules[index];
403
+ const from = current[index];
404
+ if (!module) {
405
+ from.failed = true;
406
+ continue;
407
+ }
408
+ const { bindings } = module;
409
+ moduleBytes.set(from.id, bindings?.bytes ?? (module.code ? Buffer.byteLength(module.code, "utf8") : 0));
410
+ if (!bindings) {
411
+ for (const importedId of module.importedIds) link(from, importedId, "*");
412
+ continue;
413
+ }
414
+ for (const edge of bindings.imports) link(from, edge.id, edge.names);
415
+ if (from.name === "*") {
416
+ for (const edge of bindings.reexports) link(from, edge.id, importedNames(edge));
417
+ continue;
418
+ }
419
+ const reexports = indexReexports(from.id, bindings);
420
+ for (const edge of reexports.byName.get(from.name) ?? []) link(from, edge.id, importedNames(edge));
421
+ const starNames = from.name === "default" ? noNames : /* @__PURE__ */ new Set([from.name]);
422
+ for (const edge of reexports.stars) link(from, edge.id, starNames);
423
+ for (const target of reexports.sideEffectTargets) link(from, target, noNames);
424
+ }
425
+ }
426
+ const parents = /* @__PURE__ */ new Map();
427
+ const leads = /* @__PURE__ */ new Set();
428
+ for (const from of slots.values()) {
429
+ for (const child of from.children) {
430
+ let childParents = parents.get(child);
431
+ if (!childParents) {
432
+ childParents = [];
433
+ parents.set(child, childParents);
434
+ }
435
+ childParents.push(from);
436
+ }
437
+ if (from.references.length > 0 || from.failed) leads.add(from);
438
+ }
439
+ for (const lead of leads) for (const parent of parents.get(lead) ?? []) leads.add(parent);
440
+ const importedReferences = /* @__PURE__ */ new Map();
441
+ const unloadable = /* @__PURE__ */ new Set();
442
+ for (const [referenceId, root] of rootSlots) {
443
+ const imported = /* @__PURE__ */ new Set();
444
+ importedReferences.set(referenceId, imported);
445
+ if (!leads.has(root)) continue;
446
+ const visited = /* @__PURE__ */ new Set([root]);
447
+ const stack = [root];
448
+ while (stack.length > 0) {
449
+ const current = stack.pop();
450
+ if (current.failed) unloadable.add(referenceId);
451
+ for (const reference of current.references) if (reference !== referenceId) imported.add(reference);
452
+ for (const child of current.children) if (leads.has(child) && !visited.has(child)) {
453
+ visited.add(child);
454
+ stack.push(child);
455
+ }
456
+ }
457
+ }
458
+ const slotOwners = /* @__PURE__ */ new Map();
459
+ const marks = [];
460
+ for (const [referenceId, root] of rootSlots) if (!unloadable.has(referenceId)) marks.push([root, referenceId]);
461
+ while (marks.length > 0) {
462
+ const [marked, owner] = marks.pop();
463
+ const current = slotOwners.get(marked);
464
+ const next = current === void 0 || current === owner ? owner : null;
465
+ if (next === current) continue;
466
+ slotOwners.set(marked, next);
467
+ for (const child of marked.children) marks.push([child, next]);
468
+ }
469
+ const moduleOwners = /* @__PURE__ */ new Map();
470
+ for (const [marked, owner] of slotOwners) {
471
+ const current = moduleOwners.get(marked.id);
472
+ moduleOwners.set(marked.id, current === void 0 || current === owner ? owner : null);
473
+ }
474
+ const exclusiveBytes = /* @__PURE__ */ new Map();
475
+ for (const [moduleId, owner] of moduleOwners) {
476
+ if (owner === null) continue;
477
+ exclusiveBytes.set(owner, (exclusiveBytes.get(owner) ?? 0) + (moduleBytes.get(moduleId) ?? 0));
478
+ }
479
+ const costs = /* @__PURE__ */ new Map();
480
+ for (const referenceId of options.references.keys()) {
481
+ let cost = 0;
482
+ const visited = /* @__PURE__ */ new Set();
483
+ const queue = [referenceId];
484
+ for (let index = 0; index < queue.length && cost !== null; index++) {
485
+ const current = queue[index];
486
+ if (visited.has(current)) continue;
487
+ visited.add(current);
488
+ cost = unloadable.has(current) ? null : cost + (exclusiveBytes.get(current) ?? 0);
489
+ for (const importedReference of importedReferences.get(current) ?? []) queue.push(importedReference);
490
+ }
491
+ costs.set(referenceId, cost);
492
+ }
493
+ return costs;
494
+ }
495
+ /**
496
+ * Group client references that share a route signature. References without a
497
+ * signature, references whose cost is unknown or above `maxCostBytes`, and
498
+ * signatures with a single member stay ungrouped. Group keys derive from the
499
+ * members' `referenceKeys`, so a group keeps its chunk name when routes change
500
+ * but its members do not.
501
+ */
502
+ function planClientReferenceGroups(options) {
503
+ const membersBySignature = /* @__PURE__ */ new Map();
504
+ for (const [referenceId, signature] of options.signatures) {
505
+ if (!options.referenceIds.has(referenceId)) continue;
506
+ const cost = options.costs.get(referenceId);
507
+ if (cost == null || cost > options.maxCostBytes) continue;
508
+ let members = membersBySignature.get(signature);
509
+ if (!members) {
510
+ members = [];
511
+ membersBySignature.set(signature, members);
512
+ }
513
+ members.push(referenceId);
514
+ }
515
+ const groups = [];
516
+ const usedKeys = /* @__PURE__ */ new Set();
517
+ for (const signature of [...membersBySignature.keys()].sort()) {
518
+ const members = membersBySignature.get(signature);
519
+ if (members.length < 2) continue;
520
+ const memberKeys = members.map((referenceId) => options.referenceKeys.get(referenceId));
521
+ const hash = createHash("sha256").update(memberKeys.sort().join("\0")).digest("hex");
522
+ let length = 10;
523
+ while (usedKeys.has(hash.slice(0, length))) length++;
524
+ const key = hash.slice(0, length);
525
+ usedKeys.add(key);
526
+ groups.push({
527
+ key,
528
+ referenceIds: members
529
+ });
530
+ }
531
+ return groups;
532
+ }
533
+ //#endregion
534
+ export { CLIENT_REFERENCE_GROUP_MAX_COST_BYTES, collectClientReferencePreloadPlan, collectClientReferenceRouteSignatures, measureClientReferenceGroupCosts, planClientReferenceGroups, readClientModuleBindings };
@@ -1,3 +1,34 @@
1
1
  //#region src/build/module-dependency-cache.d.ts
2
2
  export declare function createModuleDependencyCache<T>(collect: (moduleId: string) => Promise<T>): (moduleId: string) => Promise<T>;
3
+ export type ModuleDependencyEntry<T> = {
4
+ dependencies: T[];
5
+ /** Files the module's imports resolved to, without Vite queries. */
6
+ resolvedFiles: string[];
7
+ /**
8
+ * False when an import did not resolve. Vite only caches modules that
9
+ * transformed, so it retries those imports on every request. So does the store.
10
+ */
11
+ cacheable: boolean;
12
+ };
13
+ type FileEvent = {
14
+ files: readonly string[];
15
+ event: "create" | "update" | "delete";
16
+ };
17
+ /**
18
+ * Dependency lists of source modules, kept across loads of a dev virtual
19
+ * module. An entry is dropped by the file events that make Vite transform the
20
+ * module again: an edit, creation or deletion of the module's own file, and
21
+ * deletion of a file one of its imports resolved to.
22
+ */
23
+ export declare class ModuleDependencyStore<T> {
24
+ private readonly entries;
25
+ private readonly pendingReads;
26
+ read(moduleId: string, collect: (moduleId: string) => Promise<ModuleDependencyEntry<T>>): Promise<T[]>;
27
+ /**
28
+ * `files` holds every spelling of one file, such as its symlinked and real
29
+ * path. Returns whether an entry was dropped.
30
+ */
31
+ invalidate(files: readonly string[], event: FileEvent["event"]): boolean;
32
+ clear(): void;
33
+ }
3
34
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { stripViteModuleQuery } from "../utils/path.js";
1
2
  //#region src/build/module-dependency-cache.ts
2
3
  function createModuleDependencyCache(collect) {
3
4
  const cache = /* @__PURE__ */ new Map();
@@ -9,5 +10,54 @@ function createModuleDependencyCache(collect) {
9
10
  return pending;
10
11
  };
11
12
  }
13
+ function isOutdatedBy(moduleId, entry, { files, event }) {
14
+ return files.includes(stripViteModuleQuery(moduleId)) || event === "delete" && entry.resolvedFiles.some((file) => files.includes(file));
15
+ }
16
+ /**
17
+ * Dependency lists of source modules, kept across loads of a dev virtual
18
+ * module. An entry is dropped by the file events that make Vite transform the
19
+ * module again: an edit, creation or deletion of the module's own file, and
20
+ * deletion of a file one of its imports resolved to.
21
+ */
22
+ var ModuleDependencyStore = class {
23
+ entries = /* @__PURE__ */ new Map();
24
+ pendingReads = /* @__PURE__ */ new Set();
25
+ async read(moduleId, collect) {
26
+ const cached = this.entries.get(moduleId);
27
+ if (cached) return cached.dependencies;
28
+ const pending = { events: [] };
29
+ this.pendingReads.add(pending);
30
+ let entry;
31
+ try {
32
+ entry = await collect(moduleId);
33
+ } finally {
34
+ this.pendingReads.delete(pending);
35
+ }
36
+ if (entry.cacheable && pending.events && !pending.events.some((event) => isOutdatedBy(moduleId, entry, event))) this.entries.set(moduleId, entry);
37
+ return entry.dependencies;
38
+ }
39
+ /**
40
+ * `files` holds every spelling of one file, such as its symlinked and real
41
+ * path. Returns whether an entry was dropped.
42
+ */
43
+ invalidate(files, event) {
44
+ const fileEvent = {
45
+ files,
46
+ event
47
+ };
48
+ for (const pending of this.pendingReads) pending.events?.push(fileEvent);
49
+ let dropped = false;
50
+ for (const [moduleId, entry] of this.entries) {
51
+ if (!isOutdatedBy(moduleId, entry, fileEvent)) continue;
52
+ this.entries.delete(moduleId);
53
+ dropped = true;
54
+ }
55
+ return dropped;
56
+ }
57
+ clear() {
58
+ for (const pending of this.pendingReads) pending.events = null;
59
+ this.entries.clear();
60
+ }
61
+ };
12
62
  //#endregion
13
- export { createModuleDependencyCache };
63
+ export { ModuleDependencyStore, createModuleDependencyCache };
@@ -1,3 +1,4 @@
1
+ import { ValidFileMatcher } from "../routing/file-matcher.js";
1
2
  import { ResolvedNextConfig } from "../config/next-config.js";
2
3
  //#region src/build/next-client-runtime-manifests.d.ts
3
4
  type EmitNextClientRuntimeManifestsOptions = {
@@ -5,8 +6,16 @@ type EmitNextClientRuntimeManifestsOptions = {
5
6
  assetsSubdir: string;
6
7
  buildId: string;
7
8
  rewrites: ResolvedNextConfig["rewrites"];
9
+ /** Next.js-format paths of every `pages/` page and API route, e.g. "/posts/[id]". */
10
+ pages: readonly string[];
8
11
  };
9
- export declare function buildNextClientBuildManifestContent(rewrites: ResolvedNextConfig["rewrites"]): string;
12
+ /**
13
+ * Next.js page key for a `pages/` file, e.g. "/posts/[id]". Keeps the raw file
14
+ * name (no decoding), so `pages/caf%C3%A9.js` stays "/caf%C3%A9".
15
+ * Ported from Next.js: packages/next/src/build/route-discovery.ts (`getPageFromPath`).
16
+ */
17
+ export declare function getPagesManifestPage(pagesDir: string, filePath: string, matcher: Pick<ValidFileMatcher, "stripExtension">): string;
18
+ export declare function buildNextClientBuildManifestContent(rewrites: ResolvedNextConfig["rewrites"], pages: readonly string[]): string;
10
19
  export declare function buildNextClientSsgManifestContent(): string;
11
20
  export declare function emitNextClientRuntimeManifests(options: EmitNextClientRuntimeManifestsOptions): void;
12
21
  //#endregion
@@ -1,11 +1,26 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
+ import { getSortedRoutes } from "../routing/route-validation.js";
2
3
  import { toClientRewrites } from "../client/client-rewrites.js";
3
4
  import fs from "node:fs";
4
5
  //#region src/build/next-client-runtime-manifests.ts
5
- function buildNextClientBuildManifestContent(rewrites) {
6
+ /**
7
+ * Next.js page key for a `pages/` file, e.g. "/posts/[id]". Keeps the raw file
8
+ * name (no decoding), so `pages/caf%C3%A9.js` stays "/caf%C3%A9".
9
+ * Ported from Next.js: packages/next/src/build/route-discovery.ts (`getPageFromPath`).
10
+ */
11
+ function getPagesManifestPage(pagesDir, filePath, matcher) {
12
+ const page = `/${matcher.stripExtension(path.relative(pagesDir, filePath))}`;
13
+ const withoutIndex = page.endsWith("/index") ? page.slice(0, -6) : page;
14
+ return withoutIndex === "" ? "/" : withoutIndex;
15
+ }
16
+ function buildNextClientBuildManifestContent(rewrites, pages) {
6
17
  const manifest = {
7
18
  __rewrites: toClientRewrites(rewrites),
8
- sortedPages: []
19
+ sortedPages: getSortedRoutes([
20
+ ...pages,
21
+ "/_app",
22
+ "/_error"
23
+ ])
9
24
  };
10
25
  return `self.__BUILD_MANIFEST = ${JSON.stringify(manifest)};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()`;
11
26
  }
@@ -15,8 +30,8 @@ function buildNextClientSsgManifestContent() {
15
30
  function emitNextClientRuntimeManifests(options) {
16
31
  const manifestDir = path.join(options.clientDir, options.assetsSubdir, options.buildId);
17
32
  fs.mkdirSync(manifestDir, { recursive: true });
18
- fs.writeFileSync(path.join(manifestDir, "_buildManifest.js"), buildNextClientBuildManifestContent(options.rewrites), "utf-8");
33
+ fs.writeFileSync(path.join(manifestDir, "_buildManifest.js"), buildNextClientBuildManifestContent(options.rewrites, options.pages), "utf-8");
19
34
  fs.writeFileSync(path.join(manifestDir, "_ssgManifest.js"), buildNextClientSsgManifestContent(), "utf-8");
20
35
  }
21
36
  //#endregion
22
- export { buildNextClientBuildManifestContent, buildNextClientSsgManifestContent, emitNextClientRuntimeManifests };
37
+ export { buildNextClientBuildManifestContent, buildNextClientSsgManifestContent, emitNextClientRuntimeManifests, getPagesManifestPage };