@thomas.f/prnext 0.1.16-alpha → 0.1.18-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/AI.md +63 -0
- package/README.md +35 -503
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +6 -1
- package/build/analyze.mjs +340 -0
- package/build/app-config.mjs +20 -8
- package/build/app-static.mjs +41 -14
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +99 -31
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +39 -11
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/check.mjs +56 -11
- 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 +51 -4
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +50 -13
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +65 -10
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +85 -0
- package/build/dev-dependencies.mjs +44 -0
- package/build/dev-discovery.mjs +28 -0
- package/build/dev-worker.mjs +14 -5
- package/build/dev.mjs +64 -6
- package/build/dynamic.mjs +1 -1
- package/build/edge.mjs +5 -4
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +50 -0
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +140 -112
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/instrumentation.mjs +30 -0
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +3 -4
- package/build/migrate.mjs +60 -57
- package/build/migration-audit.mjs +1 -1
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +112 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +100 -0
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +129 -0
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-export.mjs +4 -4
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +17 -3
- package/build/transform-cache.mjs +37 -4
- package/build/transform.mjs +1 -1
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +95 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +49 -0
- 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-watch.mjs +22 -0
- package/build/typescript.mjs +11 -8
- package/build/webpack-graph.mjs +170 -30
- package/build/worker-memory.mjs +9 -0
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +162 -122
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +12 -2
- 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 +150 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +89 -10
- package/compat/link.cjs +12 -9
- package/compat/link.d.cts +4 -3
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/navigation.d.cts +4 -3
- 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/router.d.cts +4 -3
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/typed-route.d.cts +17 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +275 -0
- package/docs/ai/data.md +288 -0
- package/docs/ai/deployment.md +269 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +171 -0
- package/docs/ai/routing.md +214 -0
- package/docs/ai/troubleshooting.md +155 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +44 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -0
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +226 -68
- 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 +100 -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/cache-components-rsc.mjs +4 -1
- package/runtime/cache-request.mjs +2 -1
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -0
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-ai-brands.mjs +26 -0
- package/runtime/dev-ai-style.mjs +99 -0
- package/runtime/dev-build-reasons.mjs +25 -0
- package/runtime/dev-cache-control.mjs +53 -0
- package/runtime/dev-client.mjs +245 -46
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-console-copy.mjs +19 -0
- package/runtime/dev-console.mjs +106 -0
- package/runtime/dev-dependency-graph.mjs +40 -0
- package/runtime/dev-element-picker-style.mjs +27 -0
- package/runtime/dev-element-picker.mjs +213 -0
- package/runtime/dev-error-overlay-style.mjs +41 -0
- package/runtime/dev-error-overlay.mjs +167 -0
- package/runtime/dev-error-report.mjs +121 -0
- package/runtime/dev-file-changes.mjs +1 -1
- package/runtime/dev-gateway.mjs +44 -0
- package/runtime/dev-host-relay.mjs +66 -0
- package/runtime/dev-indicator.mjs +191 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +34 -0
- package/runtime/dev-logo.mjs +2 -0
- package/runtime/dev-notification.mjs +81 -0
- package/runtime/dev-popout.mjs +34 -0
- package/runtime/dev-position.mjs +107 -0
- package/runtime/dev-preview-touch.mjs +122 -0
- package/runtime/dev-runtime-pool.mjs +45 -0
- package/runtime/dev-runtime-spare.mjs +57 -0
- package/runtime/dev-tools-client.mjs +155 -0
- package/runtime/dev-tools-mcp.mjs +126 -0
- package/runtime/dev-tools-metrics.mjs +31 -0
- package/runtime/dev-tools-model.mjs +42 -0
- package/runtime/dev-tools-panel.mjs +341 -0
- package/runtime/dev-tools-result.mjs +37 -0
- package/runtime/dev-tools-server.mjs +256 -0
- package/runtime/dev-tools-source.mjs +104 -0
- package/runtime/dev-tooltip.mjs +94 -0
- package/runtime/dev-ui-icons.mjs +8 -0
- package/runtime/dev-workbench-style.mjs +106 -0
- package/runtime/dev-workbench.mjs +428 -0
- package/runtime/dev-worker-logs.mjs +31 -0
- package/runtime/development.mjs +324 -0
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/env.mjs +2 -1
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +50 -0
- package/runtime/flight-tap.mjs +28 -0
- 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/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +24 -8
- package/runtime/pages-navigation.mjs +54 -64
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/preload.mjs +12 -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 +41 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +29 -18
- package/runtime/worker.mjs +102 -24
package/build/index.mjs
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import {compileInstrumentation} from './instrumentation.mjs';
|
|
2
|
+
import {prerenderPages as prerender} from './prerender-pages.mjs';
|
|
3
|
+
import {isolatedPrerender,prerenderProduction} from './production-builder.mjs';
|
|
1
4
|
import { rename } from '../runtime/fs.mjs';
|
|
2
5
|
import { writeRuntimeContract } from './runtime-contract.mjs';
|
|
3
6
|
import { writeBuildOutput } from './output-contract.mjs';
|
|
4
7
|
import { bundleRuntime } from './bundle-runtime.mjs';
|
|
5
|
-
import {
|
|
8
|
+
import {withPersistentTransforms} from './transform-cache.mjs';
|
|
9
|
+
import {applicationEntry, frameworkImportName, frameworkRuntimeImportPattern} from './framework-imports.mjs';
|
|
6
10
|
import { expandLocales, localizedStaticPaths } from './i18n.mjs';
|
|
7
|
-
import { build as bundle, withCompilation, configureCompiler, compilerSource } from './compiler.mjs';
|
|
8
|
-
import {
|
|
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';
|
|
14
|
+
import {writeBuildAnalysis} from './analyze.mjs';
|
|
15
|
+
import {routeFiles} from './dev-discovery.mjs';
|
|
16
|
+
import {writeTypedRoutes} from './typed-routes.mjs';
|
|
17
|
+
import { mkdir, readFile, writeFile, rm, cp, copyFile, stat, readdir, realpath } from 'node:fs/promises';
|
|
9
18
|
import path from 'node:path';
|
|
10
19
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
20
|
import { createRequire } from 'node:module';
|
|
@@ -30,28 +39,52 @@ import { prepareMetadataRoutes } from './metadata-routes.mjs';
|
|
|
30
39
|
import { prepareImageResponse } from './og.mjs';
|
|
31
40
|
import { prepareCacheHandler, prepareCacheHandlers } from './cache-handlers.mjs';
|
|
32
41
|
import { npmPackagesPlugin } from './npm-packages.mjs';
|
|
33
|
-
import { createRefreshTransform, devSingletonsPlugin, devClientFile } from './dev.mjs';
|
|
42
|
+
import { createRefreshTransform, createSourceLocationTransform, devSingletonsPlugin, devClientFile } from './dev.mjs';
|
|
34
43
|
import { snapshotEnvConfig } from '../runtime/env.mjs';
|
|
35
44
|
import { validateStaticPaths } from '../runtime/pages-paths.mjs';
|
|
36
45
|
import { projectTsconfig, checkTypeScript } from './typescript.mjs';
|
|
46
|
+
import {createReactCompilerTransform} from './react-compiler.mjs';
|
|
47
|
+
import {writeReactCompilerReport,reactCompilerReport} from './react-compiler-report.mjs';
|
|
37
48
|
export { staticPath } from '../runtime/pages-paths.mjs';
|
|
38
49
|
|
|
39
50
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
40
|
-
const compatibleModules = new Set(['link', 'head', 'router', 'compat/router', 'next-router-context', 'next-app-router-context', 'image', 'cache', 'dynamic', 'error', 'document', 'app', 'script', 'og']);
|
|
41
|
-
const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx' };
|
|
51
|
+
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']);
|
|
52
|
+
const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx', '.mts': 'ts', '.cts': 'ts' };
|
|
42
53
|
const assetLoaders = Object.fromEntries(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'].map(ext => [ext, 'file']));
|
|
43
54
|
|
|
44
|
-
|
|
55
|
+
// These framework files have already been enumerated: copyFile avoids cp's
|
|
56
|
+
// recursive traversal/stat work for every file. Bound I/O on large installations.
|
|
57
|
+
async function copyRuntimeFiles(stage,{development=true}={}) {
|
|
58
|
+
const files = (await Promise.all(['runtime', 'compat'].map(async directory => {
|
|
59
|
+
await mkdir(path.join(stage, directory), { recursive: true });
|
|
60
|
+
return (await readdir(path.join(packageRoot, directory)))
|
|
61
|
+
.filter(name => directory === 'runtime' ? name === 'profiles.json' || development&&name.endsWith('.mjs') && !name.endsWith('.test.mjs') : name.endsWith('.cjs'))
|
|
62
|
+
.map(name => path.join(directory, name));
|
|
63
|
+
}))).flat();
|
|
64
|
+
let next = 0;
|
|
65
|
+
const copying = await Promise.allSettled(Array.from({ length: Math.min(8, files.length) }, async () => {
|
|
66
|
+
while (next < files.length) {
|
|
67
|
+
const file = files[next++];
|
|
68
|
+
await copyFile(path.join(packageRoot, file), path.join(stage, file));
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
// Let outstanding writes finish before the caller removes a failed stage.
|
|
72
|
+
const failed = copying.find(result => result.status === 'rejected');
|
|
73
|
+
if (failed) throw failed.reason;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function compatibilityPlugin({ browser, pageFiles, projectRoot, documentFile, serverOnlyFiles, fonts, refresh, locations, reactCompiler }) {
|
|
45
77
|
return {
|
|
46
78
|
name: 'prnext-pages-compatibility',
|
|
47
79
|
setup(esbuild) {
|
|
80
|
+
applicationEntry(esbuild,readCompilerFile,{transformTypedHelpers:browser && !!reactCompiler});
|
|
48
81
|
esbuild.onResolve({ filter: /\.(?:css|scss|sass)$/i }, args => args.importer === documentFile
|
|
49
82
|
? { errors: [{ text: 'CSS cannot be imported within pages/_document. Move global styles to pages/_app, or return collected styles from Document.getInitialProps.' }] } : undefined);
|
|
50
83
|
esbuild.onResolve({ filter: frameworkRuntimeImportPattern }, args => {
|
|
51
84
|
const name = frameworkImportName(args.path);
|
|
52
85
|
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.` }] };
|
|
53
86
|
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.` }] };
|
|
54
|
-
if (browser && ['cache', 'og'].includes(name)) return { errors: [{ text: `${args.path} is server-only and cannot be imported by a browser component.` }] };
|
|
87
|
+
if (browser && ['cache', 'og', 'redis-cache'].includes(name)) return { errors: [{ text: `${args.path} is server-only and cannot be imported by a browser component.` }] };
|
|
55
88
|
const file = name === 'compat/router' ? 'compat-router' : name;
|
|
56
89
|
return browser ? { path: path.join(packageRoot, 'compat', file + '.cjs') } : { path: `../compat/${file}.cjs`, external: true };
|
|
57
90
|
});
|
|
@@ -67,9 +100,15 @@ function compatibilityPlugin({ browser, pageFiles, projectRoot, documentFile, fo
|
|
|
67
100
|
});
|
|
68
101
|
esbuild.onLoad({ filter: /.*/, namespace: 'prnext-empty' }, () => ({ contents: '', loader: 'js' }));
|
|
69
102
|
}
|
|
70
|
-
esbuild.onLoad({ filter: /\.(?:[cm]?
|
|
103
|
+
esbuild.onLoad({ filter: /\.(?:[cm]?[jt]s|[jt]sx)$/, namespace: 'file' }, async args => {
|
|
71
104
|
if (browser && args.path === documentFile) return { errors: [{ text: 'pages/_document is server-only and cannot be imported by a browser page or component.' }] };
|
|
72
|
-
const
|
|
105
|
+
const original=await readCompilerFile(args.path,args.prnextSource);
|
|
106
|
+
// Pages shares a server graph with API handlers, Document and data
|
|
107
|
+
// loaders. Keep that entire graph original: an uppercase JSX utility
|
|
108
|
+
// may be called normally there, outside a React render. Browser render
|
|
109
|
+
// graphs receive memoization before instrumentation and JSX lowering.
|
|
110
|
+
const compiled=browser && reactCompiler && args.path!==documentFile && !serverOnlyFiles.has(args.path) ? await reactCompiler(original,args.path,{loaderOutput:args.prnextSource!==undefined}) : original;
|
|
111
|
+
const source = await fonts.transform(locations?await locations(compiled,args.path):compiled, args.path);
|
|
73
112
|
assertSupportedSource(source, args.path);
|
|
74
113
|
const dynamicSource = transformDynamicImports(source, args.path, { projectRoot, mode: browser ? 'browser' : 'server' });
|
|
75
114
|
let contents = browser && pageFiles.has(args.path) ? stripServerCode(dynamicSource, args.path) : dynamicSource;
|
|
@@ -110,91 +149,34 @@ async function addBuiltinPagesErrors(project, stage) {
|
|
|
110
149
|
}
|
|
111
150
|
}
|
|
112
151
|
|
|
113
|
-
async function prerender(stage, manifest, dev) {
|
|
114
|
-
const { prerenderRoute, renderFallback } = await import(pathToFileURL(path.join(stage, 'runtime/render.mjs')).href);
|
|
115
|
-
const paths = new Set();
|
|
116
|
-
const seedsByRoute = new Map();
|
|
117
|
-
const validatedDocuments = new Set();
|
|
118
|
-
const staticListings = new Map();
|
|
119
|
-
// Error page data is available when another static page returns notFound.
|
|
120
|
-
const ordered = [...manifest.routes].sort((left, right) => Number(!!right.errorStatus) - Number(!!left.errorStatus));
|
|
121
|
-
for (const route of ordered) {
|
|
122
|
-
if (route.kind !== 'page' || route.router === 'app') continue;
|
|
123
|
-
const modulePath = path.join(stage, route.module);
|
|
124
|
-
const page = await import(pathToFileURL(modulePath).href);
|
|
125
|
-
if (page.__prnextDocument && !validatedDocuments.has(page.__prnextDocument)) {
|
|
126
|
-
for (const name of ['getStaticProps', 'getStaticPaths', 'getServerSideProps']) {
|
|
127
|
-
if (Object.hasOwn(page.__prnextDocument, name) || typeof page.Document?.[name] === 'function') throw new Error(`pages/_document does not support ${name}. Use Document.getInitialProps for server document rendering.`);
|
|
128
|
-
}
|
|
129
|
-
validatedDocuments.add(page.__prnextDocument);
|
|
130
|
-
}
|
|
131
|
-
if (route.errorStatus && (page.getServerSideProps || page.default?.getInitialProps)) throw new Error(`pages${route.pattern} must be static and cannot use getServerSideProps or getInitialProps. Use getStaticProps instead.`);
|
|
132
|
-
route.gip = typeof page.default?.getInitialProps === 'function';
|
|
133
|
-
route.appGip = !!page.App && page.App.getInitialProps !== page.App.origGetInitialProps;
|
|
134
|
-
if (route.internal && route.errorStatus && route.appGip) {
|
|
135
|
-
// A custom App hook makes the built-in Error request-dependent. Select
|
|
136
|
-
// its actual _error module so the default status props and visible URL
|
|
137
|
-
// reach App.getInitialProps instead of a synthetic static wrapper.
|
|
138
|
-
const kind = route.errorStatus === 404 ? 'notFound' : 'serverError';
|
|
139
|
-
if (manifest.pagesErrors?.[kind] === route.id) delete manifest.pagesErrors[kind];
|
|
140
|
-
}
|
|
141
|
-
if (route.gip && (page.getStaticProps || page.getServerSideProps)) throw new Error(`Page ${route.pattern} cannot combine getInitialProps with getStaticProps or getServerSideProps.`);
|
|
142
|
-
if (route.internal && !route.errorStatus) {
|
|
143
|
-
if (page.getStaticProps || page.getServerSideProps || page.getStaticPaths) throw new Error('pages/_error does not support getStaticProps, getServerSideProps or getStaticPaths. Use ErrorComponent.getInitialProps instead.');
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (page.getServerSideProps && page.getStaticProps) throw new Error(`Page ${route.pattern} exports both getServerSideProps and getStaticProps. Choose one.`);
|
|
147
|
-
if (page.getStaticPaths && !page.getStaticProps) throw new Error(`Page ${route.pattern} exports getStaticPaths without getStaticProps.`);
|
|
148
|
-
if (page.getServerSideProps) { route.ssp = true; continue; }
|
|
149
|
-
if (page.getStaticProps) route.ssg = true;
|
|
150
|
-
else if (route.gip || route.appGip) continue;
|
|
151
|
-
let entries = [{ path: route.pattern, params: {} }];
|
|
152
|
-
if (isDynamic(route.pattern)) {
|
|
153
|
-
if (!page.getStaticProps) continue;
|
|
154
|
-
if (!page.getStaticPaths) throw new Error(`Dynamic page ${route.pattern} with getStaticProps must export getStaticPaths.`);
|
|
155
|
-
if (!staticListings.has(modulePath)) staticListings.set(modulePath, Promise.resolve().then(() => page.getStaticPaths(manifest.config.i18n ? {locales: manifest.config.i18n.locales, defaultLocale: manifest.config.i18n.defaultLocale} : {})));
|
|
156
|
-
const result = localizedStaticPaths(route, await staticListings.get(modulePath), manifest.config.i18n);
|
|
157
|
-
route.fallback = result.fallback;
|
|
158
|
-
entries = result.paths;
|
|
159
|
-
} else if (page.getStaticPaths) throw new Error(`getStaticPaths is only valid on dynamic pages (${route.pattern}).`);
|
|
160
|
-
for (const entry of entries) {
|
|
161
|
-
if (paths.has(entry.path)) throw new Error(`Multiple pages prerender the same path: ${entry.path}.`);
|
|
162
|
-
paths.add(entry.path);
|
|
163
|
-
const rendered = await prerenderRoute({ ...manifest.config, manifest, distDir: stage, route, modulePath, path: entry.path, params: entry.params, client: route.client, css: route.css, pattern: route.pattern, errorStatus: route.errorStatus, buildId: manifest.buildId, production: !dev });
|
|
164
|
-
const file = `static/${createHash('sha256').update(entry.path).digest('hex').slice(0, 20)}.html`;
|
|
165
|
-
await writeFile(path.join(stage, file), rendered.body);
|
|
166
|
-
const seed = { routeId: route.id, path: entry.path, file, status: route.errorStatus || rendered.status || 200, headers: rendered.headers || {} };
|
|
167
|
-
if (route.ssg) {
|
|
168
|
-
seed.dataFile = file.replace(/\.html$/, '.json');
|
|
169
|
-
seed.revalidate = rendered.revalidate ?? false;
|
|
170
|
-
seed.generatedAt = rendered.generatedAt;
|
|
171
|
-
await writeFile(path.join(stage, seed.dataFile), rendered.dataJSON);
|
|
172
|
-
}
|
|
173
|
-
manifest.prerendered.push(seed);
|
|
174
|
-
if (!seedsByRoute.has(route.id)) seedsByRoute.set(route.id, []);
|
|
175
|
-
seedsByRoute.get(route.id).push(seed);
|
|
176
|
-
}
|
|
177
|
-
if (route.fallback === true) {
|
|
178
|
-
const rendered = await renderFallback({ ...manifest.config, modulePath, path: route.pattern, params: {}, client: route.client, css: route.css, pattern: route.pattern, buildId: manifest.buildId, production: !dev });
|
|
179
|
-
route.fallbackFile = `static/fallback-${route.id}.html`;
|
|
180
|
-
await writeFile(path.join(stage, route.fallbackFile), rendered.body);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Rendering errors first must not change the published route/seed ordering.
|
|
184
|
-
manifest.prerendered = manifest.routes.flatMap(route => seedsByRoute.get(route.id) || []);
|
|
185
|
-
}
|
|
186
152
|
|
|
187
153
|
let buildQueue = Promise.resolve();
|
|
154
|
+
let developmentModulesLoaded = false;
|
|
155
|
+
/** Disposing shared compiler contexts must not interrupt another queued build. */
|
|
156
|
+
export function disposeBuildSession(root){
|
|
157
|
+
const task=buildQueue.then(async()=>{const {closeIncrementalCompiler}=await import('./incremental.mjs');await closeIncrementalCompiler(root);});
|
|
158
|
+
buildQueue=task.catch(()=>{});return task;
|
|
159
|
+
}
|
|
188
160
|
/** Isolate process.env between programmatic builds, including simultaneous calls. */
|
|
189
161
|
export function build(projectRoot, options = {}) {
|
|
190
162
|
const task = buildQueue.then(async () => {
|
|
163
|
+
if (options.adapter === undefined || options.adapter === 'auto') {
|
|
164
|
+
const { detectBuildAdapter } = await import('./static-input.mjs');
|
|
165
|
+
const selected = await detectBuildAdapter(projectRoot, { prepared: options.adapter === 'auto' });
|
|
166
|
+
if (selected.adapter === 'static') return (await import('./static-adapter.mjs')).buildStatic(projectRoot, { ...selected, ...options });
|
|
167
|
+
if (options.adapter === 'auto') options = { ...options, adapter: 'react' };
|
|
168
|
+
}
|
|
191
169
|
if (options.adapter === 'static') return (await import('./static-adapter.mjs')).buildStatic(projectRoot, options);
|
|
192
170
|
if (options.adapter !== undefined && options.adapter !== 'react') throw new Error(`Unknown PRNext adapter: ${options.adapter}`);
|
|
193
171
|
if (options.from !== undefined || options.spa !== undefined) throw new Error('from and spa are only supported by the static adapter.');
|
|
194
172
|
const restore = snapshotEnvConfig();
|
|
195
173
|
const envMode = options.envMode || (process.env.NODE_ENV === 'test' ? 'test' : options.dev ? 'development' : 'production');
|
|
196
174
|
process.env.NODE_ENV = options.dev ? 'development' : 'production';
|
|
197
|
-
try { return await withBuildLock(projectRoot, root =>
|
|
175
|
+
try { return await withBuildLock(projectRoot, async root => {
|
|
176
|
+
const started=performance.now();
|
|
177
|
+
const result=await withPersistentTransforms(root,{dev:!!options.dev},()=>withCompilation(() => buildProject(root, { ...options, envMode })));
|
|
178
|
+
return {...result,durationMs:Math.round(performance.now()-started)};
|
|
179
|
+
}); }
|
|
198
180
|
finally { restore(); }
|
|
199
181
|
});
|
|
200
182
|
buildQueue = task.catch(() => {});
|
|
@@ -202,15 +184,25 @@ export function build(projectRoot, options = {}) {
|
|
|
202
184
|
}
|
|
203
185
|
|
|
204
186
|
/** Compile Pages Router and App Router projects. A failed build preserves .prnext. */
|
|
205
|
-
async function buildProject(projectRoot, { dev = false, envMode, incremental = false, devInputs } = {}) {
|
|
187
|
+
async function buildProject(projectRoot, { dev = false, envMode, incremental = false, devInputs, devRoutes, devSequence, devSession, devTools,devForceRefresh=false,devAffectedModules } = {}) {
|
|
206
188
|
const started = performance.now();
|
|
207
|
-
const config = await loadProjectConfig(projectRoot, { dev, envMode });
|
|
189
|
+
const config = await loadProjectConfig(projectRoot, { dev, envMode, devInputs });
|
|
208
190
|
const tsconfig = config.typescript ? await projectTsconfig(projectRoot, config) : undefined;
|
|
209
191
|
const environment = defineEnvironment(config);
|
|
210
192
|
const publicPaths = { trailingSlash: config.trailingSlash, skipTrailingSlashRedirect: config.skipTrailingSlashRedirect, basePath: config.basePath, assetPrefix: config.assetPrefix, assetBase: publicAssetBase(config) };
|
|
211
193
|
const customRoutes = await compileCustomRoutes(config);
|
|
212
194
|
const buildId = await generateBuildId(config, { dev });
|
|
213
|
-
const
|
|
195
|
+
const scanOptions={basePath:config.basePath,pageExtensions:config.pageExtensions,i18n:config.i18n};
|
|
196
|
+
let project,deferredRoutes=[];
|
|
197
|
+
if(dev && Array.isArray(devRoutes)) {
|
|
198
|
+
const discovery=await scanProject(projectRoot,{...scanOptions,devAppFiles:new Set()});
|
|
199
|
+
project=await scanProject(projectRoot,{...scanOptions,devAppFiles:routeFiles(discovery,devRoutes)});
|
|
200
|
+
await writeTypedRoutes(projectRoot,discovery,config);
|
|
201
|
+
deferredRoutes=discovery.routes.filter(route=>!devRoutes.includes(route.pattern)&&!route.internal&&!route.errorStatus);
|
|
202
|
+
project.routes=project.routes.filter(route=>devRoutes.includes(route.pattern)||route.internal||route.errorStatus);
|
|
203
|
+
} else project=await scanProject(projectRoot,scanOptions);
|
|
204
|
+
if(!dev&&config.htmlOnly.length)selectHtmlOnlyRoutes(project.routes,config.htmlOnly,config);
|
|
205
|
+
if(!dev || !Array.isArray(devRoutes))await writeTypedRoutes(projectRoot,project,config);
|
|
214
206
|
const stage = path.join(project.root, `.prnext-build-${randomUUID()}`);
|
|
215
207
|
const destination = await prepareBuildDirectory(project.root, config.distDir);
|
|
216
208
|
const backup = path.join(project.root, `.prnext-backup-${randomUUID()}`);
|
|
@@ -218,33 +210,35 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
|
|
|
218
210
|
await mkdir(path.join(stage, 'static'), { recursive: true });
|
|
219
211
|
await addBuiltinPagesErrors(project, stage);
|
|
220
212
|
const manifest = { version: 1, dev, buildId, cacheId: randomUUID(), previewModeId: randomUUID().replaceAll('-', ''), previewModeEncryptionKey: randomBytes(32).toString('hex'),
|
|
221
|
-
|
|
213
|
+
...(dev && Array.isArray(devRoutes) ? {devOnDemand:true} : {}), ...(dev && devSession ? {devSequence,devSession} : {}), ...(dev ? {devForceRefresh,devAffectedModules,devIndicators:config.devIndicators,...(devTools?{devTools}:{})} : {}),
|
|
214
|
+
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: [],
|
|
222
215
|
...(project.pagesErrors ? { pagesErrors: project.pagesErrors } : {}) };
|
|
223
216
|
// Its build-unique name is known before bundling and avoids a hash cycle
|
|
224
217
|
// between page entry URLs and the manifest that lists those entries.
|
|
218
|
+
if(!dev)manifest.config.deploymentId=manifest.cacheId;
|
|
225
219
|
const pagesManifestUrl = `${publicPaths.assetBase}/pages-manifest-${manifest.cacheId}.json`;
|
|
226
220
|
if (dev) manifest.devClient = `${publicPaths.assetBase}/dev-manifest-${manifest.cacheId}.json`;
|
|
227
|
-
const development = dev ? { buildId, clientManifest: manifest.devClient } : undefined;
|
|
228
|
-
const refresh = dev ? createRefreshTransform(project.root) : undefined;
|
|
221
|
+
const development = dev ? { buildId, clientManifest: manifest.devClient, onDemand:manifest.devOnDemand===true, sequence:devSequence, session:devSession, indicator:config.devIndicators,...(devTools?{tools:devTools}:{}) } : undefined;
|
|
222
|
+
const refresh = dev ? createRefreshTransform(project.root, {generation:manifest.cacheId,forceGeneration:devForceRefresh,forceFiles:devAffectedModules}) : undefined;
|
|
223
|
+
const locations = dev ? createSourceLocationTransform(project.root) : undefined;
|
|
224
|
+
const reactCompiler = config.reactCompiler ? createReactCompilerTransform(project.root, config.reactCompiler, {dev}) : undefined;
|
|
225
|
+
const serverOnlyFiles = new Set();
|
|
226
|
+
if (reactCompiler) for (const file of [...project.routes.filter(route=>route.kind==='api').map(route=>route.file),...(project.document?[project.document]:[])]) {
|
|
227
|
+
serverOnlyFiles.add(file);serverOnlyFiles.add(await realpath(file));
|
|
228
|
+
}
|
|
229
229
|
const fonts = createFonts({ projectRoot: project.root, stage, assetBase: publicPaths.assetBase });
|
|
230
|
-
const cssModules = createCssModules({ projectRoot: project.root, stage, assetBase: publicPaths.assetBase, sassOptions: config.sassOptions });
|
|
230
|
+
const cssModules = createCssModules({ projectRoot: project.root, stage, assetBase: publicPaths.assetBase, sassOptions: config.sassOptions, cssEngine:config.cssEngine,dev,config });
|
|
231
231
|
configureCompiler({config,root:project.root,stage,buildId,dev,incremental,devInputs,tsconfig,assetBase:publicPaths.assetBase});
|
|
232
232
|
try {
|
|
233
|
-
await
|
|
234
|
-
await mkdir(path.join(stage, 'compat'), { recursive: true });
|
|
235
|
-
for (const name of await readdir(path.join(packageRoot, 'runtime'))) {
|
|
236
|
-
if (name.endsWith('.mjs') && !name.endsWith('.test.mjs')) await cp(path.join(packageRoot, 'runtime', name), path.join(stage, 'runtime', name));
|
|
237
|
-
}
|
|
238
|
-
await cp(path.join(packageRoot, 'runtime/profiles.json'), path.join(stage, 'runtime/profiles.json'));
|
|
233
|
+
await copyRuntimeFiles(stage,{development:dev});
|
|
239
234
|
// Runtime copies must work even when dotenv is nested beneath the PRNext
|
|
240
235
|
// package instead of hoisted beside the application's dependencies.
|
|
241
236
|
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);" } });
|
|
242
|
-
for (const name of await readdir(path.join(packageRoot, 'compat'))) {
|
|
243
|
-
if (name.endsWith('.cjs')) await cp(path.join(packageRoot, 'compat', name), path.join(stage, 'compat', name));
|
|
244
|
-
}
|
|
245
237
|
await prepareImageResponse(stage);
|
|
246
238
|
manifest.config.serverActions = config.experimental.serverActions;
|
|
239
|
+
if(config.workerMemory && !dev)manifest.config.workerMemory=config.workerMemory;
|
|
247
240
|
if (config.workerRetirement) manifest.config.workerRetirement = config.workerRetirement;
|
|
241
|
+
if(config.workerPreload&&!dev)manifest.config.workerPreload=config.workerPreload;
|
|
248
242
|
const cacheHandlers = await prepareCacheHandlers({ config, projectRoot: project.root, stage, environment });
|
|
249
243
|
if (Object.keys(cacheHandlers).length) manifest.config.cacheHandlers = cacheHandlers;
|
|
250
244
|
const cacheHandler = await prepareCacheHandler({ config, projectRoot: project.root, stage, environment });
|
|
@@ -261,7 +255,7 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
|
|
|
261
255
|
serverEntries[route.id] = serverEntry;
|
|
262
256
|
if (route.kind === 'page') {
|
|
263
257
|
const clientEntry = path.join(stage, '.entries', route.id + '.client.jsx');
|
|
264
|
-
await writeFile(clientEntry, browserEntry(route, project.app, pagesManifestUrl, { ...publicPaths, strictMode: config.reactStrictMode === true }, development));
|
|
258
|
+
await writeFile(clientEntry, browserEntry(route, project.app, pagesManifestUrl, { ...publicPaths, deploymentId:dev?undefined:manifest.cacheId, strictMode: config.reactStrictMode === true }, development));
|
|
265
259
|
clientEntries[route.id] = clientEntry;
|
|
266
260
|
}
|
|
267
261
|
manifest.routes.push({ id: route.id, pattern: route.pattern, kind: route.kind, module: `server/${route.id}.mjs`, css: [],
|
|
@@ -270,7 +264,7 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
|
|
|
270
264
|
}
|
|
271
265
|
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 } };
|
|
272
266
|
if (Object.keys(serverEntries).length) {
|
|
273
|
-
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 }), npmPackagesPlugin({ pages: true, projectRoot: project.root, transpilePackages: config.transpilePackages, serverExternalPackages: config.serverExternalPackages }), cssModules.plugin(false)] });
|
|
267
|
+
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)] });
|
|
274
268
|
fonts.record(server.metafile);
|
|
275
269
|
const serverChunks = new Set(Object.keys(server.metafile.outputs).filter(file => file.endsWith('.mjs')).map(file => path.basename(file)));
|
|
276
270
|
for (const [output, metadata] of Object.entries(server.metafile.outputs)) {
|
|
@@ -285,7 +279,7 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
|
|
|
285
279
|
}
|
|
286
280
|
}
|
|
287
281
|
if (Object.keys(clientEntries).length) {
|
|
288
|
-
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 }), cssModules.plugin(true)] });
|
|
282
|
+
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)] });
|
|
289
283
|
fonts.record(browser.metafile);
|
|
290
284
|
const byEntry = new Map(Object.entries(clientEntries).map(([id, file]) => [path.resolve(file), id]));
|
|
291
285
|
for (const [output, metadata] of Object.entries(browser.metafile.outputs)) {
|
|
@@ -302,20 +296,35 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
|
|
|
302
296
|
await compileMiddleware({ project, stage, manifest, dev, moduleResolution: config.turbopack, defineEnvironment: environment });
|
|
303
297
|
if (manifest.middleware) manifest.middleware.bodySizeLimit = config.experimental.proxyClientMaxBodySize;
|
|
304
298
|
await prepareMetadataRoutes(project, stage);
|
|
305
|
-
await compileApp({ project, stage, manifest, moduleResolution: config.turbopack, cssModules, fonts, images, dev, defineEnvironment: environment, productionBrowserSourceMaps: config.productionBrowserSourceMaps, serverExternalPackages: config.serverExternalPackages });
|
|
299
|
+
await compileApp({ project, stage, manifest, reactCompiler: config.reactCompiler, moduleResolution: config.turbopack, cssModules, fonts, images, dev, defineEnvironment: environment, productionBrowserSourceMaps: config.productionBrowserSourceMaps, serverExternalPackages: config.serverExternalPackages });
|
|
300
|
+
if(manifest.config.workerPreload)for(const pattern of config.workerPreload.routes){
|
|
301
|
+
const selected=manifest.routes.find(route=>!route.internal&&route.pattern===pattern);
|
|
302
|
+
if(!selected||selected.cacheConfig?.runtime==='edge'||selected.handlerConfig?.runtime==='edge')throw new Error(`workerPreload route ${pattern} must be an existing Node route.`);
|
|
303
|
+
}
|
|
304
|
+
await compileInstrumentation({root:project.root,stage,manifest,config,dev,environment});
|
|
305
|
+
if(deferredRoutes.length){
|
|
306
|
+
for(const route of deferredRoutes){
|
|
307
|
+
const module=`server/dev-deferred-${route.id}.mjs`;await writeFile(path.join(stage,module),'export {};\n');
|
|
308
|
+
manifest.routes.push({id:route.id,pattern:route.pattern,kind:route.kind,router:route.router,module,devDeferred:true,css:[],cacheConfig:{maxDuration:120}});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
306
311
|
await checkTypeScript(project.root, config, { dev, tsconfig });
|
|
307
312
|
fonts.attach(manifest);
|
|
308
313
|
expandLocales(manifest);
|
|
309
314
|
// All runtime preparation must finish before its shared modules are built;
|
|
310
315
|
// no staged renderer has been imported before this production-only point.
|
|
311
|
-
if (!dev) await bundleRuntime(stage);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
316
|
+
if (!dev) await bundleRuntime(stage,{projectRoot:project.root,sourceDirectory:path.join(packageRoot,'runtime')});
|
|
317
|
+
// Node caches CJS React by filename, not NODE_ENV. Once development metadata
|
|
318
|
+
// imports load it, subsequent production renders need a clean process too.
|
|
319
|
+
if(dev) developmentModulesLoaded = true;
|
|
320
|
+
if(!dev) Object.assign(manifest,await (incremental || developmentModulesLoaded ? isolatedPrerender : prerenderProduction)(project.root,stage,manifest,{prerender:config.prerender}));
|
|
321
|
+
else {await prerender(stage, manifest, dev);await prerenderApp(stage, manifest, dev);}
|
|
322
|
+
const pages = manifest.routes.filter(route => route.kind === 'page' && route.router !== 'app' && !route.internal && !route.devDeferred);
|
|
315
323
|
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 });
|
|
316
324
|
if (pages.length || manifest.pagesErrors) {
|
|
317
325
|
const publicPages = {
|
|
318
326
|
buildId: manifest.buildId,
|
|
327
|
+
...(manifest.config.deploymentId?{deploymentId:manifest.config.deploymentId}:{}),
|
|
319
328
|
...publicPaths,
|
|
320
329
|
// Header rules cannot select a different page. Other server routing
|
|
321
330
|
// stays authoritative, including middleware with conditional behavior.
|
|
@@ -332,16 +341,35 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
|
|
|
332
341
|
}
|
|
333
342
|
await rm(path.join(stage, '.entries'), { recursive: true });
|
|
334
343
|
if (dev) {
|
|
344
|
+
manifest.devStyles={bundles:devStylePlans(),dependencies:devStyleDependencies(),modules:await cssModules.mappings()};
|
|
345
|
+
for(const [,files]of manifest.devStyles.dependencies)for(const module of manifest.devStyles.modules)if(files.includes(module.file))for(const file of module.watchFiles)if(!files.includes(file))files.push(file);
|
|
346
|
+
manifest.devDependencies=devDependencies(project,manifest);
|
|
347
|
+
manifest.devModules=devModules(project.root);
|
|
348
|
+
manifest.devDependencyStatus={complete:!manifest.devDependencies.incomplete&&!manifest.devModules.incomplete,reason:manifest.devDependencies.incomplete||manifest.devModules.incomplete};
|
|
349
|
+
for(const route of manifest.devDependencies)for(const module of manifest.devStyles.modules){
|
|
350
|
+
if(route.files.includes(path.relative(project.root,module.file).replaceAll(path.sep,'/')))for(const file of module.watchFiles){
|
|
351
|
+
const relative=path.relative(project.root,file).replaceAll(path.sep,'/');if(!relative.startsWith('../')&&!route.files.includes(relative))route.files.push(relative);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
335
354
|
const appRoute = manifest.routes.find(route => route.router === 'app' && route.kind === 'page' && route.client);
|
|
336
355
|
await writeFile(path.join(stage, 'assets', path.basename(manifest.devClient)), JSON.stringify({ buildId,
|
|
337
356
|
pages: manifest.routes.filter(route => route.router !== 'app' && route.client).map(publicPage),
|
|
338
|
-
...(appRoute ? { app: { client: appRoute.client, css: appRoute.css } } : {}),
|
|
357
|
+
...(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||[]})) } } : {}),
|
|
339
358
|
}));
|
|
340
359
|
}
|
|
341
|
-
|
|
360
|
+
// A report belongs to this successful staging generation, never to the
|
|
361
|
+
// runtime manifest or public assets. Publishing the stage is atomic below.
|
|
362
|
+
if(!dev)await retainDeploymentAssets({stage,destination,manifest,policy:config.deploymentRetention});
|
|
363
|
+
const compilationCache=compilationGraphCacheReport();
|
|
364
|
+
if(!dev)await writeFile(path.join(stage,'compilation-cache-report.json'),JSON.stringify(compilationCache,null,2)+'\n');
|
|
365
|
+
const compilerReport=await writeReactCompilerReport(stage,reactCompilerReport());
|
|
366
|
+
await writeRuntimeContract(stage,{deployment:!dev,htmlOnly:!dev&&manifest.routes.some(route=>route.htmlOnly),workerMemory:Boolean(manifest.config.workerMemory),incrementalCache:Boolean(manifest.config.cacheHandler)});
|
|
342
367
|
await writeFile(path.join(stage, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
343
368
|
await writeBuildOutput(stage, manifest);
|
|
344
|
-
|
|
369
|
+
// Offline compression is a deployment optimization. Recompressing source
|
|
370
|
+
// maps and every entry after a save stalls Fast Refresh for no local benefit.
|
|
371
|
+
if (!dev && config.compress) await precompressBuild(stage, manifest, {cacheDirectory:path.join(project.root,'.prnext-cache/precompress-v1')});
|
|
372
|
+
if(!dev)await writeBuildAnalysis({root:project.root,stage,destination,manifest,records:buildAnalysisRecords(),routeInputs:buildAnalysisRoutes(),precompressed:config.compress});
|
|
345
373
|
if (config.output === 'standalone' && !dev) await (await import('./standalone-runner.mjs')).prepareStandalone({ projectRoot: project.root, stage, manifest, config });
|
|
346
374
|
const exporting = config.output === 'export' && !dev ? await import('./static-export.mjs') : null;
|
|
347
375
|
const exportSource = exporting && await exporting.prepareStaticExport({ project, stage, manifest, config });
|
|
@@ -365,7 +393,7 @@ async function buildProject(projectRoot, { dev = false, envMode, incremental = f
|
|
|
365
393
|
} finally { await rm(pointerTemporary, {force:true}); }
|
|
366
394
|
await exported?.commit();
|
|
367
395
|
if (hadDestination) await rm(backup, { recursive: true, force: true });
|
|
368
|
-
return { ...manifest, outputDirectory: destination, durationMs: Math.round(performance.now() - started) };
|
|
396
|
+
return { ...manifest, outputDirectory: destination, durationMs: Math.round(performance.now() - started),reactCompilerReport:compilerReport,compilationCache };
|
|
369
397
|
} catch (error) {
|
|
370
398
|
await rm(stage, { recursive: true, force: true });
|
|
371
399
|
if (error.errors) error.message = error.errors.map(item => `${item.location ? `${item.location.file}:${item.location.line}: ` : ''}${item.text}`).join('\n');
|
package/build/inspect.mjs
CHANGED
|
@@ -69,6 +69,7 @@ export async function inspectProject(directory) {
|
|
|
69
69
|
sample: { events: recent.length, from: recent[0]?.time, to: recent.at(-1)?.time },
|
|
70
70
|
notes: ['Enable measurements with prn start --inspect (or prn dev --inspect), visit routes, then run prn inspect again.',
|
|
71
71
|
'HTTP and fetch durations end when response headers are ready. Samples are bounded and can span server sessions; they are not full-response benchmarks.',
|
|
72
|
+
'Request-stage samples additionally separate admission, worker first byte, response headers and complete body consumption, with completion/cancellation/deadline outcomes. Stages overlap; do not add their durations. Body completion means the HTTP layer consumed the application body, not remote delivery.',
|
|
72
73
|
'Fetch destinations omit queries and credentials. Runtime cache tags/paths are hashed; matching identifiers link reads to invalidations. No diagnostic HTTP endpoint is exposed.'] };
|
|
73
74
|
}
|
|
74
75
|
export function printInspection(report) {
|
|
@@ -76,6 +77,8 @@ export function printInspection(report) {
|
|
|
76
77
|
console.log(`Recorded sample: ${report.summary.requests} HTTP requests; p50 ${report.summary.p50Ms ?? 'n/a'} ms; p95 ${report.summary.p95Ms ?? 'n/a'} ms (response headers); ${report.summary.errors} server errors.`);
|
|
77
78
|
if(report.summary.wakeP95Ms !== null) console.log(`Worker startup p95: ${report.summary.wakeP95Ms} ms.`);
|
|
78
79
|
if(report.summary.workerRssBytes !== null) console.log(`Latest recorded worker RSS: ${(report.summary.workerRssBytes/1048576).toFixed(1)} MiB. Native server memory is not included.`);
|
|
80
|
+
for(const stage of report.requestStages||[]) console.log(`STAGE ${stage.stage}: ${stage.samples} sample(s), p95 ${stage.p95Ms ?? 'n/a'} ms; ${stage.completed} completed, ${stage.failed} failed, ${stage.cancelled} cancelled, ${stage.timedOut} timed out.`);
|
|
81
|
+
for(const budget of report.budgets||[])console.log(`BUDGET ${budget.budget}: ${budget.used ?? 'n/a'}/${budget.limit ?? 'n/a'} ${budget.unit}, ${budget.waiting ?? 'n/a'} waiting; ${budget.rejected ?? 'n/a'} rejected, ${budget.timedOut ?? 'n/a'} timed out, ${budget.cancelledWaits ?? 'n/a'} cancelled waits.`);
|
|
79
82
|
for(const finding of report.findings)console.log(`${finding.level.toUpperCase()}: ${finding.message} ${finding.action}`);
|
|
80
83
|
for (const route of report.routes) console.log(`${route.mode.padEnd(13)} ${route.pattern}\n ${route.reasons.join('; ') || `${route.prerenderedPaths} prerendered path(s); revalidate: ${route.revalidate.join(', ') || 'on demand'}`}`);
|
|
81
84
|
for (const item of report.measurements) console.log(`${item.type.toUpperCase()} ${item.route}${item.destination ? ' -> ' + item.destination : ''}: ${item.count} sample(s)${item.state ? ', ' + item.state : ''}${item.reason ? ' (' + item.reason + ')' : ''}${item.p95Ms !== undefined ? ', p95 ' + item.p95Ms.toFixed(2) + ' ms' : ''}, ${item.errors} error(s)`);
|
|
@@ -1,22 +1,46 @@
|
|
|
1
1
|
const quantile = (values, q) => values.length ? Math.round([...values].sort((a,b)=>a-b)[Math.ceil(values.length*q)-1]*100)/100 : null;
|
|
2
2
|
const durations = events => events.map(e=>e.durationMs).filter(v=>Number.isFinite(v)&&v>=0);
|
|
3
|
+
const stages=['admission','renderStart','bodyAdmission','bodyRead','workerQueue','workerFirstByte','workerHeaders','httpHeaders','responseBody','requestTotal'];
|
|
4
|
+
const outcomes=['completed','failed','cancelled','timedOut'];
|
|
5
|
+
const budgetNames=['renderLive','apiLive','renderStart','inputBytes','responseBytes'];
|
|
6
|
+
const validTime=value=>Number.isSafeInteger(value)&&value>=0&&value<=18446744073709;
|
|
7
|
+
function summarizeStages(events) {
|
|
8
|
+
return stages.flatMap(stage=>{
|
|
9
|
+
const sample=events.filter(event=>event.stage===stage), times=durations(sample);
|
|
10
|
+
return sample.length?[{stage,samples:sample.length,p50Ms:quantile(times,.5),p95Ms:quantile(times,.95),...Object.fromEntries(outcomes.map(outcome=>[outcome,sample.filter(event=>event.outcome===outcome).length]))}]:[];
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function currentBudgets(snapshot) {
|
|
14
|
+
if(!Array.isArray(snapshot?.workers?.budgets))return null;
|
|
15
|
+
return budgetNames.flatMap(budget=>{
|
|
16
|
+
const value=snapshot.workers.budgets.find(value=>value?.budget===budget);
|
|
17
|
+
if(!value)return [];
|
|
18
|
+
const row={budget,unit:budget.endsWith('Bytes')?'bytes':'requests'};
|
|
19
|
+
for(const key of ['limit','used','peakUsed','waiting','peakWaiting','acquired','rejected','timedOut','cancelledWaits'])row[key]=Number.isSafeInteger(value[key])&&value[key]>=0?value[key]:null;
|
|
20
|
+
return [row];
|
|
21
|
+
});
|
|
22
|
+
}
|
|
3
23
|
export function summarizeDiagnostics(events, snapshots = []) {
|
|
4
24
|
const http=events.filter(e=>e.type==='http'), times=durations(http);
|
|
5
25
|
const wake=durations(events.filter(e=>e.type==='wake'&&e.phase==='node-ready'));
|
|
6
26
|
const processes=new Map();
|
|
7
27
|
for(const event of events)if(event.type==='process'&&Number.isSafeInteger(event.pid)&&Number.isFinite(event.rssBytes)&&event.rssBytes>=0)processes.set(event.pid,event);
|
|
8
|
-
const last=snapshots.sort((a,b)=>b.unixMs-a.unixMs)[0];
|
|
28
|
+
const last=[...snapshots].sort((a,b)=>b.unixMs-a.unixMs)[0];
|
|
9
29
|
const current=last&&Date.now()-last.unixMs<30000&&Date.now()>=last.unixMs;
|
|
10
30
|
const rss=[...processes.values()].filter(e=>Date.now()-e.time<30000&&Date.now()>=e.time);
|
|
31
|
+
const stageSample=events.filter(e=>e.type==='requestStage'&&stages.includes(e.stage)&&outcomes.includes(e.outcome)&&Number.isFinite(e.durationMs)&&e.durationMs>=0&&e.durationMs<=86400000&&validTime(e.time)).map(({stage,outcome,durationMs,time})=>({stage,outcome,durationMs,time}));
|
|
32
|
+
const requestStages=summarizeStages(stageSample), totals=requestStages.find(s=>s.stage==='requestTotal');
|
|
11
33
|
const summary={requests:http.length,errors:http.filter(e=>e.status>=500).length,p50Ms:quantile(times,.5),p95Ms:quantile(times,.95),wakeP95Ms:quantile(wake,.95),workerRssBytes:rss.length?rss.reduce((n,e)=>n+e.rssBytes,0):null,
|
|
12
|
-
queuedJobs:current?last.workers
|
|
34
|
+
queuedJobs:current?last.workers?.queuedJobs??null:null,activeJobs:current?last.workers?.activeJobs??null:null, runtimeSampleAt:last?.unixMs??null,
|
|
35
|
+
completedResponseP95Ms:quantile(durations(stageSample.filter(e=>e.stage==='requestTotal'&&e.outcome==='completed')),.95),cancelledResponses:totals?.cancelled??null,timedOutResponses:totals?.timedOut??null};
|
|
13
36
|
const findings=[];
|
|
14
37
|
if(!http.length)findings.push({level:'info',code:'NO_SAMPLE',message:'No HTTP sample recorded.',action:'Start with --inspect, visit the site and run inspect again.'});
|
|
15
38
|
if(summary.queuedJobs>0)findings.push({level:'warning',code:'WORKER_QUEUE',message:`${summary.queuedJobs} jobs are waiting for a worker.`,action:'Check slow routes and available CPU/RAM before increasing concurrency.'});
|
|
16
39
|
if(summary.p95Ms>=1000)findings.push({level:'warning',code:'SLOW_HEADERS',message:'The slowest 5% of recorded responses take at least one second to prepare headers.',action:'Inspect route and fetch timings below; this includes queueing and startup.'});
|
|
17
40
|
if(summary.errors)findings.push({level:'warning',code:'SERVER_ERRORS',message:`${summary.errors} recorded requests returned a server error.`,action:'Check application logs for the same sample period.'});
|
|
18
41
|
if(summary.wakeP95Ms>=1000)findings.push({level:'info',code:'SLOW_WAKE',message:'Recorded worker startup p95 exceeds one second.',action:'Review startup imports or use keep-warm if the extra memory is acceptable.'});
|
|
19
|
-
|
|
42
|
+
if(summary.timedOutResponses>0)findings.push({level:'warning',code:'RESPONSE_DEADLINES',message:`${summary.timedOutResponses} recorded HTTP responses reached their request deadline.`,action:'Compare admission, worker and body timings before increasing the configured timeout.'});
|
|
43
|
+
return {summary,findings,requestStages,budgets:current?currentBudgets(last):null,stageSample,metricSample:http.filter(e=>Number.isFinite(e.durationMs)&&e.durationMs>=0&&e.durationMs<=86400000&&validTime(e.time)).map(e=>({route:typeof e.route==='string'?e.route.slice(0,256):'<unmatched>',method:/^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS|CONNECT|TRACE)$/.test(e.method)?e.method:'_OTHER',durationMs:e.durationMs,time:e.time}))};
|
|
20
44
|
}
|
|
21
45
|
|
|
22
46
|
/** OTLP/JSON Metrics export of a bounded local sample, never a complete traffic counter. */
|
|
@@ -33,5 +57,14 @@ export function inspectionMetrics(report) {
|
|
|
33
57
|
return {attributes:[{key:'http.route',value:{stringValue:group.route}},{key:'http.request.method',value:{stringValue:group.method}}],
|
|
34
58
|
startTimeUnixNano:String(BigInt(Math.trunc(group.start))*1000000n),timeUnixNano:String(BigInt(Math.trunc(group.end))*1000000n),count:String(group.values.length),sum:group.values.reduce((a,b)=>a+b,0),min:Math.min(...group.values),max:Math.max(...group.values),explicitBounds:bounds,bucketCounts:buckets.map(String)};
|
|
35
59
|
});
|
|
36
|
-
|
|
60
|
+
const stageGroups=new Map();
|
|
61
|
+
for(const event of report.stageSample||[]) {
|
|
62
|
+
if(!stages.includes(event.stage)||!outcomes.includes(event.outcome)||!Number.isFinite(event.durationMs)||event.durationMs<0||event.durationMs>86400000||!validTime(event.time))continue;
|
|
63
|
+
const key=JSON.stringify([event.stage,event.outcome]);
|
|
64
|
+
if(!stageGroups.has(key))stageGroups.set(key,{stage:event.stage,outcome:event.outcome,count:0,sum:0,min:Infinity,max:0,start:event.time,end:event.time,buckets:Array(bounds.length+1).fill(0)});
|
|
65
|
+
const group=stageGroups.get(key),value=event.durationMs/1000,index=bounds.findIndex(bound=>value<=bound);
|
|
66
|
+
group.count++;group.sum+=value;group.min=Math.min(group.min,value);group.max=Math.max(group.max,value);group.start=Math.min(group.start,event.time);group.end=Math.max(group.end,event.time);group.buckets[index<0?bounds.length:index]++;
|
|
67
|
+
}
|
|
68
|
+
const stagePoints=[...stageGroups.values()].map(g=>({attributes:[{key:'prnext.request.stage',value:{stringValue:g.stage}},{key:'prnext.request.outcome',value:{stringValue:g.outcome}}],startTimeUnixNano:String(BigInt(g.start)*1000000n),timeUnixNano:String(BigInt(g.end)*1000000n),count:String(g.count),sum:g.sum,min:g.min,max:g.max,explicitBounds:bounds,bucketCounts:g.buckets.map(String)}));
|
|
69
|
+
return {resourceMetrics:[{resource:{attributes:[{key:'service.name',value:{stringValue:'prnext'}},{key:'prnext.inspect.sampled',value:{boolValue:true}}]},scopeMetrics:[{scope:{name:'prnext.inspect',version:'1'},metrics:[{name:'prnext.http.response.headers.duration',description:'Bounded diagnostic sample of time until response headers; excludes response body streaming.',unit:'s',histogram:{aggregationTemporality:1,dataPoints}},{name:'prnext.request.stage.duration',description:'Bounded diagnostic sample of request stages and terminal outcomes; stages overlap and must not be added together.',unit:'s',histogram:{aggregationTemporality:1,dataPoints:stagePoints}}]}]}]};
|
|
37
70
|
}
|
|
@@ -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
|
+
}
|