@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.
- package/README.md +27 -868
- package/build/actions.mjs +27 -15
- package/build/analyze.mjs +180 -24
- package/build/app-config.mjs +13 -0
- package/build/app-static.mjs +34 -12
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +122 -46
- package/build/bundle-runtime.mjs +22 -9
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/compact-css.mjs +53 -0
- package/build/compilation-cache.mjs +269 -0
- package/build/compilation-store.mjs +142 -0
- package/build/compiler.mjs +34 -10
- package/build/config.mjs +34 -3
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +56 -12
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-css-assets.mjs +173 -0
- package/build/dev-css-update.mjs +18 -14
- package/build/dev-dependencies.mjs +1 -1
- package/build/dev-discovery.mjs +3 -3
- package/build/dev-lazy-build.mjs +77 -0
- package/build/dev.mjs +15 -8
- package/build/dynamic.mjs +14 -7
- package/build/edge.mjs +3 -3
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +15 -5
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +112 -18
- package/build/index.mjs +90 -28
- package/build/instrumentation.mjs +30 -0
- package/build/lazy-dynamic-imports.mjs +137 -0
- package/build/metadata-routes.mjs +1 -1
- package/build/migrate.mjs +4 -4
- package/build/migration-audit.mjs +19 -31
- package/build/migration-imports.mjs +245 -0
- package/build/migration-scenarios-runner.mjs +73 -0
- package/build/migration-scenarios.mjs +72 -0
- package/build/native-discovery.mjs +65 -5
- package/build/native-packages.mjs +2 -0
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +1 -0
- package/build/persistent-cache.mjs +18 -3
- package/build/precompress.mjs +1 -1
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +19 -9
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +5 -5
- package/build/production-builder.mjs +59 -10
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/standalone.mjs +21 -3
- package/build/static-export.mjs +4 -4
- package/build/styles.mjs +6 -4
- package/build/transform.mjs +1 -1
- package/build/typed-routes.mjs +4 -0
- package/build/typescript-dev.mjs +35 -8
- package/build/typescript-native-worker.mjs +55 -0
- package/build/typescript-native.mjs +18 -0
- package/build/typescript-toolchain.mjs +19 -0
- package/build/typescript.mjs +6 -4
- package/build/webpack-graph.mjs +19 -6
- package/build/worker-memory.mjs +9 -0
- package/cli.mjs +70 -5
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-dynamic.cjs +12 -1
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +15 -3
- package/compat/cache-wire.cjs +28 -0
- package/compat/cache.cjs +2 -1
- package/compat/cache.d.cts +4 -0
- package/compat/cookies.cjs +21 -5
- package/compat/data-cache.cjs +12 -8
- package/compat/flight-delta.cjs +152 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +69 -4
- package/compat/link.cjs +13 -10
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/offline-store.cjs +11 -0
- package/compat/offline.cjs +5 -0
- package/compat/offline.d.cts +2 -0
- package/compat/redis-cache.cjs +201 -0
- package/compat/redis-cache.d.cts +37 -0
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +248 -2
- package/docs/ai/data.md +165 -5
- package/docs/ai/deployment.md +158 -0
- package/docs/ai/quickstart.md +29 -4
- package/docs/ai/routing.md +93 -0
- package/docs/ai/troubleshooting.md +116 -2
- package/package.json +40 -9
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/app-client.mjs +316 -92
- package/runtime/app-content.mjs +4 -3
- package/runtime/app-partial-client.mjs +3 -0
- package/runtime/app-partial.mjs +1 -1
- package/runtime/app-prefetch-constants.mjs +4 -0
- package/runtime/app-prefetch.mjs +2 -3
- package/runtime/app-render-key.mjs +5 -0
- package/runtime/app-render.mjs +88 -28
- package/runtime/app-routing.mjs +22 -9
- package/runtime/app-static.mjs +18 -8
- package/runtime/app-stream-html.mjs +10 -53
- package/runtime/app-style-selection.mjs +34 -0
- package/runtime/app-styles.mjs +154 -0
- package/runtime/atomic-file.mjs +27 -0
- package/runtime/cache-components-rsc.mjs +7 -2
- package/runtime/cache-request.mjs +2 -1
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-client.mjs +13 -3
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-file-changes.mjs +123 -8
- package/runtime/dev-indicator.mjs +8 -3
- package/runtime/dev-lazy-imports.mjs +121 -0
- package/runtime/dev-lazy-staging.mjs +14 -0
- package/runtime/dev-lazy-tool.mjs +16 -0
- package/runtime/dev-style-retention.mjs +15 -0
- package/runtime/development.mjs +83 -25
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +74 -0
- package/runtime/flight-tap.mjs +28 -0
- package/runtime/fs.mjs +15 -8
- package/runtime/html-only.mjs +27 -0
- package/runtime/http.mjs +2 -0
- package/runtime/incremental-cache.mjs +25 -4
- package/runtime/instrumentation.mjs +49 -0
- package/runtime/lazy-import-client.mjs +38 -0
- package/runtime/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +14 -7
- package/runtime/pages-navigation.mjs +48 -63
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/profile.mjs +14 -0
- package/runtime/profiles.json +2 -2
- package/runtime/render.mjs +3 -0
- package/runtime/route-pattern.mjs +44 -0
- package/runtime/rsc-worker.mjs +34 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/watch-project.mjs +19 -15
- package/runtime/worker.mjs +70 -26
- package/vendor/nft/LICENSE +7 -0
- package/vendor/nft/README.md +16 -0
- package/vendor/nft/out/analyze.d.ts +8 -0
- package/vendor/nft/out/analyze.js +1216 -0
- package/vendor/nft/out/analyze.js.map +1 -0
- package/vendor/nft/out/cli.d.ts +2 -0
- package/vendor/nft/out/cli.js +94 -0
- package/vendor/nft/out/cli.js.map +1 -0
- package/vendor/nft/out/fs.d.ts +22 -0
- package/vendor/nft/out/fs.js +106 -0
- package/vendor/nft/out/fs.js.map +1 -0
- package/vendor/nft/out/index.d.ts +4 -0
- package/vendor/nft/out/index.js +25 -0
- package/vendor/nft/out/index.js.map +1 -0
- package/vendor/nft/out/node-file-trace.d.ts +41 -0
- package/vendor/nft/out/node-file-trace.js +380 -0
- package/vendor/nft/out/node-file-trace.js.map +1 -0
- package/vendor/nft/out/resolve-dependency.d.ts +6 -0
- package/vendor/nft/out/resolve-dependency.js +427 -0
- package/vendor/nft/out/resolve-dependency.js.map +1 -0
- package/vendor/nft/out/types.d.ts +66 -0
- package/vendor/nft/out/types.js +2 -0
- package/vendor/nft/out/types.js.map +1 -0
- package/vendor/nft/out/utils/ast-helpers.js +49 -0
- package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
- package/vendor/nft/out/utils/binary-locators.js +88 -0
- package/vendor/nft/out/utils/binary-locators.js.map +1 -0
- package/vendor/nft/out/utils/get-package-base.js +30 -0
- package/vendor/nft/out/utils/get-package-base.js.map +1 -0
- package/vendor/nft/out/utils/interop-require.js +24 -0
- package/vendor/nft/out/utils/interop-require.js.map +1 -0
- package/vendor/nft/out/utils/node-version.js +10 -0
- package/vendor/nft/out/utils/node-version.js.map +1 -0
- package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
- package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
- package/vendor/nft/out/utils/special-cases.js +347 -0
- package/vendor/nft/out/utils/special-cases.js.map +1 -0
- package/vendor/nft/out/utils/static-eval.js +579 -0
- package/vendor/nft/out/utils/static-eval.js.map +1 -0
- package/vendor/nft/out/utils/types.js +2 -0
- package/vendor/nft/out/utils/types.js.map +1 -0
- package/vendor/nft/out/utils/wrappers.js +659 -0
- package/vendor/nft/out/utils/wrappers.js.map +1 -0
- package/vendor/nft/package.json +4 -0
- package/vendor/nft/upstream.json +51 -0
package/build/incremental.mjs
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
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]?
|
|
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
|
-
|
|
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()=>{
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|