@thomas.f/prnext 0.1.17-alpha → 0.1.19-alpha

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 (202) hide show
  1. package/README.md +27 -868
  2. package/build/actions.mjs +27 -15
  3. package/build/analyze.mjs +180 -24
  4. package/build/app-config.mjs +13 -0
  5. package/build/app-static.mjs +34 -12
  6. package/build/app-styles.mjs +79 -0
  7. package/build/app.mjs +122 -46
  8. package/build/bundle-runtime.mjs +22 -9
  9. package/build/cache-components.mjs +1 -1
  10. package/build/cache-handlers.mjs +1 -1
  11. package/build/compact-css.mjs +53 -0
  12. package/build/compilation-cache.mjs +269 -0
  13. package/build/compilation-store.mjs +142 -0
  14. package/build/compiler.mjs +34 -10
  15. package/build/config.mjs +34 -3
  16. package/build/create.mjs +239 -0
  17. package/build/css-modules.mjs +56 -12
  18. package/build/deployment-assets.mjs +89 -0
  19. package/build/deployment-config.mjs +18 -0
  20. package/build/dev-css-assets.mjs +173 -0
  21. package/build/dev-css-update.mjs +18 -14
  22. package/build/dev-dependencies.mjs +1 -1
  23. package/build/dev-discovery.mjs +3 -3
  24. package/build/dev-lazy-build.mjs +77 -0
  25. package/build/dev.mjs +15 -8
  26. package/build/dynamic.mjs +14 -7
  27. package/build/edge.mjs +3 -3
  28. package/build/fonts.mjs +1 -1
  29. package/build/framework-imports.mjs +15 -5
  30. package/build/html-only-config.mjs +53 -0
  31. package/build/html-only-graph.mjs +67 -0
  32. package/build/incremental.mjs +112 -18
  33. package/build/index.mjs +90 -28
  34. package/build/instrumentation.mjs +30 -0
  35. package/build/lazy-dynamic-imports.mjs +137 -0
  36. package/build/metadata-routes.mjs +1 -1
  37. package/build/migrate.mjs +4 -4
  38. package/build/migration-audit.mjs +19 -31
  39. package/build/migration-imports.mjs +245 -0
  40. package/build/migration-scenarios-runner.mjs +73 -0
  41. package/build/migration-scenarios.mjs +72 -0
  42. package/build/native-discovery.mjs +65 -5
  43. package/build/native-packages.mjs +2 -0
  44. package/build/native-type-config.mjs +37 -0
  45. package/build/npm-packages.mjs +1 -0
  46. package/build/persistent-cache.mjs +18 -3
  47. package/build/precompress.mjs +1 -1
  48. package/build/prerender-cache.mjs +70 -0
  49. package/build/prerender-config.mjs +29 -0
  50. package/build/prerender-pages.mjs +19 -9
  51. package/build/prerender-pool.mjs +165 -0
  52. package/build/prerender-process-worker.mjs +58 -0
  53. package/build/prerender-worker.mjs +5 -5
  54. package/build/production-builder.mjs +59 -10
  55. package/build/react-compiler-report.mjs +225 -0
  56. package/build/react-compiler.mjs +139 -0
  57. package/build/runtime-contract.mjs +2 -2
  58. package/build/standalone.mjs +21 -3
  59. package/build/static-export.mjs +4 -4
  60. package/build/styles.mjs +6 -4
  61. package/build/transform.mjs +1 -1
  62. package/build/typed-routes.mjs +4 -0
  63. package/build/typescript-dev.mjs +35 -8
  64. package/build/typescript-native-worker.mjs +55 -0
  65. package/build/typescript-native.mjs +18 -0
  66. package/build/typescript-toolchain.mjs +19 -0
  67. package/build/typescript.mjs +6 -4
  68. package/build/webpack-graph.mjs +19 -6
  69. package/build/worker-memory.mjs +9 -0
  70. package/cli.mjs +70 -5
  71. package/compat/activity-cache.cjs +26 -0
  72. package/compat/after.cjs +6 -4
  73. package/compat/app-dynamic.cjs +12 -1
  74. package/compat/app-layout-context.cjs +52 -2
  75. package/compat/base64.cjs +10 -0
  76. package/compat/cache-handlers.cjs +15 -3
  77. package/compat/cache-wire.cjs +28 -0
  78. package/compat/cache.cjs +2 -1
  79. package/compat/cache.d.cts +4 -0
  80. package/compat/cookies.cjs +21 -5
  81. package/compat/data-cache.cjs +12 -8
  82. package/compat/flight-delta.cjs +152 -0
  83. package/compat/headers.cjs +4 -2
  84. package/compat/incremental-cache.cjs +20 -7
  85. package/compat/index.d.ts +69 -4
  86. package/compat/link.cjs +13 -10
  87. package/compat/navigation-authorization.cjs +9 -0
  88. package/compat/navigation-stages.cjs +19 -0
  89. package/compat/offline-store.cjs +11 -0
  90. package/compat/offline.cjs +5 -0
  91. package/compat/offline.d.cts +2 -0
  92. package/compat/redis-cache.cjs +201 -0
  93. package/compat/redis-cache.d.cts +37 -0
  94. package/compat/server-primitives.cjs +31 -11
  95. package/compat/server.d.cts +15 -2
  96. package/compat/static-generation.cjs +7 -0
  97. package/compat/use-cache.cjs +3 -0
  98. package/docs/ai/compatibility.md +248 -2
  99. package/docs/ai/data.md +165 -5
  100. package/docs/ai/deployment.md +158 -0
  101. package/docs/ai/quickstart.md +29 -4
  102. package/docs/ai/routing.md +93 -0
  103. package/docs/ai/troubleshooting.md +116 -2
  104. package/package.json +40 -9
  105. package/runtime/action-client.mjs +10 -4
  106. package/runtime/action-server.mjs +2 -0
  107. package/runtime/app-client.mjs +316 -92
  108. package/runtime/app-content.mjs +4 -3
  109. package/runtime/app-partial-client.mjs +3 -0
  110. package/runtime/app-partial.mjs +1 -1
  111. package/runtime/app-prefetch-constants.mjs +4 -0
  112. package/runtime/app-prefetch.mjs +2 -3
  113. package/runtime/app-render-key.mjs +5 -0
  114. package/runtime/app-render.mjs +88 -28
  115. package/runtime/app-routing.mjs +22 -9
  116. package/runtime/app-static.mjs +18 -8
  117. package/runtime/app-stream-html.mjs +10 -53
  118. package/runtime/app-style-selection.mjs +34 -0
  119. package/runtime/app-styles.mjs +154 -0
  120. package/runtime/atomic-file.mjs +27 -0
  121. package/runtime/cache-components-rsc.mjs +7 -2
  122. package/runtime/cache-request.mjs +2 -1
  123. package/runtime/deployment-client.mjs +19 -0
  124. package/runtime/dev-client.mjs +13 -3
  125. package/runtime/dev-compiler-broker.mjs +42 -0
  126. package/runtime/dev-file-changes.mjs +123 -8
  127. package/runtime/dev-indicator.mjs +8 -3
  128. package/runtime/dev-lazy-imports.mjs +121 -0
  129. package/runtime/dev-lazy-staging.mjs +14 -0
  130. package/runtime/dev-lazy-tool.mjs +16 -0
  131. package/runtime/dev-style-retention.mjs +15 -0
  132. package/runtime/development.mjs +83 -25
  133. package/runtime/document.mjs +1 -1
  134. package/runtime/edge.mjs +6 -5
  135. package/runtime/fetch-cache.mjs +10 -8
  136. package/runtime/flight-delta.mjs +74 -0
  137. package/runtime/flight-tap.mjs +28 -0
  138. package/runtime/fs.mjs +15 -8
  139. package/runtime/html-only.mjs +27 -0
  140. package/runtime/http.mjs +2 -0
  141. package/runtime/incremental-cache.mjs +25 -4
  142. package/runtime/instrumentation.mjs +49 -0
  143. package/runtime/lazy-import-client.mjs +38 -0
  144. package/runtime/navigation-recovery.mjs +36 -0
  145. package/runtime/navigation.mjs +7 -0
  146. package/runtime/node-html-stream.mjs +47 -0
  147. package/runtime/pages-client.mjs +14 -7
  148. package/runtime/pages-navigation.mjs +48 -63
  149. package/runtime/prefetch-scheduler.mjs +110 -0
  150. package/runtime/profile.mjs +14 -0
  151. package/runtime/profiles.json +2 -2
  152. package/runtime/render.mjs +3 -0
  153. package/runtime/route-pattern.mjs +44 -0
  154. package/runtime/rsc-worker.mjs +34 -12
  155. package/runtime/trace.mjs +78 -0
  156. package/runtime/watch-project.mjs +19 -15
  157. package/runtime/worker.mjs +70 -26
  158. package/vendor/nft/LICENSE +7 -0
  159. package/vendor/nft/README.md +16 -0
  160. package/vendor/nft/out/analyze.d.ts +8 -0
  161. package/vendor/nft/out/analyze.js +1216 -0
  162. package/vendor/nft/out/analyze.js.map +1 -0
  163. package/vendor/nft/out/cli.d.ts +2 -0
  164. package/vendor/nft/out/cli.js +94 -0
  165. package/vendor/nft/out/cli.js.map +1 -0
  166. package/vendor/nft/out/fs.d.ts +22 -0
  167. package/vendor/nft/out/fs.js +106 -0
  168. package/vendor/nft/out/fs.js.map +1 -0
  169. package/vendor/nft/out/index.d.ts +4 -0
  170. package/vendor/nft/out/index.js +25 -0
  171. package/vendor/nft/out/index.js.map +1 -0
  172. package/vendor/nft/out/node-file-trace.d.ts +41 -0
  173. package/vendor/nft/out/node-file-trace.js +380 -0
  174. package/vendor/nft/out/node-file-trace.js.map +1 -0
  175. package/vendor/nft/out/resolve-dependency.d.ts +6 -0
  176. package/vendor/nft/out/resolve-dependency.js +427 -0
  177. package/vendor/nft/out/resolve-dependency.js.map +1 -0
  178. package/vendor/nft/out/types.d.ts +66 -0
  179. package/vendor/nft/out/types.js +2 -0
  180. package/vendor/nft/out/types.js.map +1 -0
  181. package/vendor/nft/out/utils/ast-helpers.js +49 -0
  182. package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
  183. package/vendor/nft/out/utils/binary-locators.js +88 -0
  184. package/vendor/nft/out/utils/binary-locators.js.map +1 -0
  185. package/vendor/nft/out/utils/get-package-base.js +30 -0
  186. package/vendor/nft/out/utils/get-package-base.js.map +1 -0
  187. package/vendor/nft/out/utils/interop-require.js +24 -0
  188. package/vendor/nft/out/utils/interop-require.js.map +1 -0
  189. package/vendor/nft/out/utils/node-version.js +10 -0
  190. package/vendor/nft/out/utils/node-version.js.map +1 -0
  191. package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
  192. package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
  193. package/vendor/nft/out/utils/special-cases.js +347 -0
  194. package/vendor/nft/out/utils/special-cases.js.map +1 -0
  195. package/vendor/nft/out/utils/static-eval.js +579 -0
  196. package/vendor/nft/out/utils/static-eval.js.map +1 -0
  197. package/vendor/nft/out/utils/types.js +2 -0
  198. package/vendor/nft/out/utils/types.js.map +1 -0
  199. package/vendor/nft/out/utils/wrappers.js +659 -0
  200. package/vendor/nft/out/utils/wrappers.js.map +1 -0
  201. package/vendor/nft/package.json +4 -0
  202. package/vendor/nft/upstream.json +51 -0
@@ -1,14 +1,76 @@
1
1
  import { context } from 'esbuild';
2
2
  import { readFile, writeFile, mkdir, realpath } from 'node:fs/promises';
3
+ import { realpath as legacyRealpath } from 'node:fs';
3
4
  import { createHash } from 'node:crypto';
4
5
  import path from 'node:path';
5
6
  import {clearDirectoryCache} from './directory-cache.mjs';
6
7
  import {closeWebpackCompiler,webpackCompilerStats} from './webpack-graph.mjs';
7
8
  const contexts = new Map();
8
9
  const LIMIT = 8;
9
- let created = 0, reused = 0;
10
- export const incrementalCompilerStats = () => ({contexts:contexts.size, created, reused,webpack:webpackCompilerStats()});
10
+ const AUXILIARY_LIMIT = 2;
11
+ const RESERVES = Object.freeze({ main: 6, styles: 2 });
12
+ const counters = () => ({ created: 0, reused: 0, evicted: 0 });
13
+ const roles = () => ({ main: counters(), styles: counters(), auxiliary: counters() });
14
+ const totals = counters(), roleTotals = roles();
15
+
16
+ function record(metrics, role, event) {
17
+ totals[event]++;
18
+ roleTotals[role][event]++;
19
+ metrics[event]++;
20
+ metrics.roles[role][event]++;
21
+ }
22
+
23
+ function roleStats(metrics, active) {
24
+ return Object.fromEntries(Object.entries(metrics).map(([role, values]) => [role, {
25
+ ...values,
26
+ contexts: active.filter(slot => slot.role === role).length,
27
+ }]));
28
+ }
29
+
30
+ export function incrementalCompilerStats() {
31
+ const active = [...contexts.values()];
32
+ // Counters belong to retained projects, rather than an unbounded registry of
33
+ // every project ever compiled by this process. Global role totals persist.
34
+ const projects = [...new Set(active.map(slot => slot.metrics))].map(metrics => {
35
+ const retained = active.filter(slot => slot.metrics === metrics);
36
+ return {
37
+ root: metrics.root,
38
+ created: metrics.created,
39
+ reused: metrics.reused,
40
+ evicted: metrics.evicted,
41
+ contexts: retained.length,
42
+ roles: roleStats(metrics.roles, retained),
43
+ };
44
+ });
45
+ return {
46
+ contexts: contexts.size,
47
+ ...totals,
48
+ roles: roleStats(roleTotals, active),
49
+ projects,
50
+ limits: { project: LIMIT, auxiliary: AUXILIARY_LIMIT, reserves: { ...RESERVES } },
51
+ webpack: webpackCompilerStats(),
52
+ };
53
+ }
54
+
55
+ function evictionCandidate(group, role) {
56
+ const candidates = [...contexts].filter(([, slot]) => slot.group === group);
57
+ if (group === 'auxiliary') return candidates[0];
58
+ const count = candidateRole => candidates.filter(([, slot]) => slot.role === candidateRole).length;
59
+ // Both roles can borrow unused capacity. Once full, a role at its reserve
60
+ // replaces its own oldest graph; a role below its reserve reclaims a loan.
61
+ if (count(role) >= RESERVES[role]) return candidates.find(([, slot]) => slot.role === role);
62
+ return candidates.find(([, slot]) => count(slot.role) > RESERVES[slot.role]);
63
+ }
11
64
  export async function closeIncrementalCompiler(root) {
65
+ if (root !== undefined) {
66
+ try { root = await realpath(root); }
67
+ catch (error) {
68
+ // Disposal also runs after users remove a project directory. A supplied
69
+ // canonical path must still match its retained contexts in that case.
70
+ if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') throw error;
71
+ root = path.resolve(root);
72
+ }
73
+ }
12
74
  clearDirectoryCache();
13
75
  const active=[];for(const [key,slot] of contexts)if(root===undefined||slot.root===root){contexts.delete(key);active.push(slot);}
14
76
  const results=await Promise.allSettled([...active.map(slot=>slot.context.dispose()),closeWebpackCompiler(root)]);
@@ -16,18 +78,42 @@ export async function closeIncrementalCompiler(root) {
16
78
  if(errors.length)throw new AggregateError(errors,'Unable to dispose incremental compiler sessions');
17
79
  }
18
80
  /** Retain esbuild's parsed module graphs while rebinding per-build framework plugins. */
19
- export async function incrementalBuild(options, {root,stage,group='project',run=callback=>callback()}) {
20
- const canonicalRoot = await realpath(root);
21
- if (canonicalRoot !== root) {
22
- const canonical = value => typeof value === 'string' ? value.replaceAll(root, canonicalRoot) : Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' && !(value instanceof RegExp) && !ArrayBuffer.isView(value) ? Object.fromEntries(Object.entries(value).map(([key,item]) => [key,canonical(item)])) : value;
23
- return incrementalBuild(canonical(options),{root:canonicalRoot,stage:stage.replace(root,canonicalRoot),group,run});
81
+ export async function incrementalBuild(options, {root,stage,group='project',role=group==='auxiliary'?'auxiliary':'main',run=callback=>callback()}) {
82
+ if (!['project', 'auxiliary'].includes(group) || (group === 'project' ? !Object.hasOwn(RESERVES, role) : role !== 'auxiliary')) {
83
+ throw new TypeError('Invalid incremental compiler group or role');
24
84
  }
85
+ // Canonical identity is for ownership and disposal only. Compilation paths
86
+ // and application strings retain the caller's lexical spelling.
87
+ const ownerRoot = await realpath(root);
25
88
  const virtual=path.join(root,'.prnext-incremental-stage');
89
+ const rootPaths=[root,ownerRoot];
90
+ if(process.platform==='win32'&&root!==ownerRoot){
91
+ // Windows resolvers can preserve a short ancestor while resolving the
92
+ // junction target. The JS realpath walk retains that third spelling.
93
+ rootPaths.push(await new Promise((resolve,reject)=>legacyRealpath(root,(error,value)=>error?reject(error):resolve(value))));
94
+ }
95
+ const virtualPaths=[...new Set(rootPaths.map(directory=>path.join(directory,'.prnext-incremental-stage')))];
96
+ const stageRelative=path.relative(root,stage);
97
+ const stagePaths=[...new Set([stage,...rootPaths.map(directory=>path.resolve(directory,stageRelative))])];
98
+ const relativePath=(directory,value)=>{
99
+ if(typeof value!=='string'||!path.isAbsolute(value))return;
100
+ const relative=path.relative(directory,value);
101
+ if(relative===''||!path.isAbsolute(relative)&&relative!=='..'&&!relative.startsWith('..'+path.sep))return relative;
102
+ };
103
+ const withinPaths=(directories,value)=>directories.some(directory=>relativePath(directory,value)!==undefined);
104
+ const remapPath=(value,directories,target)=>{
105
+ for(const directory of directories){const relative=relativePath(directory,value);if(relative!==undefined)return path.join(target,relative);}
106
+ return value;
107
+ };
26
108
  const toVirtual=value=>typeof value==='string'?value.replaceAll(stage,virtual):value;
27
- const fromVirtual=value=>typeof value==='string'?value.replaceAll(virtual,stage):value;
109
+ // esbuild can canonicalize only the parent of a nonexistent virtual file.
110
+ // Recognize both staging identities on path fields, never application text.
111
+ const fromVirtual=value=>remapPath(value,[...virtualPaths,...stagePaths],stage);
112
+ const toVirtualPath=value=>remapPath(value,stagePaths,virtual);
113
+ const isVirtual=value=>withinPaths(virtualPaths,value),isStaged=value=>withinPaths(stagePaths,value);
28
114
  const deep=(value,map)=>typeof value==='string'?map(value):Array.isArray(value)?value.map(item=>deep(item,map)):value&&typeof value==='object'&&!(value instanceof RegExp)&&!ArrayBuffer.isView(value)?Object.fromEntries(Object.entries(value).map(([key,item])=>[map(key),deep(item,map)])):value;
29
115
  const realResult=result=>result && {...result,...(result.path?{path:fromVirtual(result.path)}:{}),...(result.resolveDir?{resolveDir:fromVirtual(result.resolveDir)}:{})};
30
- const virtualResult=result=>result && {...result,...(result.path?{path:toVirtual(result.path)}:{}),...(result.resolveDir?{resolveDir:toVirtual(result.resolveDir)}:{}),...(result.watchFiles?{watchFiles:result.watchFiles.map(toVirtual)}:{}),...(result.watchDirs?{watchDirs:result.watchDirs.map(toVirtual)}:{})};
116
+ const virtualResult=result=>result && {...result,...(result.path?{path:toVirtualPath(result.path)}:{}),...(result.resolveDir?{resolveDir:toVirtualPath(result.resolveDir)}:{}),...(result.watchFiles?{watchFiles:result.watchFiles.map(toVirtualPath)}:{}),...(result.watchDirs?{watchDirs:result.watchDirs.map(toVirtualPath)}:{})};
31
117
  const hooks=[];
32
118
  const initialOptions={...options,plugins:undefined,write:false};
33
119
  let slot;
@@ -39,18 +125,24 @@ export async function incrementalBuild(options, {root,stage,group='project',run=
39
125
  });
40
126
  const normalized=deep(initialOptions,toVirtual);
41
127
  const signature=JSON.stringify([normalized,hooks.map(({kind,settings})=>[kind,settings?.namespace,settings?.filter?.source,settings?.filter?.flags]),(options.plugins||[]).map(plugin=>plugin.name)]);
42
- const key=group+':'+createHash('sha256').update(signature).digest('hex');
128
+ const key=JSON.stringify([ownerRoot,root,group,role,createHash('sha256').update(signature).digest('hex')]);
43
129
  slot=contexts.get(key);
44
- if(slot){contexts.delete(key);reused++;}
130
+ if(slot){contexts.delete(key);record(slot.metrics,role,'reused');}
45
131
  else {
46
- while([...contexts.values()].filter(slot=>slot.group===group).length>=(group==='auxiliary'?2:LIMIT)){const [oldest,previous]=[...contexts].find(([,slot])=>slot.group===group);contexts.delete(oldest);await previous.context.dispose();}
47
- created++;slot={group,root};
48
- slot.current={hooks,run,toVirtual,fromVirtual,virtualResult,stage};
132
+ const metrics = [...contexts.values()].find(slot => slot.root === ownerRoot)?.metrics || { root: ownerRoot, ...counters(), roles: roles() };
133
+ while ([...contexts.values()].filter(slot => slot.group === group).length >= (group === 'auxiliary' ? AUXILIARY_LIMIT : LIMIT)) {
134
+ const [oldest, previous] = evictionCandidate(group, role);
135
+ contexts.delete(oldest);
136
+ record(previous.metrics, previous.role, 'evicted');
137
+ await previous.context.dispose();
138
+ }
139
+ slot={group,role,root:ownerRoot,metrics};
140
+ slot.current={hooks,run,toVirtual,fromVirtual,virtualResult,stage,isVirtual,isStaged};
49
141
  slot.context=await context({...normalized,plugins:[{name:'prnext-incremental-delegates',setup(builder){
50
142
  slot.builder=builder;
51
143
  builder.onResolve({filter:/.*/},async args=>{
52
144
  const active=slot.current;
53
- if(args.pluginData?.prnextVirtualResolution || !(args.path.startsWith(virtual+path.sep)||args.path.startsWith(active.stage+path.sep)||(args.resolveDir===virtual || args.resolveDir.startsWith(virtual+path.sep))))return;
145
+ if(args.pluginData?.prnextVirtualResolution || !(active.isVirtual(args.path)||active.isStaged(args.path)||active.isVirtual(args.resolveDir)||active.isStaged(args.resolveDir)))return;
54
146
  const result=await builder.resolve(active.fromVirtual(args.path),{resolveDir:active.fromVirtual(args.resolveDir),importer:active.fromVirtual(args.importer),kind:args.kind,with:args.with,pluginData:{...args.pluginData,prnextVirtualResolution:true}});
55
147
  const pluginData={...(result.pluginData || args.pluginData)}; delete pluginData.prnextVirtualResolution;
56
148
  return {...active.virtualResult(result),pluginData};
@@ -63,13 +155,15 @@ export async function incrementalBuild(options, {root,stage,group='project',run=
63
155
  if(settings)builder[kind](settings,callback);else builder[kind](callback);
64
156
  });
65
157
  builder.onLoad({filter:/.*/,namespace:'file'},async args=>{
66
- if(!args.path.startsWith(virtual+path.sep))return;
67
- const actual=slot.current.fromVirtual(args.path),extension=path.extname(actual);
158
+ const active=slot.current;
159
+ if(!active.isVirtual(args.path))return;
160
+ const actual=active.fromVirtual(args.path),extension=path.extname(actual);
68
161
  return {contents:await readFile(actual),loader:options.loader?.[extension] || ({'.json':'json','.ts':'ts','.tsx':'tsx','.jsx':'jsx','.css':'css'}[extension] || 'js'),resolveDir:path.dirname(args.path)};
69
162
  });
70
163
  }}]});
164
+ record(metrics,role,'created');
71
165
  }
72
- slot.current={hooks,run,toVirtual,fromVirtual,virtualResult,stage};
166
+ slot.current={hooks,run,toVirtual,fromVirtual,virtualResult,stage,isVirtual,isStaged};
73
167
  contexts.set(key,slot);
74
168
  const result=await slot.context.rebuild();
75
169
  const relativeVirtual=path.basename(virtual),relativeStage=path.basename(stage);
package/build/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
+ import {compileInstrumentation} from './instrumentation.mjs';
1
2
  import {prerenderPages as prerender} from './prerender-pages.mjs';
2
- import {isolatedPrerender} from './production-builder.mjs';
3
+ import {isolatedPrerender,prerenderProduction} from './production-builder.mjs';
3
4
  import { rename } from '../runtime/fs.mjs';
4
5
  import { writeRuntimeContract } from './runtime-contract.mjs';
5
6
  import { writeBuildOutput } from './output-contract.mjs';
@@ -7,11 +8,14 @@ import { bundleRuntime } from './bundle-runtime.mjs';
7
8
  import {withPersistentTransforms} from './transform-cache.mjs';
8
9
  import {applicationEntry, frameworkImportName, frameworkRuntimeImportPattern} from './framework-imports.mjs';
9
10
  import { expandLocales, localizedStaticPaths } from './i18n.mjs';
10
- import { build as bundle, withCompilation, configureCompiler, compilerSource,buildAnalysisRecords,buildAnalysisRoutes,devDependencies,devStylePlans,devStyleDependencies,devModules,readCompilerFile } from './compiler.mjs';
11
+ import { build as bundle, withCompilation, configureCompiler, compilerSource,buildAnalysisRecords,buildAnalysisRoutes,devDependencies,devStylePlans,devStyleDependencies,devModules,readCompilerFile,compilationGraphCacheReport } from './compiler.mjs';
12
+ import {retainDeploymentAssets} from './deployment-assets.mjs';
13
+ import {selectHtmlOnlyRoutes} from './html-only-config.mjs';
11
14
  import {writeBuildAnalysis} from './analyze.mjs';
12
15
  import {routeFiles} from './dev-discovery.mjs';
16
+ import {tryBuildLazyGraph,rememberLazyBuildContext,forgetLazyBuildContext} from './dev-lazy-build.mjs';
13
17
  import {writeTypedRoutes} from './typed-routes.mjs';
14
- import { mkdir, readFile, writeFile, rm, cp, copyFile, stat, readdir } from 'node:fs/promises';
18
+ import { mkdir, readFile, writeFile, rm, cp, copyFile, stat, readdir, realpath } from 'node:fs/promises';
15
19
  import path from 'node:path';
16
20
  import { fileURLToPath, pathToFileURL } from 'node:url';
17
21
  import { createRequire } from 'node:module';
@@ -40,20 +44,22 @@ import { createRefreshTransform, createSourceLocationTransform, devSingletonsPlu
40
44
  import { snapshotEnvConfig } from '../runtime/env.mjs';
41
45
  import { validateStaticPaths } from '../runtime/pages-paths.mjs';
42
46
  import { projectTsconfig, checkTypeScript } from './typescript.mjs';
47
+ import {createReactCompilerTransform} from './react-compiler.mjs';
48
+ import {writeReactCompilerReport,reactCompilerReport} from './react-compiler-report.mjs';
43
49
  export { staticPath } from '../runtime/pages-paths.mjs';
44
50
 
45
51
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
46
- const compatibleModules = new Set(['link', 'head', 'router', 'compat/router', 'next-router-context', 'next-app-router-context', 'image', 'cache', 'dynamic', 'error', 'document', 'app', 'script', 'og']);
47
- const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx' };
52
+ const compatibleModules = new Set(['link', 'head', 'router', 'compat/router', 'next-router-context', 'next-app-router-context', 'image', 'cache', 'dynamic', 'error', 'document', 'app', 'script', 'og', 'offline', 'redis-cache']);
53
+ const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx', '.mts': 'ts', '.cts': 'ts' };
48
54
  const assetLoaders = Object.fromEntries(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'].map(ext => [ext, 'file']));
49
55
 
50
56
  // These framework files have already been enumerated: copyFile avoids cp's
51
57
  // recursive traversal/stat work for every file. Bound I/O on large installations.
52
- async function copyRuntimeFiles(stage) {
58
+ async function copyRuntimeFiles(stage,{development=true}={}) {
53
59
  const files = (await Promise.all(['runtime', 'compat'].map(async directory => {
54
60
  await mkdir(path.join(stage, directory), { recursive: true });
55
61
  return (await readdir(path.join(packageRoot, directory)))
56
- .filter(name => directory === 'runtime' ? name === 'profiles.json' || name.endsWith('.mjs') && !name.endsWith('.test.mjs') : name.endsWith('.cjs'))
62
+ .filter(name => directory === 'runtime' ? name === 'profiles.json' || development&&name.endsWith('.mjs') && !name.endsWith('.test.mjs') : name.endsWith('.cjs'))
57
63
  .map(name => path.join(directory, name));
58
64
  }))).flat();
59
65
  let next = 0;
@@ -68,18 +74,18 @@ async function copyRuntimeFiles(stage) {
68
74
  if (failed) throw failed.reason;
69
75
  }
70
76
 
71
- function compatibilityPlugin({ browser, pageFiles, projectRoot, documentFile, fonts, refresh, locations }) {
77
+ function compatibilityPlugin({ browser, pageFiles, projectRoot, documentFile, serverOnlyFiles, fonts, refresh, locations, reactCompiler }) {
72
78
  return {
73
79
  name: 'prnext-pages-compatibility',
74
80
  setup(esbuild) {
75
- applicationEntry(esbuild,readCompilerFile);
81
+ applicationEntry(esbuild,readCompilerFile,{transformTypedHelpers:browser && !!reactCompiler});
76
82
  esbuild.onResolve({ filter: /\.(?:css|scss|sass)$/i }, args => args.importer === documentFile
77
83
  ? { errors: [{ text: 'CSS cannot be imported within pages/_document. Move global styles to pages/_app, or return collected styles from Document.getInitialProps.' }] } : undefined);
78
84
  esbuild.onResolve({ filter: frameworkRuntimeImportPattern }, args => {
79
85
  const name = frameworkImportName(args.path);
80
86
  if (!compatibleModules.has(name)) return { errors: [{ text: `PRNext does not implement ${args.path} yet (imported by ${args.importer}). Supported runtime imports: next/link, next/head, next/router, next/image, next/cache, next/dynamic, next/error, next/document, next/app, next/script.` }] };
81
87
  if (browser && name === 'document') return { errors: [{ text: `${args.path} is server-only. Import Document, Html, Head, Main and NextScript from pages/_document, not from a browser page or component.` }] };
82
- if (browser && ['cache', 'og'].includes(name)) return { errors: [{ text: `${args.path} is server-only and cannot be imported by a browser component.` }] };
88
+ if (browser && ['cache', 'og', 'redis-cache'].includes(name)) return { errors: [{ text: `${args.path} is server-only and cannot be imported by a browser component.` }] };
83
89
  const file = name === 'compat/router' ? 'compat-router' : name;
84
90
  return browser ? { path: path.join(packageRoot, 'compat', file + '.cjs') } : { path: `../compat/${file}.cjs`, external: true };
85
91
  });
@@ -95,10 +101,15 @@ function compatibilityPlugin({ browser, pageFiles, projectRoot, documentFile, fo
95
101
  });
96
102
  esbuild.onLoad({ filter: /.*/, namespace: 'prnext-empty' }, () => ({ contents: '', loader: 'js' }));
97
103
  }
98
- esbuild.onLoad({ filter: /\.(?:[cm]?js|jsx|tsx?)$/, namespace: 'file' }, async args => {
104
+ esbuild.onLoad({ filter: /\.(?:[cm]?[jt]s|[jt]sx)$/, namespace: 'file' }, async args => {
99
105
  if (browser && args.path === documentFile) return { errors: [{ text: 'pages/_document is server-only and cannot be imported by a browser page or component.' }] };
100
106
  const original=await readCompilerFile(args.path,args.prnextSource);
101
- const source = await fonts.transform(locations?await locations(original,args.path):original, args.path);
107
+ // Pages shares a server graph with API handlers, Document and data
108
+ // loaders. Keep that entire graph original: an uppercase JSX utility
109
+ // may be called normally there, outside a React render. Browser render
110
+ // graphs receive memoization before instrumentation and JSX lowering.
111
+ const compiled=browser && reactCompiler && args.path!==documentFile && !serverOnlyFiles.has(args.path) ? await reactCompiler(original,args.path,{loaderOutput:args.prnextSource!==undefined}) : original;
112
+ const source = await fonts.transform(locations?await locations(compiled,args.path):compiled, args.path);
102
113
  assertSupportedSource(source, args.path);
103
114
  const dynamicSource = transformDynamicImports(source, args.path, { projectRoot, mode: browser ? 'browser' : 'server' });
104
115
  let contents = browser && pageFiles.has(args.path) ? stripServerCode(dynamicSource, args.path) : dynamicSource;
@@ -144,7 +155,12 @@ let buildQueue = Promise.resolve();
144
155
  let developmentModulesLoaded = false;
145
156
  /** Disposing shared compiler contexts must not interrupt another queued build. */
146
157
  export function disposeBuildSession(root){
147
- const task=buildQueue.then(async()=>{const {closeIncrementalCompiler}=await import('./incremental.mjs');await closeIncrementalCompiler(root);});
158
+ const task=buildQueue.then(async()=>{
159
+ const canonicalRoot=root===undefined?undefined:await realpath(root).catch(error=>{if(error.code==='ENOENT'||error.code==='ENOTDIR')return path.resolve(root);throw error;});
160
+ forgetLazyBuildContext(canonicalRoot);
161
+ const {closeIncrementalCompiler}=await import('./incremental.mjs');
162
+ await closeIncrementalCompiler(canonicalRoot);
163
+ });
148
164
  buildQueue=task.catch(()=>{});return task;
149
165
  }
150
166
  /** Isolate process.env between programmatic builds, including simultaneous calls. */
@@ -174,8 +190,13 @@ export function build(projectRoot, options = {}) {
174
190
  }
175
191
 
176
192
  /** Compile Pages Router and App Router projects. A failed build preserves .prnext. */
177
- async function buildProject(projectRoot, { dev = false, envMode, incremental = false, devInputs, devRoutes, devSequence, devSession, devTools,devForceRefresh=false,devAffectedModules } = {}) {
193
+ async function buildProject(projectRoot, { dev = false, envMode, incremental = false, devInputs, devRoutes, devSequence, devSession, devActionSecret, devTools,devForceRefresh=false,devAffectedModules,devLazyRequest,devLazyBase,devLazyActivated=[] } = {}) {
178
194
  const started = performance.now();
195
+ if (dev && devLazyRequest && devLazyBase) {
196
+ const deferred = await tryBuildLazyGraph(projectRoot,{base:devLazyBase,request:devLazyRequest,
197
+ devInputs,devSession,devActionSecret,incremental});
198
+ if (deferred) return deferred;
199
+ }
179
200
  const config = await loadProjectConfig(projectRoot, { dev, envMode, devInputs });
180
201
  const tsconfig = config.typescript ? await projectTsconfig(projectRoot, config) : undefined;
181
202
  const environment = defineEnvironment(config);
@@ -183,13 +204,20 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
183
204
  const customRoutes = await compileCustomRoutes(config);
184
205
  const buildId = await generateBuildId(config, { dev });
185
206
  const scanOptions={basePath:config.basePath,pageExtensions:config.pageExtensions,i18n:config.i18n};
186
- let project;
207
+ let project,deferredRoutes=[];
187
208
  if(dev && Array.isArray(devRoutes)) {
188
209
  const discovery=await scanProject(projectRoot,{...scanOptions,devAppFiles:new Set()});
189
210
  project=await scanProject(projectRoot,{...scanOptions,devAppFiles:routeFiles(discovery,devRoutes)});
190
211
  await writeTypedRoutes(projectRoot,discovery,config);
212
+ deferredRoutes=discovery.routes.filter(route=>!devRoutes.includes(route.pattern)&&!route.internal&&!route.errorStatus);
191
213
  project.routes=project.routes.filter(route=>devRoutes.includes(route.pattern)||route.internal||route.errorStatus);
192
214
  } else project=await scanProject(projectRoot,scanOptions);
215
+ if(dev&&config.experimental.lazyDynamicImports){
216
+ if(project.routes.some(route=>route.router!=='app'&&!route.internal))throw Error('experimental.lazyDynamicImports currently requires an App Router-only application.');
217
+ if(config.webpack||Object.keys(config.turbopack?.rules||{}).length)throw Error('experimental.lazyDynamicImports currently requires the native compiler without webpack hooks or custom loaders.');
218
+ if(/^https?:\/\//.test(config.assetPrefix))throw Error('experimental.lazyDynamicImports requires same-origin development assets.');
219
+ }
220
+ if(!dev&&config.htmlOnly.length)selectHtmlOnlyRoutes(project.routes,config.htmlOnly,config);
193
221
  if(!dev || !Array.isArray(devRoutes))await writeTypedRoutes(projectRoot,project,config);
194
222
  const stage = path.join(project.root, `.prnext-build-${randomUUID()}`);
195
223
  const destination = await prepareBuildDirectory(project.root, config.distDir);
@@ -199,25 +227,32 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
199
227
  await addBuiltinPagesErrors(project, stage);
200
228
  const manifest = { version: 1, dev, buildId, cacheId: randomUUID(), previewModeId: randomUUID().replaceAll('-', ''), previewModeEncryptionKey: randomBytes(32).toString('hex'),
201
229
  ...(dev && Array.isArray(devRoutes) ? {devOnDemand:true} : {}), ...(dev && devSession ? {devSequence,devSession} : {}), ...(dev ? {devForceRefresh,devAffectedModules,devIndicators:config.devIndicators,...(devTools?{devTools}:{})} : {}),
202
- config: { i18n: config.i18n, skipMiddlewareUrlNormalize: config.skipMiddlewareUrlNormalize, compress: config.compress, poweredByHeader: config.poweredByHeader, reactStrictMode: config.reactStrictMode, cacheComponents: config.cacheComponents, cacheLife: config.cacheLife, images: config.images, ...publicPaths }, customRoutes, routes: [], prerendered: [],
230
+ config: { i18n: config.i18n, skipMiddlewareUrlNormalize: config.skipMiddlewareUrlNormalize, compress: config.compress, poweredByHeader: config.poweredByHeader, reactStrictMode: config.reactStrictMode, cacheComponents: config.cacheComponents, partialPrefetching: config.partialPrefetching, navigationCache: config.navigationCache, cacheLife: config.cacheLife, htmlOnly:config.htmlOnly, images: config.images, ...publicPaths }, customRoutes, routes: [], prerendered: [],
203
231
  ...(project.pagesErrors ? { pagesErrors: project.pagesErrors } : {}) };
204
232
  // Its build-unique name is known before bundling and avoids a hash cycle
205
233
  // between page entry URLs and the manifest that lists those entries.
234
+ if(!dev)manifest.config.deploymentId=manifest.cacheId;
206
235
  const pagesManifestUrl = `${publicPaths.assetBase}/pages-manifest-${manifest.cacheId}.json`;
207
236
  if (dev) manifest.devClient = `${publicPaths.assetBase}/dev-manifest-${manifest.cacheId}.json`;
208
237
  const development = dev ? { buildId, clientManifest: manifest.devClient, onDemand:manifest.devOnDemand===true, sequence:devSequence, session:devSession, indicator:config.devIndicators,...(devTools?{tools:devTools}:{}) } : undefined;
209
238
  const refresh = dev ? createRefreshTransform(project.root, {generation:manifest.cacheId,forceGeneration:devForceRefresh,forceFiles:devAffectedModules}) : undefined;
210
239
  const locations = dev ? createSourceLocationTransform(project.root) : undefined;
240
+ const reactCompiler = config.reactCompiler ? createReactCompilerTransform(project.root, config.reactCompiler, {dev}) : undefined;
241
+ const serverOnlyFiles = new Set();
242
+ if (reactCompiler) for (const file of [...project.routes.filter(route=>route.kind==='api').map(route=>route.file),...(project.document?[project.document]:[])]) {
243
+ serverOnlyFiles.add(file);serverOnlyFiles.add(await realpath(file));
244
+ }
211
245
  const fonts = createFonts({ projectRoot: project.root, stage, assetBase: publicPaths.assetBase });
212
- const cssModules = createCssModules({ projectRoot: project.root, stage, assetBase: publicPaths.assetBase, sassOptions: config.sassOptions, cssEngine:config.cssEngine });
246
+ const cssModules = createCssModules({ projectRoot: project.root, stage, assetBase: publicPaths.assetBase, sassOptions: config.sassOptions, cssEngine:config.cssEngine,dev,config });
213
247
  configureCompiler({config,root:project.root,stage,buildId,dev,incremental,devInputs,tsconfig,assetBase:publicPaths.assetBase});
214
248
  try {
215
- await copyRuntimeFiles(stage);
249
+ await copyRuntimeFiles(stage,{development:dev});
216
250
  // Runtime copies must work even when dotenv is nested beneath the PRNext
217
251
  // package instead of hoisted beside the application's dependencies.
218
252
  await bundle({ entryPoints: [path.join(packageRoot, 'runtime/env.mjs')], outfile: path.join(stage, 'runtime/env.mjs'), bundle: true, platform: 'node', format: 'esm', target: 'node22', logLevel: 'silent', banner: { js: "import {createRequire as __prnextCreateRequire} from 'node:module';const require=__prnextCreateRequire(import.meta.url);" } });
219
253
  await prepareImageResponse(stage);
220
254
  manifest.config.serverActions = config.experimental.serverActions;
255
+ if(config.workerMemory && !dev)manifest.config.workerMemory=config.workerMemory;
221
256
  if (config.workerRetirement) manifest.config.workerRetirement = config.workerRetirement;
222
257
  if(config.workerPreload&&!dev)manifest.config.workerPreload=config.workerPreload;
223
258
  const cacheHandlers = await prepareCacheHandlers({ config, projectRoot: project.root, stage, environment });
@@ -236,7 +271,7 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
236
271
  serverEntries[route.id] = serverEntry;
237
272
  if (route.kind === 'page') {
238
273
  const clientEntry = path.join(stage, '.entries', route.id + '.client.jsx');
239
- await writeFile(clientEntry, browserEntry(route, project.app, pagesManifestUrl, { ...publicPaths, strictMode: config.reactStrictMode === true }, development));
274
+ await writeFile(clientEntry, browserEntry(route, project.app, pagesManifestUrl, { ...publicPaths, deploymentId:dev?undefined:manifest.cacheId, strictMode: config.reactStrictMode === true }, development));
240
275
  clientEntries[route.id] = clientEntry;
241
276
  }
242
277
  manifest.routes.push({ id: route.id, pattern: route.pattern, kind: route.kind, module: `server/${route.id}.mjs`, css: [],
@@ -245,7 +280,7 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
245
280
  }
246
281
  const common = { absWorkingDir: project.root, bundle: true, logLevel: 'silent', jsx: 'automatic', loader: assetLoaders, assetNames: '[name]-[hash]', publicPath: publicPaths.assetBase, metafile: true, define: { 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'), ...environment } };
247
282
  if (Object.keys(serverEntries).length) {
248
- const server = await bundle({ ...common, entryPoints: serverEntries, outdir: path.join(stage, 'server'), outExtension: { '.js': '.mjs' }, platform: 'node', format: 'esm', splitting: true, chunkNames: 'chunk-[hash]', banner: { js: "import {createRequire as __prnextCreateRequire} from 'node:module';const require=__prnextCreateRequire(import.meta.url);" }, target: 'node22', sourcemap: dev, plugins: [moduleResolutionPlugin(config.turbopack, project.root), images.plugin(), fonts.plugin(), compatibilityPlugin({ browser: false, pageFiles, projectRoot: project.root, documentFile: project.document, fonts, locations }), npmPackagesPlugin({ pages: true, projectRoot: project.root, transpilePackages: config.transpilePackages, serverExternalPackages: config.serverExternalPackages }), cssModules.plugin(false)] });
283
+ const server = await bundle({ ...common, entryPoints: serverEntries, outdir: path.join(stage, 'server'), outExtension: { '.js': '.mjs' }, platform: 'node', format: 'esm', splitting: true, chunkNames: 'chunk-[hash]', banner: { js: "import {createRequire as __prnextCreateRequire} from 'node:module';const require=__prnextCreateRequire(import.meta.url);" }, target: 'node22', sourcemap: dev, plugins: [moduleResolutionPlugin(config.turbopack, project.root), images.plugin(), fonts.plugin(), compatibilityPlugin({ browser: false, pageFiles, projectRoot: project.root, documentFile: project.document, serverOnlyFiles, fonts, locations, reactCompiler }), npmPackagesPlugin({ pages: true, projectRoot: project.root, transpilePackages: config.transpilePackages, serverExternalPackages: config.serverExternalPackages }), cssModules.plugin(false)] });
249
284
  fonts.record(server.metafile);
250
285
  const serverChunks = new Set(Object.keys(server.metafile.outputs).filter(file => file.endsWith('.mjs')).map(file => path.basename(file)));
251
286
  for (const [output, metadata] of Object.entries(server.metafile.outputs)) {
@@ -260,7 +295,7 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
260
295
  }
261
296
  }
262
297
  if (Object.keys(clientEntries).length) {
263
- const browser = await bundle({ ...common, entryPoints: clientEntries, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]', chunkNames: 'chunk-[hash]', platform: 'browser', format: 'esm', target: ['es2022'], splitting: true, minify: !dev, sourcemap: dev || config.productionBrowserSourceMaps, define: { 'process.env': '{}', ...common.define }, plugins: [moduleResolutionPlugin(config.turbopack, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), compatibilityPlugin({ browser: true, pageFiles, projectRoot: project.root, documentFile: project.document, fonts, refresh, locations }), cssModules.plugin(true)] });
298
+ const browser = await bundle({ ...common, entryPoints: clientEntries, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]', chunkNames: 'chunk-[hash]', platform: 'browser', format: 'esm', target: ['es2022'], splitting: true, minify: !dev, sourcemap: dev || config.productionBrowserSourceMaps, define: { 'process.env': '{}', ...common.define }, plugins: [moduleResolutionPlugin(config.turbopack, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), compatibilityPlugin({ browser: true, pageFiles, projectRoot: project.root, documentFile: project.document, serverOnlyFiles, fonts, refresh, locations, reactCompiler }), cssModules.plugin(true)] });
264
299
  fonts.record(browser.metafile);
265
300
  const byEntry = new Map(Object.entries(clientEntries).map(([id, file]) => [path.resolve(file), id]));
266
301
  for (const [output, metadata] of Object.entries(browser.metafile.outputs)) {
@@ -277,27 +312,46 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
277
312
  await compileMiddleware({ project, stage, manifest, dev, moduleResolution: config.turbopack, defineEnvironment: environment });
278
313
  if (manifest.middleware) manifest.middleware.bodySizeLimit = config.experimental.proxyClientMaxBodySize;
279
314
  await prepareMetadataRoutes(project, stage);
280
- await compileApp({ project, stage, manifest, moduleResolution: config.turbopack, cssModules, fonts, images, dev, defineEnvironment: environment, productionBrowserSourceMaps: config.productionBrowserSourceMaps, serverExternalPackages: config.serverExternalPackages });
315
+ await compileApp({ project, stage, manifest, reactCompiler: config.reactCompiler, moduleResolution: config.turbopack, cssModules, fonts, images, dev, devActionSecret:dev?devActionSecret:undefined, defineEnvironment: environment, productionBrowserSourceMaps: config.productionBrowserSourceMaps, serverExternalPackages: config.serverExternalPackages, lazyDynamicImports:dev&&config.experimental.lazyDynamicImports,devLazyRequest,devLazyActivated });
316
+ if(manifest.devLazy){
317
+ const css=Object.values(manifest.devLazy.outputs).flatMap(module=>module.css);
318
+ for(const route of manifest.routes)if(route.router==='app'&&route.kind==='page'){
319
+ route.css=[...new Set([...route.css,...css])];
320
+ if(css.length&&Array.isArray(route.cssScopes)){
321
+ const root=route.cssScopes.find(scope=>scope.kind==='layout'&&!scope.path);
322
+ if(root)root.css=[...new Set([...root.css,...css])];
323
+ else route.cssScopes.push({kind:'layout',path:'',css:[...new Set(css)]});
324
+ }
325
+ }
326
+ }
281
327
  if(manifest.config.workerPreload)for(const pattern of config.workerPreload.routes){
282
328
  const selected=manifest.routes.find(route=>!route.internal&&route.pattern===pattern);
283
329
  if(!selected||selected.cacheConfig?.runtime==='edge'||selected.handlerConfig?.runtime==='edge')throw new Error(`workerPreload route ${pattern} must be an existing Node route.`);
284
330
  }
331
+ await compileInstrumentation({root:project.root,stage,manifest,config,dev,environment});
332
+ if(deferredRoutes.length){
333
+ for(const route of deferredRoutes){
334
+ const module=`server/dev-deferred-${route.id}.mjs`;await writeFile(path.join(stage,module),'export {};\n');
335
+ manifest.routes.push({id:route.id,pattern:route.pattern,kind:route.kind,router:route.router,module,devDeferred:true,css:[],cacheConfig:{maxDuration:120}});
336
+ }
337
+ }
285
338
  await checkTypeScript(project.root, config, { dev, tsconfig });
286
339
  fonts.attach(manifest);
287
340
  expandLocales(manifest);
288
341
  // All runtime preparation must finish before its shared modules are built;
289
342
  // no staged renderer has been imported before this production-only point.
290
- if (!dev) await bundleRuntime(stage,{projectRoot:project.root});
343
+ if (!dev) await bundleRuntime(stage,{projectRoot:project.root,sourceDirectory:path.join(packageRoot,'runtime')});
291
344
  // Node caches CJS React by filename, not NODE_ENV. Once development metadata
292
345
  // imports load it, subsequent production renders need a clean process too.
293
346
  if(dev) developmentModulesLoaded = true;
294
- if(!dev && (incremental || developmentModulesLoaded)) Object.assign(manifest,await isolatedPrerender(project.root,stage,manifest));
347
+ if(!dev) Object.assign(manifest,await (incremental || developmentModulesLoaded ? isolatedPrerender : prerenderProduction)(project.root,stage,manifest,{prerender:config.prerender}));
295
348
  else {await prerender(stage, manifest, dev);await prerenderApp(stage, manifest, dev);}
296
- const pages = manifest.routes.filter(route => route.kind === 'page' && route.router !== 'app' && !route.internal);
349
+ const pages = manifest.routes.filter(route => route.kind === 'page' && route.router !== 'app' && !route.internal && !route.devDeferred);
297
350
  const publicPage = route => ({ ...(route.locale ? {locale:route.locale,originalPattern:route.originalPattern} : {}), id: route.id, pattern: route.pattern, client: route.client, css: route.css, ...(route.fonts ? { fonts: route.fonts } : {}), ssg: !!route.ssg, ssp: !!route.ssp, gip: !!route.gip, appGip: !!route.appGip });
298
351
  if (pages.length || manifest.pagesErrors) {
299
352
  const publicPages = {
300
353
  buildId: manifest.buildId,
354
+ ...(manifest.config.deploymentId?{deploymentId:manifest.config.deploymentId}:{}),
301
355
  ...publicPaths,
302
356
  // Header rules cannot select a different page. Other server routing
303
357
  // stays authoritative, including middleware with conditional behavior.
@@ -326,11 +380,18 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
326
380
  }
327
381
  const appRoute = manifest.routes.find(route => route.router === 'app' && route.kind === 'page' && route.client);
328
382
  await writeFile(path.join(stage, 'assets', path.basename(manifest.devClient)), JSON.stringify({ buildId,
383
+ ...(manifest.devLazy?{lazy:Object.values(manifest.devLazy.outputs)}:{}),
329
384
  pages: manifest.routes.filter(route => route.router !== 'app' && route.client).map(publicPage),
330
- ...(appRoute ? { app: { client: appRoute.client, css: appRoute.css } } : {}),
385
+ ...(appRoute ? { app: { client: appRoute.client, css: appRoute.css, routes: manifest.routes.filter(route=>route.router==='app'&&route.kind==='page'&&route.client).map(({id,pattern,css,cssScopes})=>({id,pattern,css,cssScopes:cssScopes||[]})) } } : {}),
331
386
  }));
332
387
  }
333
- await writeRuntimeContract(stage);
388
+ // A report belongs to this successful staging generation, never to the
389
+ // runtime manifest or public assets. Publishing the stage is atomic below.
390
+ if(!dev)await retainDeploymentAssets({stage,destination,manifest,policy:config.deploymentRetention});
391
+ const compilationCache=compilationGraphCacheReport();
392
+ if(!dev)await writeFile(path.join(stage,'compilation-cache-report.json'),JSON.stringify(compilationCache,null,2)+'\n');
393
+ const compilerReport=await writeReactCompilerReport(stage,reactCompilerReport());
394
+ await writeRuntimeContract(stage,{deployment:!dev,htmlOnly:!dev&&manifest.routes.some(route=>route.htmlOnly),workerMemory:Boolean(manifest.config.workerMemory),incrementalCache:Boolean(manifest.config.cacheHandler)});
334
395
  await writeFile(path.join(stage, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
335
396
  await writeBuildOutput(stage, manifest);
336
397
  // Offline compression is a deployment optimization. Recompressing source
@@ -360,7 +421,8 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
360
421
  } finally { await rm(pointerTemporary, {force:true}); }
361
422
  await exported?.commit();
362
423
  if (hadDestination) await rm(backup, { recursive: true, force: true });
363
- return { ...manifest, outputDirectory: destination, durationMs: Math.round(performance.now() - started) };
424
+ if(dev&&!devLazyRequest)rememberLazyBuildContext(project.root,{manifest,config,environment,tsconfig,envMode});
425
+ return { ...manifest, outputDirectory: destination, durationMs: Math.round(performance.now() - started),reactCompilerReport:compilerReport,compilationCache };
364
426
  } catch (error) {
365
427
  await rm(stage, { recursive: true, force: true });
366
428
  if (error.errors) error.message = error.errors.map(item => `${item.location ? `${item.location.file}:${item.location.line}: ` : ''}${item.text}`).join('\n');
@@ -0,0 +1,30 @@
1
+ import {access,writeFile,mkdir} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {build} from './compiler.mjs';
4
+ import {compileEdge} from './edge.mjs';
5
+ import {moduleResolutionPlugin} from './module-resolution.mjs';
6
+
7
+ export async function compileInstrumentation({root,stage,manifest,config,dev,environment}) {
8
+ const found=[];
9
+ for(const directory of ['', 'src'])for(const extension of ['ts','js','mjs','cjs']){
10
+ const file=path.join(root,directory,'instrumentation.'+extension);
11
+ try{await access(file);found.push(file);}catch(error){if(error.code!=='ENOENT')throw error;}
12
+ }
13
+ if(!found.length)return;
14
+ if(found.length>1)throw Error('Only one instrumentation file is allowed, in the project root or src directory.');
15
+ const file=found[0],entry=path.join(stage,'.entries/instrumentation.mjs');manifest.instrumentation={};
16
+ await mkdir(path.dirname(entry),{recursive:true});
17
+ await writeFile(entry,`import * as hooks from ${JSON.stringify(file)};export const register=hooks.register??hooks.default?.register;export const onRequestError=hooks.onRequestError??hooks.default?.onRequestError;`);
18
+ for(const runtime of ['node','rsc']){
19
+ const module=`server/instrumentation-${runtime}.mjs`;
20
+ await build({absWorkingDir:root,entryPoints:[entry],outfile:path.join(stage,module),bundle:true,packages:'external',platform:'node',format:'esm',target:'node22',banner:{js:"import {createRequire as __prnextCreateRequire} from 'node:module';const require=__prnextCreateRequire(import.meta.url);"},conditions:runtime==='rsc'?['react-server']:[],sourcemap:dev,logLevel:'silent',
21
+ define:{...environment,'process.env.NODE_ENV':JSON.stringify(dev?'development':'production'),'process.env.NEXT_RUNTIME':'"nodejs"'},
22
+ plugins:[moduleResolutionPlugin(config.turbopack,root)]});
23
+ manifest.instrumentation[runtime]=module;
24
+ }
25
+ if(manifest.middleware?.runtime==='edge'||manifest.routes.some(route=>route.cacheConfig?.runtime==='edge'||route.handlerConfig?.runtime==='edge')){
26
+ const module='server/instrumentation-edge.mjs';
27
+ await compileEdge({file:entry,outfile:path.join(stage,module),projectRoot:root,dev,defineEnvironment:environment});
28
+ manifest.instrumentation.edge=module;
29
+ }
30
+ }