@thomas.f/prnext 0.1.18-alpha → 0.1.20-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 +2 -0
- package/build/actions.mjs +26 -14
- package/build/app.mjs +90 -101
- package/build/build-lock.mjs +2 -71
- package/build/build-retention.mjs +119 -0
- package/build/bundle-runtime.mjs +9 -8
- package/build/cache-command.mjs +31 -0
- package/build/check.mjs +41 -3
- package/build/compilation-cache.mjs +32 -12
- package/build/compilation-storage.mjs +190 -0
- package/build/compiler.mjs +8 -7
- package/build/config.mjs +25 -2
- package/build/create.mjs +1 -1
- package/build/css-modules.mjs +4 -0
- package/build/dev-action-graph.mjs +21 -0
- package/build/dev-css-assets.mjs +173 -0
- package/build/dev-css-update.mjs +14 -12
- package/build/dev-lazy-build.mjs +77 -0
- package/build/dynamic.mjs +13 -6
- package/build/font-cache.mjs +9 -6
- package/build/font-files.mjs +1 -1
- package/build/incremental.mjs +112 -18
- package/build/index.mjs +61 -15
- package/build/lazy-dynamic-imports.mjs +137 -0
- 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-ppr.mjs +32 -0
- package/build/npm-packages.mjs +1 -0
- package/build/og.mjs +121 -1
- package/build/parallel-plan.mjs +55 -0
- package/build/persistent-cache.mjs +29 -24
- package/build/production-builder.mjs +1 -1
- package/build/production-check.mjs +34 -0
- package/build/react-compiler-report.mjs +31 -8
- package/build/react-compiler.mjs +101 -10
- package/build/server-config.mjs +35 -0
- package/build/source-analysis.mjs +54 -4
- package/build/standalone.mjs +21 -3
- package/build/static-adapter.mjs +7 -3
- package/build/transform.mjs +38 -6
- package/build/typescript-dev.mjs +3 -1
- package/build/webpack-graph.mjs +13 -3
- package/cli.mjs +24 -6
- package/compat/app-dynamic.cjs +12 -1
- package/compat/app-layout-context.cjs +7 -3
- package/compat/cache-handlers.cjs +3 -1
- package/compat/flight-delta.cjs +50 -13
- package/compat/index.d.ts +24 -1
- package/compat/link.cjs +2 -2
- package/compat/navigation-authorization.cjs +8 -1
- package/docs/ai/compatibility.md +187 -7
- package/docs/ai/deployment.md +141 -0
- package/docs/ai/quickstart.md +5 -0
- package/docs/ai/routing.md +11 -0
- package/docs/ai/troubleshooting.md +79 -0
- package/package.json +26 -11
- package/release/fontkit.mjs +31 -0
- package/release/verify.mjs +3 -0
- package/runtime/api.mjs +5 -2
- package/runtime/app-client.mjs +254 -53
- package/runtime/app-partial.mjs +2 -10
- package/runtime/app-prefetch.mjs +2 -2
- package/runtime/app-render.mjs +11 -7
- package/runtime/app-stream-channel.mjs +3 -2
- package/runtime/app-styles.mjs +22 -4
- package/runtime/atomic-file.mjs +27 -0
- package/runtime/cache-components-rsc.mjs +3 -1
- package/runtime/compile-cache-retention.mjs +53 -0
- package/runtime/compile-cache.mjs +28 -1
- package/runtime/dev-client.mjs +13 -5
- package/runtime/dev-file-changes.mjs +123 -8
- package/runtime/dev-gateway.mjs +3 -2
- package/runtime/dev-lazy-imports.mjs +121 -0
- package/runtime/dev-lazy-staging.mjs +14 -0
- package/runtime/dev-route-selection.mjs +64 -0
- package/runtime/dev-runtime-pool.mjs +3 -2
- package/runtime/dev-runtime-spare.mjs +1 -1
- package/runtime/dev-session-retention.mjs +154 -0
- package/runtime/dev-style-retention.mjs +15 -0
- package/runtime/development.mjs +86 -26
- package/runtime/env.mjs +9 -0
- package/runtime/flight-delta.mjs +59 -9
- package/runtime/fs.mjs +15 -8
- package/runtime/lazy-import-client.mjs +38 -0
- package/runtime/native-ppr.mjs +35 -0
- package/runtime/og/NOTICE.txt +1 -1
- package/runtime/og/index.node.mjs +76 -31216
- package/runtime/og/upstream.json +115 -0
- package/runtime/persistent/client.mjs +4 -2
- package/runtime/persistent/daemon.mjs +3 -11
- package/runtime/persistent/health.mjs +58 -0
- package/runtime/project-lock.mjs +71 -0
- package/runtime/request-transport.mjs +112 -32
- package/runtime/storage-path.mjs +36 -0
- package/runtime/trace.mjs +44 -4
- package/runtime/worker.mjs +9 -4
- package/vendor/fontkit/NOTICE.txt +5 -0
- package/vendor/fontkit/SOURCE-NOTICES.txt +209 -0
- package/vendor/fontkit/index.mjs +3 -0
- package/vendor/fontkit/licenses/@swc__helpers/LICENSE +201 -0
- package/vendor/fontkit/licenses/base64-js/LICENSE +21 -0
- package/vendor/fontkit/licenses/brotli/readme.md +61 -0
- package/vendor/fontkit/licenses/clone/LICENSE +18 -0
- package/vendor/fontkit/licenses/dfa/README.md +83 -0
- package/vendor/fontkit/licenses/fast-deep-equal/LICENSE +21 -0
- package/vendor/fontkit/licenses/fontkit/README.md +266 -0
- package/vendor/fontkit/licenses/restructure/LICENSE +21 -0
- package/vendor/fontkit/licenses/tiny-inflate/LICENSE +21 -0
- package/vendor/fontkit/licenses/tslib/LICENSE.txt +12 -0
- package/vendor/fontkit/licenses/unicode-properties/LICENSE +8 -0
- package/vendor/fontkit/licenses/unicode-trie/LICENSE +8 -0
- package/vendor/fontkit/upstream.json +215 -0
- 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/README.md
CHANGED
|
@@ -93,6 +93,8 @@ npm run dev -- examples/app
|
|
|
93
93
|
|
|
94
94
|
Source builds require Rust 1.98 and native build tools: Visual Studio C++ tools and a Windows SDK on Windows, Xcode command line tools on macOS, or a C/C++ toolchain and pkg-config on Linux. Install NASM on macOS, Linux and Windows x64.
|
|
95
95
|
|
|
96
|
+
For a larger integration example, see the [SaaS reference](https://github.com/Lorkydey/PRNext/tree/main/examples/saas-reference): Better Auth, PostgreSQL/Drizzle, French/English routing and a portable standalone build. It has its own dependency lockfile and database setup.
|
|
97
|
+
|
|
96
98
|
</details>
|
|
97
99
|
|
|
98
100
|
## Documentation
|
package/build/actions.mjs
CHANGED
|
@@ -9,28 +9,39 @@ import path from 'node:path';
|
|
|
9
9
|
|
|
10
10
|
const traverse = traverseModule.default || traverseModule;
|
|
11
11
|
const generate = generateModule.default || generateModule;
|
|
12
|
+
// Kept inside the compiler process. The development session is public metadata
|
|
13
|
+
// and must never be usable by itself to derive an action encryption key.
|
|
14
|
+
const processDevelopmentSecret = randomBytes(32);
|
|
12
15
|
function hasDirective(node,value) { return node?.directives?.some(item=>item.value.value===value); }
|
|
13
16
|
function removeDirective(node,value) { if(node?.directives) node.directives=node.directives.filter(item=>item.value.value!==value); }
|
|
14
17
|
|
|
15
|
-
export function createServerActions({ projectRoot }) {
|
|
16
|
-
|
|
18
|
+
export function createServerActions({ projectRoot, developmentSession, developmentSecret }) {
|
|
19
|
+
if (developmentSession !== undefined && (typeof developmentSession !== 'string' || !developmentSession || developmentSession.length > 1024)) throw new TypeError('A development action session must be a non-empty string of at most 1024 characters');
|
|
20
|
+
if (developmentSecret !== undefined && (typeof developmentSecret !== 'string' || !/^[A-Za-z0-9+/]{43}=$/.test(developmentSecret))) throw new TypeError('A development action secret must encode 32 random bytes');
|
|
21
|
+
const privateSecret = developmentSecret ? Buffer.from(developmentSecret,'base64') : processDevelopmentSecret;
|
|
22
|
+
const derive = purpose => createHmac('sha256', privateSecret).update(JSON.stringify([purpose, path.resolve(projectRoot), developmentSession])).digest();
|
|
23
|
+
const salt = developmentSession ? derive('action-identities') : randomBytes(32);
|
|
17
24
|
const modules = new Map();
|
|
18
25
|
const actions = new Map();
|
|
19
26
|
const configured = process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY;
|
|
20
27
|
if (configured && (!/^[A-Za-z0-9+/]+={0,2}$/.test(configured) || ![16,24,32].includes(Buffer.from(configured,'base64').length))) throw new Error('NEXT_SERVER_ACTIONS_ENCRYPTION_KEY must be a base64-encoded 16, 24, or 32 byte AES key.');
|
|
21
|
-
const actionKey = configured ? Buffer.from(configured,'base64').toString('base64') : randomBytes(32).toString('base64');
|
|
22
|
-
function identity(file,name) { return createHmac('sha256',salt).update(path.relative(projectRoot,file).replaceAll(path.sep,'/')+'\0'+name).digest('hex'); }
|
|
23
|
-
function register(file,name,binding) {
|
|
24
|
-
const id=identity(file,name);
|
|
28
|
+
const actionKey = configured ? Buffer.from(configured,'base64').toString('base64') : (developmentSession ? derive('action-encryption') : randomBytes(32)).toString('base64');
|
|
29
|
+
function identity(file,name,revision) { return createHmac('sha256',salt).update(path.relative(projectRoot,file).replaceAll(path.sep,'/')+'\0'+name+(revision?'\0'+revision:'')).digest('hex'); }
|
|
30
|
+
function register(file,name,binding,revision) {
|
|
31
|
+
const id=identity(file,name,revision);
|
|
25
32
|
if(!actions.has(id)) actions.set(id,{id,file,binding});
|
|
26
33
|
return id;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
function analyze(source,file) {
|
|
30
|
-
const
|
|
36
|
+
function analyze(source,file,{revisionSource=source}={}) {
|
|
37
|
+
const sourceHash=createHash('sha256').update(source).digest('hex');
|
|
38
|
+
// Cache transforms embed a generation ID. IDs for an unchanged action must
|
|
39
|
+
// follow its original source revision, not those ephemeral emitted bytes.
|
|
40
|
+
const revision=developmentSession?createHash('sha256').update(revisionSource).digest('hex'):sourceHash;
|
|
41
|
+
const key=file+'\0'+sourceHash+(developmentSession?'\0'+revision:'');
|
|
31
42
|
if(modules.has(key))return modules.get(key);
|
|
32
43
|
const template=JSON.parse(cachedTransform('server-action-analysis',source,[projectRoot,file],()=>JSON.stringify(analyzeTemplate(source,file))));
|
|
33
|
-
const ids=new Map(template.registrations.map(item=>[item.id,register(file,item.name,item.binding)]));
|
|
44
|
+
const ids=new Map(template.registrations.map(item=>[item.id,register(file,item.name,item.binding,developmentSession?revision:undefined)]));
|
|
34
45
|
let server=template.server;
|
|
35
46
|
for(const [previous,current]of ids)server=server.replaceAll(previous,current);
|
|
36
47
|
const result={...template,server,declared:template.declared.map(item=>({...item,id:ids.get(item.id)}))};
|
|
@@ -203,13 +214,14 @@ export function createServerActions({ projectRoot }) {
|
|
|
203
214
|
return {
|
|
204
215
|
actions, actionKey,
|
|
205
216
|
// Only compiler-generated module-level action proxies bypass React Refresh.
|
|
206
|
-
// Their
|
|
207
|
-
|
|
208
|
-
|
|
217
|
+
// Their opaque IDs are not component exports. Development keeps unchanged
|
|
218
|
+
// modules valid within one session; production IDs still change each build.
|
|
219
|
+
isServerModule(source,file,options) {
|
|
220
|
+
return source.includes('use server') && analyze(source,file,options).moduleServer === true;
|
|
209
221
|
},
|
|
210
|
-
transform(source,file,mode) {
|
|
222
|
+
transform(source,file,mode,options) {
|
|
211
223
|
if(!source.includes('use server')) return source;
|
|
212
|
-
const result=analyze(source,file);
|
|
224
|
+
const result=analyze(source,file,options);
|
|
213
225
|
// A Route Handler invokes the implementation as ordinary server code.
|
|
214
226
|
// It does not serialize functions through Flight or manufacture proxies.
|
|
215
227
|
// Analyze first to preserve validation and the shared action registry.
|
package/build/app.mjs
CHANGED
|
@@ -6,9 +6,9 @@ import path from 'node:path';
|
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { createRequire, builtinModules } from 'node:module';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { parse } from '@babel/parser';
|
|
10
9
|
import * as t from '@babel/types';
|
|
11
|
-
import {
|
|
10
|
+
import {analyzeSource} from './source-analysis.mjs';
|
|
11
|
+
import { rewriteServerChunkOutputFiles } from './transform.mjs';
|
|
12
12
|
import { transformDynamicImports } from './dynamic.mjs';
|
|
13
13
|
import { createServerActions } from './actions.mjs';
|
|
14
14
|
import { expandActionExports } from './action-exports.mjs';
|
|
@@ -21,6 +21,9 @@ import {createReactCompilerTransform} from './react-compiler.mjs';
|
|
|
21
21
|
import {createAppStyleGraph, appStyleScopes, planAppStyleEntries} from './app-styles.mjs';
|
|
22
22
|
import {selectHtmlOnlyRoutes} from './html-only-config.mjs';
|
|
23
23
|
import {createHtmlOnlyGraph} from './html-only-graph.mjs';
|
|
24
|
+
import {createLazyDynamicImports} from './lazy-dynamic-imports.mjs';
|
|
25
|
+
import {developmentActionMetadata} from './dev-action-graph.mjs';
|
|
26
|
+
import {immutableStylePlan, executeStylePlan, createStyleOutputWriter} from './parallel-plan.mjs';
|
|
24
27
|
|
|
25
28
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
29
|
const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx', '.mts': 'ts', '.cts': 'ts' };
|
|
@@ -42,77 +45,28 @@ export async function validateReactVersions(resolveFromProject) {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
function sourceAst(source, filename) {
|
|
46
|
-
return parse(source, { sourceType: 'unambiguous', sourceFilename: filename, plugins: ['jsx', ...(/\.[cm]?tsx?$/.test(filename) ? ['typescript'] : [])] });
|
|
47
|
-
}
|
|
48
48
|
function clientDirective(source, filename) {
|
|
49
49
|
if (!source.includes('use client')) return false;
|
|
50
|
-
return
|
|
50
|
+
return analyzeSource(source,filename).directives.includes('use client');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function frameworkDirective(source, filename) {
|
|
54
54
|
if (clientDirective(source, filename)) return true;
|
|
55
55
|
if (!source.includes('use server')) return false;
|
|
56
|
-
|
|
57
|
-
if (!node || typeof node !== 'object') return false;
|
|
58
|
-
if (t.isDirective(node) && node.value.value === 'use server') return true;
|
|
59
|
-
return (t.VISITOR_KEYS[node.type] || []).some(key => Array.isArray(node[key]) ? node[key].some(contains) : contains(node[key]));
|
|
60
|
-
}
|
|
61
|
-
return contains(sourceAst(source, filename));
|
|
56
|
+
return analyzeSource(source,filename).hasServerDirective;
|
|
62
57
|
}
|
|
63
58
|
|
|
64
59
|
/** Enumerate runtime ESM exports without evaluating client code in the server build. */
|
|
65
|
-
async function exportedNames(file, esbuild, seen = new Set()) {
|
|
60
|
+
export async function exportedNames(file, esbuild, seen = new Set()) {
|
|
66
61
|
if (seen.has(file)) return new Set();
|
|
67
62
|
seen.add(file);
|
|
68
63
|
const source = await compilerSource(file);
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (node.exportKind === 'type') continue;
|
|
74
|
-
if (t.isExportNamedDeclaration(node)) {
|
|
75
|
-
if (node.declaration && !t.isTSInterfaceDeclaration(node.declaration) && !t.isTSTypeAliasDeclaration(node.declaration) && !node.declaration.declare) {
|
|
76
|
-
for (const name of Object.keys(t.getOuterBindingIdentifiers(node.declaration))) names.add(name);
|
|
77
|
-
}
|
|
78
|
-
for (const specifier of node.specifiers) if (specifier.exportKind !== 'type') names.add(specifier.exported.name ?? specifier.exported.value);
|
|
79
|
-
}
|
|
80
|
-
if (t.isExportAllDeclaration(node)) {
|
|
81
|
-
const resolved = await esbuild.resolve(node.source.value, { resolveDir: path.dirname(file), kind: 'import-statement', pluginData: { prnextExportScan: true } });
|
|
82
|
-
if (resolved.errors.length || !resolved.path || resolved.external) throw new Error(`Cannot enumerate client boundary exports from ${node.source.value} in ${file}. Use explicit named re-exports.`);
|
|
64
|
+
const analysis=analyzeSource(source,file),names=new Set(analysis.runtimeExports);
|
|
65
|
+
for (const specifier of analysis.exportStars) {
|
|
66
|
+
const resolved = await esbuild.resolve(specifier, { resolveDir: path.dirname(file), kind: 'import-statement', pluginData: { prnextExportScan: true } });
|
|
67
|
+
if (resolved.errors.length || !resolved.path || resolved.external) throw new Error(`Cannot enumerate client boundary exports from ${specifier} in ${file}. Use explicit named re-exports.`);
|
|
83
68
|
for (const name of await exportedNames(resolved.path, esbuild, seen)) if (name !== 'default') names.add(name);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// CommonJS is supported for npm and PRNext compatibility components. The default
|
|
87
|
-
// is module.exports; statically assigned properties are named exports as in Node.
|
|
88
|
-
function visit(node) {
|
|
89
|
-
if (!node || typeof node !== 'object') return;
|
|
90
|
-
if (t.isCallExpression(node) && t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object, { name: 'Object' }) && t.isIdentifier(node.callee.property, { name: 'defineProperty' }) && t.isIdentifier(node.arguments[0], { name: 'exports' }) && t.isStringLiteral(node.arguments[1]) && node.arguments[1].value !== '__esModule') {
|
|
91
|
-
names.add('default');
|
|
92
|
-
names.add(node.arguments[1].value);
|
|
93
|
-
}
|
|
94
|
-
if (t.isAssignmentExpression(node) && t.isMemberExpression(node.left)) {
|
|
95
|
-
const left = node.left;
|
|
96
|
-
const property = left.computed ? left.property.value : left.property.name;
|
|
97
|
-
if (t.isIdentifier(left.object, { name: 'module' }) && property === 'exports') {
|
|
98
|
-
names.add('default');
|
|
99
|
-
if (t.isObjectExpression(node.right)) for (const item of node.right.properties) {
|
|
100
|
-
const key = item.computed ? item.key?.value : item.key?.name ?? item.key?.value;
|
|
101
|
-
if (typeof key === 'string') names.add(key);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (t.isIdentifier(left.object, { name: 'exports' }) || (t.isMemberExpression(left.object) && t.isIdentifier(left.object.object, { name: 'module' }) && left.object.property.name === 'exports')) {
|
|
105
|
-
names.add('default');
|
|
106
|
-
if (typeof property === 'string') names.add(property);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
for (const key of t.VISITOR_KEYS[node.type] || []) {
|
|
110
|
-
const child = node[key];
|
|
111
|
-
if (Array.isArray(child)) child.forEach(visit); else visit(child);
|
|
112
|
-
}
|
|
113
69
|
}
|
|
114
|
-
visit(ast.program);
|
|
115
|
-
names.delete('__esModule');
|
|
116
70
|
return names;
|
|
117
71
|
}
|
|
118
72
|
|
|
@@ -177,16 +131,18 @@ function routeEntry(route, globalError, dev) {
|
|
|
177
131
|
}
|
|
178
132
|
|
|
179
133
|
/** Compile independent React Server and client graphs. The RSC graph contains only references to client boundaries. */
|
|
180
|
-
export async function compileApp({ project, stage, manifest, cssModules, fonts, images, dev, reactCompiler = false, defineEnvironment = {}, productionBrowserSourceMaps = false, serverExternalPackages = [], moduleResolution = {} }) {
|
|
134
|
+
export async function compileApp({ project, stage, manifest, cssModules, fonts, images, dev, devActionSecret, reactCompiler = false, defineEnvironment = {}, productionBrowserSourceMaps = false, serverExternalPackages = [], moduleResolution = {},lazyDynamicImports=false,devLazyRequest,devLazyOnly=false,devLazyActivated=[], parallelBuilds=false }) {
|
|
135
|
+
if(devLazyOnly&&(!dev||!lazyDynamicImports||!devLazyRequest))throw Error('Deferred-only compilation requires a registered development import');
|
|
181
136
|
const refresh = dev ? createRefreshTransform(project.root, {generation:manifest.cacheId,forceGeneration:manifest.devForceRefresh,forceFiles:manifest.devAffectedModules}) : undefined;
|
|
182
137
|
const locations = dev ? createSourceLocationTransform(project.root) : undefined;
|
|
183
138
|
const compileReact = reactCompiler ? createReactCompilerTransform(project.root, reactCompiler, {dev}) : undefined;
|
|
184
139
|
const { basePath = '', assetPrefix = '', assetBase = '/_prnext/assets', trailingSlash = false, skipTrailingSlashRedirect = false } = manifest.config || {};
|
|
185
140
|
const publicPath = assetBase + '/';
|
|
186
|
-
const
|
|
141
|
+
const lazy=dev&&lazyDynamicImports?createLazyDynamicImports({root:project.root,stage,manifest,request:devLazyRequest,activated:devLazyActivated}):undefined;
|
|
142
|
+
const routes = devLazyOnly?[]:project.routes.filter(route => route.router === 'app');
|
|
187
143
|
const htmlOnly = dev ? new Set() : selectHtmlOnlyRoutes(project.routes, manifest.config.htmlOnly, manifest.config);
|
|
188
144
|
const htmlGraph = !dev && htmlOnly.size ? createHtmlOnlyGraph(project.root) : undefined;
|
|
189
|
-
if (project.appNotFound) {
|
|
145
|
+
if (project.appNotFound&&!devLazyOnly) {
|
|
190
146
|
const file = path.join(stage, '.entries', 'app-not-found.jsx');
|
|
191
147
|
await writeFile(file, `import {notFound} from 'next/navigation';export default function MissingRoute(){notFound()}`);
|
|
192
148
|
const segments = project.appNotFound.segments.map(segment => ({ ...segment }));
|
|
@@ -199,12 +155,12 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
199
155
|
routes.push({ ...project.appNotFound, segments, id, file, pattern: '/_not-found', kind: 'page', router: 'app', internal: true });
|
|
200
156
|
manifest.appNotFound = id;
|
|
201
157
|
}
|
|
202
|
-
if (!routes.length) return;
|
|
158
|
+
if (!routes.length&&!devLazyOnly) return;
|
|
203
159
|
if (!manifest.config.cacheComponents && routes.some(route => route.cacheConfig?.instant !== undefined || route.pageConfig?.instant !== undefined || route.segments?.some(segment => segment.staticConfig?.instant !== undefined))) throw new Error('instant requires cacheComponents: true.');
|
|
204
160
|
if (!manifest.config.cacheComponents && routes.some(route => route.cacheConfig?.ensureStatic !== undefined || route.pageConfig?.ensureStatic !== undefined || route.segments?.some(segment => segment.staticConfig?.ensureStatic !== undefined))) throw new Error('ensureStatic requires cacheComponents: true.');
|
|
205
|
-
const actions = createServerActions({ projectRoot: project.root });
|
|
206
|
-
function actionTransform(source, file, mode, runtime = 'nodejs') {
|
|
207
|
-
const result = actions.transform(source, file, mode);
|
|
161
|
+
const actions = createServerActions({ projectRoot: project.root,developmentSession:dev?manifest.devSession:undefined,developmentSecret:dev?devActionSecret:undefined });
|
|
162
|
+
function actionTransform(source, file, mode, runtime = 'nodejs', revisionSource = source) {
|
|
163
|
+
const result = actions.transform(source, file, mode, {revisionSource});
|
|
208
164
|
if (mode !== 'browser') for (const action of actions.actions.values()) if (action.file === file) {
|
|
209
165
|
(action.runtimes ||= new Set()).add(runtime);
|
|
210
166
|
}
|
|
@@ -226,6 +182,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
226
182
|
for (const segment of route.segments || []) if (segment.layout) cacheSegments.set(await canonicalFile(segment.layout), 'layout');
|
|
227
183
|
}
|
|
228
184
|
let browserStyleRevision;
|
|
185
|
+
const writeParallelStyles = parallelBuilds ? createStyleOutputWriter(path.join(stage, 'assets')) : undefined;
|
|
229
186
|
const resolveFromProject = createRequire(path.join(project.root, 'package.json'));
|
|
230
187
|
await validateReactVersions(resolveFromProject);
|
|
231
188
|
const nativePackages = createNativePackages(project.root, { isClientSource: frameworkDirective });
|
|
@@ -314,14 +271,17 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
314
271
|
htmlGraph?.source(args.path, original);
|
|
315
272
|
// Only Client Component graphs use React's memo-cache runtime. Server
|
|
316
273
|
// Components, route handlers and Server Action graphs remain untouched.
|
|
317
|
-
const
|
|
318
|
-
const
|
|
274
|
+
const compile = compileReact && (browser || mode==='ssr');
|
|
275
|
+
const locateFirst = !!(compile && reactCompiler.backend==='oxc' && locations);
|
|
276
|
+
const input = locateFirst ? await locations(original,args.path) : original;
|
|
277
|
+
const compiled = compile ? await compileReact(input,args.path,{loaderOutput:args.prnextSource!==undefined}) : input;
|
|
278
|
+
const source = await fonts.transform(await expandActionExports(locations && !locateFirst ? await locations(compiled,args.path) : compiled, args.path, async (specifier, importer) => {
|
|
319
279
|
const resolved = await esbuild.resolve(specifier, { resolveDir: path.dirname(importer), kind: 'import-statement', pluginData: { prnextExportScan: true } });
|
|
320
280
|
return resolved.errors.length || resolved.external || resolved.namespace !== 'file' ? undefined : resolved.path;
|
|
321
281
|
}), args.path);
|
|
322
282
|
const cacheSegment = cacheSegments.get(await canonicalFile(args.path)) || '';
|
|
323
283
|
const cachedSource = transformCacheComponents(source, args.path, { enabled: manifest.config.cacheComponents, handlers: manifest.config.cacheHandlers, projectRoot: project.root, mode, buildId: manifest.cacheId, segment: cacheSegment });
|
|
324
|
-
const actionSource = actionTransform(cachedSource, args.path, mode);
|
|
284
|
+
const actionSource = actionTransform(cachedSource, args.path, mode, 'nodejs', original);
|
|
325
285
|
const builtin = path.dirname(args.path) === path.join(packageRoot, 'compat') && forcedClient.has(path.basename(args.path));
|
|
326
286
|
if (rsc && (builtin || clientDirective(source, args.path))) {
|
|
327
287
|
htmlGraph?.issue(args.path, 'a Client Component or interactive framework component');
|
|
@@ -334,8 +294,9 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
334
294
|
const references = boundary.exports.map((name, index) => builtin && path.basename(args.path) === 'image.cjs' && name === 'getImageProps' ? `export {getImageProps} from ${JSON.stringify(path.join(packageRoot, 'compat/image-shared.cjs'))};` : `const ref${index}=registerClientReference(function(){throw new Error(${JSON.stringify(`Cannot call client export ${name} from a Server Component (${path.relative(project.root, args.path)}).`)});},${JSON.stringify(boundary.id)},${JSON.stringify(name)});export {ref${index} as ${JSON.stringify(name)}};`);
|
|
335
295
|
return { contents: `import {registerClientReference} from 'react-server-dom-webpack/server';\n${references.join('\n')}`, loader: 'js', resolveDir: path.dirname(args.path) };
|
|
336
296
|
}
|
|
337
|
-
let contents = transformDynamicImports(actionSource, args.path, { projectRoot: project.root, mode: rsc ? 'rsc' : browser ? 'browser' : 'server' });
|
|
338
|
-
if
|
|
297
|
+
let contents = transformDynamicImports(actionSource, args.path, { projectRoot: project.root, mode: rsc ? 'rsc' : browser ? 'browser' : 'server',dev });
|
|
298
|
+
if(browser&&lazy)contents=lazy.transform(contents,args.path);
|
|
299
|
+
if (browser && refresh && !actions.isServerModule(cachedSource,args.path,{revisionSource:original})) contents = await refresh(contents, args.path);
|
|
339
300
|
return { contents, loader: extensions[path.extname(args.path)] || 'jsx', resolveDir: path.dirname(args.path) };
|
|
340
301
|
});
|
|
341
302
|
},
|
|
@@ -397,11 +358,14 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
397
358
|
const result = await compileEdge({ file, outfile, projectRoot: project.root, dev, defineEnvironment, reactMode: mode, defaultExport, transformTypedHelpers:!!compileReact && mode==='ssr',
|
|
398
359
|
plugins: [moduleResolutionPlugin(moduleResolution, project.root), edgeGraphPlugin(mode), images.plugin(), fonts.plugin(), cssModules.plugin(false)],
|
|
399
360
|
transformSource: async (source, file, builder, {loaderOutput=false}={}) => {
|
|
400
|
-
const
|
|
401
|
-
|
|
361
|
+
const compile = compileReact && mode==='ssr';
|
|
362
|
+
const locateFirst = !!(compile && reactCompiler.backend==='oxc' && locations);
|
|
363
|
+
const input = locateFirst ? await locations(source,file) : source;
|
|
364
|
+
const compiled = compile ? await compileReact(input,file,{loaderOutput}) : input;
|
|
365
|
+
return actionTransform(await fonts.transform(await expandActionExports(locations && !locateFirst ? await locations(compiled,file) : compiled, file, async (specifier, importer) => {
|
|
402
366
|
const resolved = await builder.resolve(specifier, {resolveDir:path.dirname(importer),kind:'import-statement',pluginData:{prnextExportScan:true}});
|
|
403
367
|
return resolved.errors.length || resolved.external || resolved.namespace !== 'file' ? undefined : resolved.path;
|
|
404
|
-
}), file), file, mode, 'edge');
|
|
368
|
+
}), file), file, mode, 'edge', source);
|
|
405
369
|
} });
|
|
406
370
|
fonts.record(result.metafile);
|
|
407
371
|
styleGraph.record(result.metafile);
|
|
@@ -435,35 +399,39 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
435
399
|
fonts.record(output.metafile);
|
|
436
400
|
styleGraph.record(output.metafile);
|
|
437
401
|
htmlGraph?.record(output.metafile);
|
|
402
|
+
const chunks = new Set(Object.keys(output.metafile.outputs).filter(file => file.endsWith('.mjs')).map(file => path.basename(file)));
|
|
403
|
+
const importers = new Set(Object.entries(output.metafile.outputs).filter(([file,metadata])=>file.endsWith('.mjs')&&metadata.imports.some(item=>!item.external)).map(([file])=>path.resolve(project.root,file)));
|
|
404
|
+
output={...output,outputFiles:rewriteServerChunkOutputFiles(output.outputFiles,chunks,assetBase,importers)};
|
|
438
405
|
for (const file of output.outputFiles) {
|
|
439
406
|
await mkdir(path.dirname(file.path), { recursive: true });
|
|
440
407
|
await writeFile(file.path, file.contents);
|
|
441
408
|
}
|
|
442
|
-
const chunks = new Set(Object.keys(output.metafile.outputs).filter(file => file.endsWith('.mjs')).map(file => path.basename(file)));
|
|
443
409
|
for (const [file, metadata] of Object.entries(output.metafile.outputs)) {
|
|
444
410
|
const absolute = path.resolve(project.root, file);
|
|
445
|
-
if (file.endsWith('.mjs') && metadata.imports.some(item => !item.external)) await rewriteServerChunks(absolute, chunks, assetBase);
|
|
446
411
|
if (!metadata.entryPoint && !file.endsWith('.map') && !file.endsWith('.mjs')) await cp(absolute, path.join(stage, 'assets', path.basename(file)));
|
|
447
412
|
}
|
|
448
413
|
return output;
|
|
449
414
|
}
|
|
450
415
|
|
|
451
|
-
async function collectBrowserStyles(browserEntries) {
|
|
416
|
+
async function collectBrowserStyles(browserEntries,force=false) {
|
|
452
417
|
const revision = `${boundaries.size}:${actions.actions.size}:${nativePackages.version}`;
|
|
453
|
-
if (browserStyleRevision === revision) return;
|
|
418
|
+
if (!force&&browserStyleRevision === revision) return;
|
|
454
419
|
// SSR intentionally erases ssr:false loaders. Discover their dependencies
|
|
455
420
|
// in one browser graph, then classify styles by reachability from each root.
|
|
456
421
|
const discovery = await bundle({ ...common, write: false, entryPoints: browserEntries, outdir: path.join(stage, '.browser-styles'),
|
|
457
422
|
platform: 'browser', format: 'esm', target: ['es2022'], splitting: true,
|
|
458
423
|
define: { 'process.env': '{}', ...common.define },
|
|
459
|
-
plugins: [moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), nativePackages.plugin('browser'), cssModules.plugin(true)] });
|
|
424
|
+
plugins: [...(lazy?[lazy.plugin()]:[]),moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), nativePackages.plugin('browser'), cssModules.plugin(true)] });
|
|
425
|
+
await lazy?.discover(discovery.metafile);
|
|
460
426
|
fonts.record(discovery.metafile);
|
|
461
427
|
styleGraph.record(discovery.metafile);
|
|
462
|
-
const entryIds = new Map(await Promise.all(Object.entries(browserEntries).map(async ([id, file]) => [await canonicalFile(file), id])));
|
|
428
|
+
const entryIds = new Map((await Promise.all(Object.entries(browserEntries).map(async ([id, file]) => [[path.resolve(file), id], [await canonicalFile(file), id]]))).flat());
|
|
463
429
|
const globalRoots = [], normalRoots = [];
|
|
464
430
|
for (const output of Object.values(discovery.metafile.outputs)) {
|
|
465
431
|
if (!output.entryPoint) continue;
|
|
466
|
-
|
|
432
|
+
// Dynamic entry points can contain a query/fragment identity rather
|
|
433
|
+
// than a physical filename. Only configured roots need classification.
|
|
434
|
+
const id = entryIds.get(path.resolve(project.root, output.entryPoint));
|
|
467
435
|
if (id) (id === globalErrorId ? globalRoots : normalRoots).push(output.entryPoint);
|
|
468
436
|
}
|
|
469
437
|
async function collect(roots, target) {
|
|
@@ -514,12 +482,13 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
514
482
|
const browserRevision = () => `${boundaryRevision()}:${cssFiles.size}:${globalErrorCssFiles.size}:${nativePackages.version}`;
|
|
515
483
|
// Route Handlers have a Node React environment and must not share RSC chunks
|
|
516
484
|
// that import registerServerReference or client-reference registration.
|
|
517
|
-
await serverBuild(handlerEntries, 'server', 'route');
|
|
485
|
+
if (!devLazyOnly) await serverBuild(handlerEntries, 'server', 'route');
|
|
518
486
|
for (;;) {
|
|
519
487
|
// Each graph consumes discoveries from the preceding one. Discovering a
|
|
520
488
|
// client boundary in RSC is normal: SSR and browser below already include
|
|
521
489
|
// it. Only discoveries made too late for a graph require another pass.
|
|
522
490
|
const emittedActions = actionRevision();
|
|
491
|
+
if (!devLazyOnly) {
|
|
523
492
|
for (const action of actions.actions.values()) {
|
|
524
493
|
const entry = path.join(stage, '.entries', 'action-' + action.id + '.mjs');
|
|
525
494
|
await writeFile(entry, `export {${JSON.stringify(action.binding)} as invoke} from ${JSON.stringify(action.file)};`);
|
|
@@ -527,8 +496,10 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
527
496
|
if (!action.runtimes?.size || action.runtimes.has('nodejs')) entries['action-' + action.id] = entry;
|
|
528
497
|
}
|
|
529
498
|
await serverBuild(entries, 'server', 'rsc');
|
|
499
|
+
}
|
|
530
500
|
const serverNativeVersion = nativePackages.version;
|
|
531
501
|
const emittedBoundaries = boundaryRevision();
|
|
502
|
+
if (!devLazyOnly) {
|
|
532
503
|
const ssrEntries = {};
|
|
533
504
|
const globalSsrEntries = {};
|
|
534
505
|
for (const boundary of boundaries.values()) {
|
|
@@ -539,63 +510,78 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
539
510
|
await serverBuild(ssrEntries, 'app-ssr', 'ssr');
|
|
540
511
|
for (const boundary of boundaries.values()) if (boundary.edge) await edgeAppBuild(path.join(stage, '.entries', boundary.id + '.ssr.mjs'), path.join(stage, 'app-ssr-edge', boundary.id + '.mjs'), 'ssr', boundary.exports.includes('default'));
|
|
541
512
|
await serverBuild(globalSsrEntries, 'app-ssr', 'ssr', globalErrorCssFiles);
|
|
513
|
+
}
|
|
542
514
|
const browserEntries = {};
|
|
543
515
|
for (const boundary of boundaries.values()) {
|
|
544
516
|
const entry = path.join(stage, '.entries', boundary.id + '.browser.mjs');
|
|
545
517
|
await writeFile(entry, boundaryEntry(boundary));
|
|
546
518
|
browserEntries[boundary.id] = entry;
|
|
547
519
|
}
|
|
548
|
-
await collectBrowserStyles(browserEntries);
|
|
520
|
+
if(!devLazyRequest)await collectBrowserStyles(browserEntries);
|
|
521
|
+
let lazyEntries={};
|
|
522
|
+
if(lazy)for(;;){
|
|
523
|
+
const next=await lazy.targetEntries();
|
|
524
|
+
if(Object.keys(next).join(',')===Object.keys(lazyEntries).join(','))break;
|
|
525
|
+
lazyEntries=next;
|
|
526
|
+
await collectBrowserStyles(devLazyRequest?lazyEntries:{...browserEntries,...lazyEntries},true);
|
|
527
|
+
}
|
|
549
528
|
globalErrorOnlyCss.clear();
|
|
550
529
|
for (const file of globalErrorCssFiles) if (!cssFiles.has(file)) globalErrorOnlyCss.add(file);
|
|
551
530
|
const emittedBrowserInputs = browserRevision();
|
|
552
|
-
if (routes.some(route => route.kind === 'page')) {
|
|
531
|
+
if (devLazyOnly||routes.some(route => route.kind === 'page')) {
|
|
553
532
|
let globalErrorCss = [];
|
|
554
533
|
if (globalErrorCssFiles.size) {
|
|
555
534
|
const styleEntry = path.join(stage, '.entries', 'app-global-error-style.mjs');
|
|
556
535
|
await writeFile(styleEntry, [...globalErrorCssFiles].map(file => `import ${JSON.stringify(file)};`).join('\n'));
|
|
557
536
|
const styles = await bundle({ ...common, entryPoints: { 'app-global-error-style': styleEntry }, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]',
|
|
558
537
|
platform: 'browser', format: 'esm', target: ['es2022'], minify: !dev, sourcemap: dev || productionBrowserSourceMaps,
|
|
559
|
-
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] });
|
|
538
|
+
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] }, { role: 'styles' });
|
|
560
539
|
globalErrorCss = Object.values(styles.metafile.outputs).filter(output => output.cssBundle).map(output => publicPath + path.basename(output.cssBundle));
|
|
561
540
|
}
|
|
562
541
|
if (globalErrorId) manifest.app.globalError = { id: globalErrorId, name: 'default', css: globalErrorCss };
|
|
563
542
|
const runtimeEntry = path.join(stage, '.entries', 'app-runtime.mjs');
|
|
564
543
|
const htmlOnlyRoutes = !dev && htmlOnly.size ? routes.filter(route => route.kind === 'page' && !route.internal).map(route => ({pattern:route.pattern,...(htmlOnly.has(route.id)?{htmlOnly:true}:{})})) : undefined;
|
|
565
544
|
const importers = Object.entries(browserEntries).map(([id, file]) => `${JSON.stringify(id)}:()=>import(${JSON.stringify(file)})`);
|
|
566
|
-
await writeFile(runtimeEntry, `${dev ? `import ${JSON.stringify(devClientFile)};\n` : ''}\nimport {bootstrapApp} from ${JSON.stringify(path.join(packageRoot, 'runtime/app-client.mjs'))};\nbootstrapApp({clientModules:{${importers.join(',')}},...${JSON.stringify({ basePath, assetPrefix, assetBase, htmlOnlyRoutes, trailingSlash, skipTrailingSlashRedirect, deploymentId: dev ? undefined : manifest.cacheId, strictMode: manifest.config.reactStrictMode !== false, cacheComponents: manifest.config.cacheComponents === true, partialPrefetching: manifest.config.partialPrefetching === true, navigationCache: manifest.config.navigationCache, ...(globalErrorId ? { globalErrorId, globalErrorCss } : {}), ...(dev ? { dev: { buildId: manifest.buildId, clientManifest: manifest.devClient, onDemand:manifest.devOnDemand===true, sequence:manifest.devSequence, session:manifest.devSession, indicator:manifest.devIndicators, ...(manifest.devTools ? {tools:manifest.devTools} : {}) } } : {}) })}});\n`);
|
|
567
|
-
const clientEntries = { 'app-runtime': runtimeEntry, ...browserEntries };
|
|
545
|
+
if(!devLazyRequest)await writeFile(runtimeEntry, `${dev ? `import ${JSON.stringify(devClientFile)};\n` : ''}\nimport {bootstrapApp} from ${JSON.stringify(path.join(packageRoot, 'runtime/app-client.mjs'))};\nbootstrapApp({clientModules:{${importers.join(',')}},...${JSON.stringify({ basePath, assetPrefix, assetBase, htmlOnlyRoutes, trailingSlash, skipTrailingSlashRedirect, deploymentId: dev ? undefined : manifest.cacheId, strictMode: manifest.config.reactStrictMode !== false, cacheComponents: manifest.config.cacheComponents === true, partialPrefetching: manifest.config.partialPrefetching === true, navigationCache: manifest.config.navigationCache, ...(globalErrorId ? { globalErrorId, globalErrorCss } : {}), ...(dev ? { dev: { buildId: manifest.buildId, clientManifest: manifest.devClient, onDemand:manifest.devOnDemand===true, sequence:manifest.devSequence, session:manifest.devSession, indicator:manifest.devIndicators, ...(manifest.devTools ? {tools:manifest.devTools} : {}) } } : {}) })}});\n`);
|
|
546
|
+
const clientEntries = devLazyRequest?{...lazyEntries,...lazy.bridgeEntries()}:{ 'app-runtime': runtimeEntry, ...browserEntries,...lazyEntries,...(lazy?lazy.bridgeEntries():{}) };
|
|
568
547
|
const browser = await bundle({ ...common, entryPoints: clientEntries, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]', chunkNames: 'app-chunk-[hash]', platform: 'browser', format: 'esm', target: ['es2022'], splitting: true, minify: !dev, sourcemap: dev || productionBrowserSourceMaps,
|
|
569
|
-
define: { 'process.env': '{}', ...common.define, __PRNEXT_CACHE_COMPONENTS__: JSON.stringify(manifest.config.cacheComponents === true) }, plugins: [moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitAllStyles:
|
|
548
|
+
define: { 'process.env': '{}', ...common.define, __PRNEXT_CACHE_COMPONENTS__: JSON.stringify(manifest.config.cacheComponents === true) }, plugins: [...(lazy?[lazy.plugin()]:[]),moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitAllStyles: !lazy }), nativePackages.plugin('browser'), cssModules.plugin(true, { emitStyles: () => !!lazy })] });
|
|
570
549
|
fonts.record(browser.metafile);
|
|
571
550
|
const entryIds = new Map(Object.entries(clientEntries).map(([id, file]) => [file, id]));
|
|
572
551
|
const edgeBoundaries = new Set([...boundaries.values()].filter(boundary => boundary.edge).map(boundary => boundary.id));
|
|
573
552
|
let client;
|
|
574
553
|
for (const [output, metadata] of Object.entries(browser.metafile.outputs)) {
|
|
575
554
|
if (!metadata.entryPoint || !output.endsWith('.js')) continue;
|
|
555
|
+
if(lazy?.record(metadata,output))continue;
|
|
576
556
|
const id = entryIds.get(path.resolve(project.root, metadata.entryPoint));
|
|
577
557
|
if (id === 'app-runtime') { client = publicPath + path.basename(output); continue; }
|
|
578
558
|
if (!id) continue;
|
|
579
559
|
manifest.app.clientModules[id] = { id, chunks: [id, publicPath + path.basename(output)], name: '*', ssrModule: `app-ssr/${id}.mjs`, ...(edgeBoundaries.has(id) ? { edgeSsrModule: `app-ssr-edge/${id}.mjs` } : {}), browserModule: publicPath + path.basename(output) };
|
|
580
560
|
}
|
|
581
|
-
const
|
|
582
|
-
for (const route of
|
|
561
|
+
const styledRoutes = routes.filter(route => route.kind === 'page');
|
|
562
|
+
for (const route of styledRoutes) {
|
|
583
563
|
// All esbuild graphs canonicalize their inputs, including symlinked
|
|
584
564
|
// convention files. Match those identities before querying reachability.
|
|
585
565
|
for (const files of appStyleScopes(route)) for (const file of files) styleGraph.alias(file, await canonicalFile(file));
|
|
586
|
-
|
|
566
|
+
}
|
|
567
|
+
// Freeze discovery before any independent emission starts. Deduplication
|
|
568
|
+
// owns each output path once, and linking follows source order even when
|
|
569
|
+
// builds complete out of order. Failure drains all started jobs first.
|
|
570
|
+
const plan = immutableStylePlan(styledRoutes, route => planAppStyleEntries(route, styleGraph));
|
|
571
|
+
const {outputs: styleBundles, concurrency} = await executeStylePlan(plan, async ({styles: files}) => {
|
|
572
|
+
const id = 'app-style-' + createHash('sha256').update(files.map(file => path.relative(project.root, file).replaceAll(path.sep, '/')).join('\0')).digest('hex').slice(0, 16);
|
|
573
|
+
const styleEntry = path.join(stage, '.entries', id + '.mjs');
|
|
574
|
+
await writeFile(styleEntry, files.map(file => `import ${JSON.stringify(file)};`).join('\n'));
|
|
575
|
+
const output = await bundle({ ...common, ...(writeParallelStyles ? {write: false} : {}), entryPoints: { [id]: styleEntry }, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]', platform: 'browser', format: 'esm', target: ['es2022'], minify: !dev, sourcemap: dev || productionBrowserSourceMaps,
|
|
576
|
+
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] }, { role: 'styles' });
|
|
577
|
+
if (writeParallelStyles) await writeParallelStyles(output.outputFiles);
|
|
578
|
+
return Object.values(output.metafile.outputs).filter(output => output.cssBundle).map(output => publicPath + path.basename(output.cssBundle));
|
|
579
|
+
}, dev ? false : parallelBuilds);
|
|
580
|
+
if (!dev && parallelBuilds) manifest.buildParallelism = {version: 1, phase: 'app-styles', jobs: plan.jobs.length, concurrency};
|
|
581
|
+
for (const route of plan.routes) {
|
|
587
582
|
const styles = [];
|
|
588
583
|
const cssScopes = [];
|
|
589
|
-
for (const {
|
|
590
|
-
const signature = JSON.stringify(files);
|
|
591
|
-
if (!styleBundles.has(signature)) {
|
|
592
|
-
const id = 'app-style-' + createHash('sha256').update(files.map(file => path.relative(project.root, file).replaceAll(path.sep, '/')).join('\0')).digest('hex').slice(0, 16);
|
|
593
|
-
const styleEntry = path.join(stage, '.entries', id + '.mjs');
|
|
594
|
-
await writeFile(styleEntry, files.map(file => `import ${JSON.stringify(file)};`).join('\n'));
|
|
595
|
-
const output = await bundle({ ...common, entryPoints: { [id]: styleEntry }, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]', platform: 'browser', format: 'esm', target: ['es2022'], minify: !dev, sourcemap: dev || productionBrowserSourceMaps,
|
|
596
|
-
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] });
|
|
597
|
-
styleBundles.set(signature, Object.values(output.metafile.outputs).filter(output => output.cssBundle).map(output => publicPath + path.basename(output.cssBundle)));
|
|
598
|
-
}
|
|
584
|
+
for (const { signature, path:scopePath, kind } of route.scopes) {
|
|
599
585
|
const css = styleBundles.get(signature);
|
|
600
586
|
styles.push(...css);
|
|
601
587
|
if (route.parallel) cssScopes.push({ path:scopePath, kind, css });
|
|
@@ -610,10 +596,13 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
610
596
|
module: `server/action-${action.runtimes?.has('nodejs')?'':'edge-'}${action.id}.mjs`, export: 'invoke',
|
|
611
597
|
...(action.runtimes?.has('edge') ? {edgeModule:`server/action-edge-${action.id}.mjs`} : {}),
|
|
612
598
|
}]));
|
|
613
|
-
if (emittedActions === actionRevision() && emittedBoundaries === boundaryRevision() &&
|
|
599
|
+
if ((devLazyOnly || emittedActions === actionRevision() && emittedBoundaries === boundaryRevision()) &&
|
|
614
600
|
serverNativeVersion === nativePackages.version && emittedBrowserInputs === browserRevision()) break;
|
|
615
601
|
}
|
|
616
602
|
|
|
603
|
+
lazy?.finish();
|
|
604
|
+
if (dev) manifest.devActions = developmentActionMetadata(project.root, actions.actions.values());
|
|
605
|
+
|
|
617
606
|
if (htmlGraph) for (const route of routes) if (htmlOnly.has(route.id)) {
|
|
618
607
|
for (const file of [...appStyleScopes(route).flat(), ...(route.metadataFiles || []).map(item=>item.file)].filter(Boolean)) htmlGraph.alias(file, await canonicalFile(file));
|
|
619
608
|
htmlGraph.validate(route);
|
package/build/build-lock.mjs
CHANGED
|
@@ -1,71 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { hostname } from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
|
|
6
|
-
const LOCK = '.prnext-build.lock';
|
|
7
|
-
const busy = message => Object.assign(new Error(message), { code: 'PRNEXT_BUILD_LOCKED' });
|
|
8
|
-
function alive(pid) {
|
|
9
|
-
try { process.kill(pid, 0); return true; }
|
|
10
|
-
catch (error) { return error.code !== 'ESRCH'; }
|
|
11
|
-
}
|
|
12
|
-
async function owner(directory) {
|
|
13
|
-
const info = await lstat(directory);
|
|
14
|
-
if (!info.isDirectory() || info.isSymbolicLink()) throw busy(`The project lock is not a real directory. Check ${path.basename(directory)} before retrying.`);
|
|
15
|
-
const file = await open(path.join(directory, 'owner.json'), 'r');
|
|
16
|
-
try {
|
|
17
|
-
if ((await file.stat()).size > 4096) throw new Error('oversized owner');
|
|
18
|
-
const value = JSON.parse(await file.readFile('utf8'));
|
|
19
|
-
if (!Number.isSafeInteger(value.pid) || value.pid < 1 || typeof value.host !== 'string' || !/^[a-f0-9-]{36}$/.test(value.token)) throw new Error('invalid owner');
|
|
20
|
-
return value;
|
|
21
|
-
} finally { await file.close(); }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** One publisher per canonical project, covering React and direct static imports. */
|
|
25
|
-
export async function withBuildLock(directory, action, {development = false} = {}) {
|
|
26
|
-
const root = await realpath(directory), lock = path.join(root, development ? '.prnext-dev.lock' : LOCK);
|
|
27
|
-
const identity = { pid: process.pid, host: hostname(), token: randomUUID(), createdAt: new Date().toISOString() };
|
|
28
|
-
const prepared = path.join(root, `.prnext-lock-${identity.token}`);
|
|
29
|
-
await mkdir(prepared, { mode: 0o700 });
|
|
30
|
-
try {
|
|
31
|
-
await writeFile(path.join(prepared, 'owner.json'), JSON.stringify(identity), { flag: 'wx', mode: 0o600 });
|
|
32
|
-
for (let attempt = 0; ; attempt++) {
|
|
33
|
-
try { await rename(prepared, lock); break; }
|
|
34
|
-
catch (error) {
|
|
35
|
-
if (!['EEXIST', 'ENOTEMPTY', 'EPERM', 'EACCES'].includes(error.code)) throw error;
|
|
36
|
-
let previous;
|
|
37
|
-
try { previous = await owner(lock); }
|
|
38
|
-
catch (cause) {
|
|
39
|
-
if (cause.code === 'ENOENT' && attempt < 8) continue;
|
|
40
|
-
throw busy(`Cannot verify the project lock. Check ${path.basename(lock)} before retrying.`);
|
|
41
|
-
}
|
|
42
|
-
if (previous.host !== identity.host || alive(previous.pid)) throw busy(development ? `A development server is already running for this project (PID ${previous.pid}). Stop it before starting another.` : `A build is already running for this project (PID ${previous.pid}). Wait for it to finish.`);
|
|
43
|
-
// Exactly one contender can recover this generation. Never delete a lock
|
|
44
|
-
// merely because it is old: long builds and suspended processes are valid.
|
|
45
|
-
let claim;
|
|
46
|
-
try { claim = await open(path.join(lock, 'recovery'), 'wx', 0o600); }
|
|
47
|
-
catch (cause) {
|
|
48
|
-
if (cause.code === 'ENOENT' && attempt < 8) continue;
|
|
49
|
-
throw busy(`Project lock recovery is in progress. Retry shortly; if recovery was interrupted, check ${path.basename(lock)} manually.`);
|
|
50
|
-
}
|
|
51
|
-
const retired = path.join(root, `.prnext-lock-retired-${identity.token}`);
|
|
52
|
-
try {
|
|
53
|
-
await claim.writeFile(JSON.stringify(identity)); await claim.close(); claim = null;
|
|
54
|
-
const current = await owner(lock);
|
|
55
|
-
if (current.token !== previous.token) {
|
|
56
|
-
// Do not unlink through this path: the new owner can release and
|
|
57
|
-
// another generation can appear between our read and the unlink.
|
|
58
|
-
throw busy('The build lock changed during recovery. Retry the build.');
|
|
59
|
-
}
|
|
60
|
-
await rename(lock, retired);
|
|
61
|
-
await rm(retired, { recursive: true });
|
|
62
|
-
} finally { await claim?.close(); }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
try { return await action(root); }
|
|
66
|
-
finally {
|
|
67
|
-
// Ownership must still match before releasing a lock.
|
|
68
|
-
if ((await owner(lock)).token === identity.token) await rm(lock, { recursive: true });
|
|
69
|
-
}
|
|
70
|
-
} finally { await rm(prepared, { recursive: true, force: true }); }
|
|
71
|
-
}
|
|
1
|
+
// Compiler entry point for the shared publisher and maintenance lock protocol.
|
|
2
|
+
export {withBuildLock} from '../runtime/project-lock.mjs';
|