@thomas.f/prnext 0.1.15-alpha → 0.1.17-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 +387 -12
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +5 -0
- package/build/analyze.mjs +184 -0
- package/build/app-config.mjs +7 -8
- package/build/app-static.mjs +12 -7
- package/build/app.mjs +23 -10
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +20 -5
- package/build/check.mjs +56 -11
- package/build/compiler.mjs +25 -1
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +21 -13
- package/build/css-modules.mjs +15 -4
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +83 -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 +55 -4
- package/build/edge.mjs +3 -2
- package/build/framework-imports.mjs +40 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +96 -101
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +2 -3
- package/build/migrate.mjs +60 -57
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +97 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-pages.mjs +90 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +80 -0
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +15 -3
- package/build/transform-cache.mjs +37 -4
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +91 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +22 -0
- package/build/typescript-watch.mjs +22 -0
- package/build/typescript.mjs +5 -4
- package/build/webpack-graph.mjs +164 -27
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +105 -121
- package/compat/index.d.ts +21 -7
- package/compat/link.d.cts +4 -3
- package/compat/navigation.d.cts +4 -3
- package/compat/router.d.cts +4 -3
- package/compat/typed-route.d.cts +17 -0
- package/docs/ai/compatibility.md +106 -0
- package/docs/ai/data.md +128 -0
- package/docs/ai/deployment.md +111 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +146 -0
- package/docs/ai/routing.md +132 -0
- package/docs/ai/troubleshooting.md +120 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +28 -10
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +6 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +17 -6
- package/runtime/app-render.mjs +12 -0
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -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 +239 -45
- 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 +186 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +18 -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 +307 -0
- package/runtime/env.mjs +2 -1
- package/runtime/pages-client.mjs +10 -1
- package/runtime/pages-navigation.mjs +6 -1
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/preload.mjs +12 -0
- package/runtime/rsc-worker.mjs +7 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +12 -5
- package/runtime/worker.mjs +36 -2
package/build/check.mjs
CHANGED
|
@@ -7,34 +7,79 @@ import { scanProject } from './scan.mjs';
|
|
|
7
7
|
import { compileCustomRoutes } from './custom-routes.mjs';
|
|
8
8
|
import { validateReactVersions } from './app.mjs';
|
|
9
9
|
import { snapshotEnvConfig } from '../runtime/env.mjs';
|
|
10
|
+
import { environmentDiagnostics } from '../native/diagnostics.mjs';
|
|
11
|
+
import { packageManifest } from '../native/resolve.mjs';
|
|
12
|
+
import { projectTsconfig } from './typescript.mjs';
|
|
10
13
|
|
|
11
14
|
/** Preflight an existing project without rewriting sources, dependencies or build output. */
|
|
12
|
-
export async function checkProject(directory, { dev = false, validateDependencies = true } = {}) {
|
|
15
|
+
export async function checkProject(directory, { dev = false, validateDependencies = true, validateEnvironment = validateDependencies } = {}, services = {}) {
|
|
13
16
|
const root = path.resolve(directory);
|
|
14
|
-
const report = { root, ok: false, routes: [], versions: {}, errors: [], notes: [
|
|
17
|
+
const report = { root, ok: false, routes: [], versions: {}, errors: [], diagnostics: [], notes: [
|
|
15
18
|
'Preflight validates configuration, route conventions and installed React versions. A successful production build and browser tests are still required.',
|
|
16
19
|
'Webpack plugin hooks are validated during compilation; preflight only validates plugin shape and translated configuration.',
|
|
17
20
|
'Rust handles HTTP, routing, static files, images and persistent caches. React and npm JavaScript execute on Node/V8.',
|
|
18
21
|
] };
|
|
22
|
+
const diagnostic = (id, error, action) => {
|
|
23
|
+
const item = { id, severity: 'error', message: error.message, action };
|
|
24
|
+
report.diagnostics.push(item);
|
|
25
|
+
report.errors.push(item.message);
|
|
26
|
+
};
|
|
19
27
|
const restore = snapshotEnvConfig();
|
|
20
28
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
let config, project;
|
|
30
|
+
try { config = await loadProjectConfig(root, {dev}); }
|
|
31
|
+
catch (error) { diagnostic('config', error, 'Correct the project configuration and run check again.'); }
|
|
32
|
+
if (config) {
|
|
33
|
+
if (config.onDemandEntries) report.notes.push('Development compiles requested routes and retains open browser routes. onDemandEntries controls inactive route retention; middleware, rewrites and parallel/intercepting routing use the complete graph.');
|
|
34
|
+
try { await compileCustomRoutes(config); }
|
|
35
|
+
catch (error) { diagnostic('custom-routes', error, 'Correct redirects, headers or rewrites in the configuration.'); }
|
|
36
|
+
try { project = await scanProject(root, {basePath:config.basePath,pageExtensions:config.pageExtensions,i18n:config.i18n}); }
|
|
37
|
+
catch (error) { diagnostic('routes', error, 'Correct the conflicting route files or route conventions.'); }
|
|
38
|
+
try { await validateCompilerConfig(config,root,{dev,edge:project?.middleware?.runtime==='edge'||project?.routes.some(route=>route.cacheConfig?.runtime==='edge'||route.handlerConfig?.runtime==='edge')}); }
|
|
39
|
+
catch (error) { diagnostic('compiler', error, 'Correct the compiler options or install the required compiler plugins.'); }
|
|
40
|
+
try {
|
|
41
|
+
const tsconfig = await projectTsconfig(root, config);
|
|
42
|
+
if (tsconfig) {
|
|
43
|
+
let ts;
|
|
44
|
+
try { ts = createRequire(path.join(root, 'package.json'))('typescript'); }
|
|
45
|
+
catch {
|
|
46
|
+
const required = config.typedRoutes || config.typescript && !config.typescript.ignoreBuildErrors;
|
|
47
|
+
const item = { id: 'typescript', severity: required ? 'error' : 'warning', message: 'TypeScript configuration exists, but TypeScript is not installed in the application.', action: 'Install typescript as an application dev dependency.' };
|
|
48
|
+
report.diagnostics.push(item); if (required) report.errors.push(item.message);
|
|
49
|
+
}
|
|
50
|
+
if (ts) {
|
|
51
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(tsconfig, { noEmit: true }, { ...ts.sys, onUnRecoverableConfigFileDiagnostic: value => { throw new Error(ts.flattenDiagnosticMessageText(value.messageText, '\n')); } });
|
|
52
|
+
if (!parsed || parsed.errors.length) throw new Error(parsed ? ts.formatDiagnostics(parsed.errors, { getCanonicalFileName: name => name, getCurrentDirectory: () => root, getNewLine: () => '\n' }) : 'TypeScript configuration could not be parsed.');
|
|
53
|
+
report.versions.typescript = ts.version;
|
|
54
|
+
report.diagnostics.push({ id: 'typescript', severity: 'info', message: `TypeScript ${ts.version} configuration is valid. Semantic errors require prnext typecheck.` });
|
|
55
|
+
}
|
|
56
|
+
} else if (config.typedRoutes || config.typescript && !config.typescript.ignoreBuildErrors) throw new Error('TypeScript is enabled, but tsconfig.json does not exist.');
|
|
57
|
+
} catch (error) { diagnostic('typescript', error, 'Correct typescript.tsconfigPath or the selected TypeScript configuration.'); }
|
|
58
|
+
}
|
|
59
|
+
if (project) {
|
|
26
60
|
report.routes = project.routes.filter(route=>!route.internal).map(({pattern,kind,router})=>({pattern,kind,router:router||'pages'}));
|
|
61
|
+
}
|
|
27
62
|
const resolve = createRequire(path.join(root,'package.json'));
|
|
28
63
|
for (const name of ['next','react','react-dom','react-server-dom-webpack']) {
|
|
29
64
|
try { report.versions[name] = JSON.parse(await readFile(resolve.resolve(`${name}/package.json`),'utf8')).version; }
|
|
30
65
|
catch { report.versions[name] = null; }
|
|
31
66
|
}
|
|
32
67
|
if (validateDependencies) {
|
|
33
|
-
|
|
34
|
-
|
|
68
|
+
try {
|
|
69
|
+
if (project?.routes.some(route=>route.router==='app')) await validateReactVersions(resolve);
|
|
70
|
+
else {
|
|
71
|
+
const names = ['react', 'react-dom'];
|
|
72
|
+
if (names.some(name => report.versions[name] !== packageManifest.peerDependencies[name])) throw new Error(`Pages routes require the declared React peer versions. Resolved ${names.map(name => `${name}@${report.versions[name] || 'missing'}`).join(', ')}. Install ${names.map(name => `${name}@${packageManifest.peerDependencies[name]}`).join(' ')} in the application.`);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) { diagnostic('react', error, 'Install the matching React protocol packages shown in this error.'); }
|
|
35
75
|
} else report.notes.push('Installed React versions have not been validated yet; migration checks them after dependency installation.');
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
if (validateEnvironment) {
|
|
77
|
+
const diagnostics = await (services.environmentDiagnostics || environmentDiagnostics)();
|
|
78
|
+
report.diagnostics.push(...diagnostics);
|
|
79
|
+
report.errors.push(...diagnostics.filter(item => item.severity === 'error').map(item => item.message));
|
|
80
|
+
}
|
|
81
|
+
report.ok = report.errors.length === 0;
|
|
82
|
+
} catch (error) { diagnostic('preflight', error, 'Resolve the reported file or installation error and retry.'); }
|
|
38
83
|
finally { restore(); }
|
|
39
84
|
return report;
|
|
40
85
|
}
|
package/build/compiler.mjs
CHANGED
|
@@ -13,11 +13,28 @@ import { webpackGraph } from './webpack-graph.mjs';
|
|
|
13
13
|
import { turbopackRules, sourceReader } from './turbopack-conditions.mjs';
|
|
14
14
|
import {withSourceMaps,configureSourceMaps,inlineSourceMap} from './source-maps.mjs';
|
|
15
15
|
import { optimizeImportsPlugins } from './optimize-imports.mjs';
|
|
16
|
+
import {recordBuildAnalysis} from './analyze.mjs';
|
|
17
|
+
import {recordDevDependencies,developmentDependencies,developmentStylePlans,developmentStyleDependencies,developmentModules} from './dev-dependencies.mjs';
|
|
16
18
|
|
|
17
19
|
let validateOptions;
|
|
18
20
|
const compilation = new AsyncLocalStorage();
|
|
19
21
|
export const withCompilation = callback => withSourceMaps(()=>compilation.run({ targets: new Map(), loaded: new Map() }, callback));
|
|
20
22
|
export function configureCompiler(options) { Object.assign(compilation.getStore() || {}, options);configureSourceMaps(options.dev || options.config?.productionBrowserSourceMaps); }
|
|
23
|
+
export const buildAnalysisRecords=()=>compilation.getStore()?.analysis;
|
|
24
|
+
export const buildAnalysisRoutes=()=>compilation.getStore()?.analysisRoutes;
|
|
25
|
+
export const devDependencies=(project,manifest)=>developmentDependencies(compilation.getStore(),project,manifest);
|
|
26
|
+
export const devStylePlans=()=>developmentStylePlans(compilation.getStore());
|
|
27
|
+
export const devStyleDependencies=()=>developmentStyleDependencies(compilation.getStore());
|
|
28
|
+
export const devModules=root=>developmentModules(compilation.getStore(),root);
|
|
29
|
+
export async function readCompilerFile(file,override){
|
|
30
|
+
if(override!==undefined)return override;
|
|
31
|
+
const state=compilation.getStore();if(!state)return readFile(file,'utf8');
|
|
32
|
+
state.sourceFiles||=new Map();
|
|
33
|
+
if(state.sourceFiles.has(file))return state.sourceFiles.get(file);
|
|
34
|
+
const pending=readFile(file,'utf8').then(source=>{state.devInputs?.record(file,source);state.sourceBytes=(state.sourceBytes||0)+source.length*2;if(state.sourceBytes>8*1024*1024)state.sourceFiles.delete(file);return source;});
|
|
35
|
+
if(state.sourceFiles.size<512)state.sourceFiles.set(file,pending);
|
|
36
|
+
return pending;
|
|
37
|
+
}
|
|
21
38
|
export async function compilerSource(file, fallback) {
|
|
22
39
|
return compilation.getStore()?.loaded.get(file)?.source ?? fallback ?? await readFile(file, 'utf8');
|
|
23
40
|
}
|
|
@@ -120,6 +137,13 @@ export async function validateCompilerConfig(config, root, {dev=false,edge=false
|
|
|
120
137
|
for (const target of ['node','browser',...(edge?['edge']:[])]) await targetConfig(state,target);
|
|
121
138
|
}
|
|
122
139
|
export async function build(options) {
|
|
140
|
+
const state=compilation.getStore();
|
|
141
|
+
const result=await compileGraph(options);
|
|
142
|
+
recordBuildAnalysis(state,options,result);
|
|
143
|
+
recordDevDependencies(state,options,result);
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
async function compileGraph(options) {
|
|
123
147
|
const state = compilation.getStore();
|
|
124
148
|
if ((options.plugins || []).some(plugin => framework.test(plugin.name))) options = { ...options, plugins: optimizeImportsPlugins(options.plugins, state?.config?.experimental?.optimizePackageImports) };
|
|
125
149
|
// esbuild's shared service dispatches callbacks from its own async context.
|
|
@@ -137,7 +161,7 @@ export async function build(options) {
|
|
|
137
161
|
// Runtime vendor bundles are framework-owned, not project webpack targets.
|
|
138
162
|
const projectGraph = (options.plugins || []).some(plugin => framework.test(plugin.name));
|
|
139
163
|
if (projectGraph && state?.tsconfig) options = { ...options, tsconfig: state.tsconfig };
|
|
140
|
-
const compile = input => state?.incremental &&
|
|
164
|
+
const compile = input => state?.incremental && (projectGraph || !input.plugins?.length) ? incrementalBuild(input,{root:state.root,stage:state.stage,group:projectGraph?'project':'auxiliary',run:callback=>compilation.run(state,callback)}) : esbuild(input);
|
|
141
165
|
if (!(state?.config?.webpack || Object.keys(state?.config?.turbopack?.rules || {}).length) || !projectGraph) return compile(options);
|
|
142
166
|
const target = options.define?.['process.env.NEXT_RUNTIME'] === '"edge"' ? 'edge' : options.platform === 'browser' ? 'browser' : 'node';
|
|
143
167
|
const config = await targetConfig(state,target);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {availableParallelism} from 'node:os';
|
|
2
|
+
|
|
3
|
+
// A shared finite worker set, with deterministic results and full settlement
|
|
4
|
+
// before a caller discards its stage. No new work starts after a failure.
|
|
5
|
+
export async function mapConcurrent(items,run,limit=Math.min(4,availableParallelism())) {
|
|
6
|
+
if(!Number.isInteger(limit)||limit<1||limit>32)throw new RangeError('Invalid build concurrency');
|
|
7
|
+
const values=Array.from(items),results=new Array(values.length);let next=0,failure,failed=false;
|
|
8
|
+
await Promise.all(Array.from({length:Math.min(limit,values.length)},async()=>{
|
|
9
|
+
while(!failed&&next<values.length){const index=next++;try{results[index]=await run(values[index],index);}catch(error){if(!failed){failed=true;failure=error;}}}
|
|
10
|
+
}));
|
|
11
|
+
if(failed)throw failure;return results;
|
|
12
|
+
}
|
package/build/config.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { validateI18n } from './i18n.mjs';
|
|
|
17
17
|
import { frameworkPackages } from './framework-imports.mjs';
|
|
18
18
|
import { validateTypeScript } from './typescript.mjs';
|
|
19
19
|
import { validateWorkerRetirement } from './worker-retirement.mjs';
|
|
20
|
+
import {validateWorkerPreload} from './worker-preload.mjs';
|
|
20
21
|
import { validateOptimizePackageImports } from './optimize-imports.mjs';
|
|
21
22
|
import { validateProxyBodySize } from './proxy-body-config.mjs';
|
|
22
23
|
|
|
@@ -26,6 +27,8 @@ const supported = new Set(['env', 'redirects', 'headers', 'rewrites', 'generateB
|
|
|
26
27
|
const defaults = { distDir: '.prnext', env: {}, compress: true, poweredByHeader: true, productionBrowserSourceMaps: false, basePath: '', assetPrefix: '', trailingSlash: false, skipTrailingSlashRedirect: false, skipMiddlewareUrlNormalize: false, experimental: { nextScriptWorkers: false } };
|
|
27
28
|
supported.add('cacheHandler');
|
|
28
29
|
supported.add('workerRetirement');
|
|
30
|
+
supported.add('workerPreload');
|
|
31
|
+
supported.add('cssEngine');
|
|
29
32
|
supported.add('cacheMaxMemorySize');
|
|
30
33
|
supported.add('turbopack');
|
|
31
34
|
supported.add('i18n');
|
|
@@ -47,7 +50,7 @@ export function publicAssetBase(config) {
|
|
|
47
50
|
return (config.assetPrefix || config.basePath || '').replace(/\/+$/, '') + '/_prnext/assets';
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
function configPlugin() {
|
|
53
|
+
function configPlugin(devInputs) {
|
|
51
54
|
return {
|
|
52
55
|
name: 'prnext-project-config',
|
|
53
56
|
setup(build) {
|
|
@@ -56,20 +59,21 @@ function configPlugin() {
|
|
|
56
59
|
if (filename.includes(`${path.sep}node_modules${path.sep}`)) return;
|
|
57
60
|
// Preserve filesystem paths after bundling config's relative helpers.
|
|
58
61
|
const prefix = `const __prnext_config_url=${JSON.stringify(pathToFileURL(filename).href)};const __prnext_config_file=${JSON.stringify(filename)};const __prnext_config_dir=${JSON.stringify(path.dirname(filename))};\n`;
|
|
59
|
-
const
|
|
62
|
+
const raw = await readFile(filename, 'utf8');devInputs?.record(filename,raw);
|
|
63
|
+
const source = raw.replace(/^#![^\n]*(?:\n|$)/, '');
|
|
60
64
|
return { contents: prefix + source, loader: filename.endsWith('.ts') ? 'ts' : 'js', resolveDir: path.dirname(filename) };
|
|
61
65
|
});
|
|
62
66
|
},
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
async function importConfig(filename) {
|
|
70
|
+
async function importConfig(filename,devInputs) {
|
|
67
71
|
const temporary = path.join(path.dirname(filename), `.prnext-config-${randomUUID()}.mjs`);
|
|
68
72
|
try {
|
|
69
73
|
const result = await bundle({ entryPoints: [filename], absWorkingDir: path.dirname(filename), bundle: true, write: false, platform: 'node', target: 'node22', format: 'esm', packages: 'external', logLevel: 'silent',
|
|
70
74
|
define: { 'import.meta.url': '__prnext_config_url', __filename: '__prnext_config_file', __dirname: '__prnext_config_dir' },
|
|
71
75
|
banner: { js: `import {createRequire as __prnext_create_require} from 'node:module';const require=__prnext_create_require(${JSON.stringify(pathToFileURL(filename).href)});` },
|
|
72
|
-
plugins: [configPlugin()],
|
|
76
|
+
plugins: [configPlugin(devInputs)],
|
|
73
77
|
});
|
|
74
78
|
await writeFile(temporary, result.outputFiles[0].contents, { flag: 'wx' });
|
|
75
79
|
const imported = await import(pathToFileURL(temporary).href);
|
|
@@ -85,14 +89,18 @@ export function validateProjectConfig(input, filename = 'project config') {
|
|
|
85
89
|
// Optional properties explicitly set to undefined still use their defaults,
|
|
86
90
|
// as in common process.env-based Next configuration wrappers.
|
|
87
91
|
const config = { ...defaults, ...Object.fromEntries(Object.entries(input).filter(([,value]) => value !== undefined)) };
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (key === 'reactCompiler') throw new Error('reactCompiler currently supports only false. PRNext does not run the React Compiler transform; keep Next.js for builds that require it.');
|
|
91
|
-
throw new Error('typedRoutes currently supports only false. PRNext does not generate or validate typed route declarations; keep Next.js for builds that require them.');
|
|
92
|
-
}
|
|
92
|
+
if(config.typedRoutes!==undefined&&typeof config.typedRoutes!=='boolean')throw new TypeError('typedRoutes must be a boolean');
|
|
93
|
+
if(config.reactCompiler!==undefined&&config.reactCompiler!==false)throw new Error('reactCompiler currently supports only false. PRNext does not run the React Compiler transform; keep Next.js for builds that require it.');
|
|
93
94
|
config.typescript = validateTypeScript(config.typescript);
|
|
94
95
|
config.workerRetirement = validateWorkerRetirement(config.workerRetirement);
|
|
95
|
-
|
|
96
|
+
config.workerPreload=validateWorkerPreload(config.workerPreload);
|
|
97
|
+
config.cssEngine ??= 'auto';
|
|
98
|
+
if (!['auto','postcss','lightningcss'].includes(config.cssEngine)) throw new TypeError('cssEngine must be auto, postcss or lightningcss.');
|
|
99
|
+
if (config.devIndicators !== false) {
|
|
100
|
+
const indicator = config.devIndicators ?? {};
|
|
101
|
+
if (config.devIndicators === null || typeof indicator !== 'object' || Array.isArray(indicator) || Object.keys(indicator).some(key => key !== 'position') || (indicator.position !== undefined && !['bottom-right', 'bottom-left', 'top-right', 'top-left'].includes(indicator.position))) throw new TypeError('devIndicators must be false or an object with position: bottom-right, bottom-left, top-right or top-left.');
|
|
102
|
+
config.devIndicators = { position: indicator.position ?? 'bottom-left' };
|
|
103
|
+
}
|
|
96
104
|
if (config.onDemandEntries !== undefined) {
|
|
97
105
|
const entries = config.onDemandEntries;
|
|
98
106
|
if (!entries || typeof entries !== 'object' || Array.isArray(entries)) throw new TypeError('onDemandEntries must be an object');
|
|
@@ -182,9 +190,9 @@ export function validateProjectConfig(input, filename = 'project config') {
|
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
/** Config and its relative imports are evaluated fresh on every build. */
|
|
185
|
-
export async function loadProjectConfig(projectRoot, { dev = false, envMode, phase = dev ? constants.PHASE_DEVELOPMENT_SERVER : constants.PHASE_PRODUCTION_BUILD } = {}) {
|
|
193
|
+
export async function loadProjectConfig(projectRoot, { dev = false, envMode, devInputs, phase = dev ? constants.PHASE_DEVELOPMENT_SERVER : constants.PHASE_PRODUCTION_BUILD } = {}) {
|
|
186
194
|
const root = path.resolve(projectRoot);
|
|
187
|
-
loadEnvConfig(root, { dev, mode: envMode });
|
|
195
|
+
loadEnvConfig(root, { dev, mode: envMode, inputs:devInputs });
|
|
188
196
|
const files = await readdir(root);
|
|
189
197
|
const names = files.filter(name => configName.test(name));
|
|
190
198
|
const unsupportedFiles = files.filter(name => /^(?:next|prnext)\.config\.(?:mts|cts|jsx|tsx|json)$/.test(name));
|
|
@@ -192,7 +200,7 @@ export async function loadProjectConfig(projectRoot, { dev = false, envMode, pha
|
|
|
192
200
|
if (names.length > 1) throw new Error(`Multiple configuration files found: ${names.sort().join(', ')}. Keep one next.config.* or prnext.config.* file.`);
|
|
193
201
|
if (!names.length) return validateProjectConfig({});
|
|
194
202
|
const filename = path.join(root, names[0]);
|
|
195
|
-
let input = await importConfig(filename);
|
|
203
|
+
let input = await importConfig(filename,devInputs);
|
|
196
204
|
if (typeof input === 'function') input = await input(phase, { defaultConfig: { ...defaults, env: {}, experimental: { ...defaults.experimental } } });
|
|
197
205
|
return validateProjectConfig(input, names[0]);
|
|
198
206
|
}
|
package/build/css-modules.mjs
CHANGED
|
@@ -5,6 +5,9 @@ import { createHash } from 'node:crypto';
|
|
|
5
5
|
import { runInNewContext } from 'node:vm';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
7
|
import { createStyleProcessor, stylesheetPattern, moduleStylesheetPattern } from './styles.mjs';
|
|
8
|
+
import {rename} from '../runtime/fs.mjs';
|
|
9
|
+
import {randomUUID} from 'node:crypto';
|
|
10
|
+
import {rm} from 'node:fs/promises';
|
|
8
11
|
|
|
9
12
|
const assetLoaders = Object.fromEntries(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'].map(ext => [ext, 'file']));
|
|
10
13
|
|
|
@@ -20,18 +23,20 @@ async function resolveCss(esbuild, args) {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
/** One cached CSS compilation supplies identical class maps to the server and the browser. */
|
|
23
|
-
export function createCssModules({ projectRoot, stage, assetBase = '/_prnext/assets', sassOptions }) {
|
|
26
|
+
export function createCssModules({ projectRoot, stage, assetBase = '/_prnext/assets', sassOptions, cssEngine }) {
|
|
24
27
|
const cache = new Map();
|
|
25
|
-
const processor = createStyleProcessor({ projectRoot, sassOptions });
|
|
28
|
+
const processor = createStyleProcessor({ projectRoot, sassOptions, cssEngine });
|
|
26
29
|
const hash = file => createHash('sha256').update(path.relative(projectRoot, file).replaceAll(path.sep, '/')).digest('hex').slice(0, 12);
|
|
27
30
|
|
|
28
31
|
async function compile(file) {
|
|
32
|
+
const dependencies=new Set([file]);
|
|
29
33
|
const result = await bundle({
|
|
30
34
|
absWorkingDir: projectRoot,
|
|
31
35
|
stdin: { contents: `import styles from ${JSON.stringify(file)}; export default styles;`, resolveDir: projectRoot, loader: 'js' },
|
|
32
36
|
outfile: path.join(stage, '.cssmodules', hash(file) + '.js'),
|
|
33
37
|
bundle: true,
|
|
34
38
|
write: false,
|
|
39
|
+
metafile:true,
|
|
35
40
|
format: 'cjs',
|
|
36
41
|
platform: 'browser',
|
|
37
42
|
logLevel: 'silent',
|
|
@@ -52,10 +57,12 @@ export function createCssModules({ projectRoot, stage, assetBase = '/_prnext/ass
|
|
|
52
57
|
});
|
|
53
58
|
esbuild.onLoad({ filter: /.*/, namespace: 'prnext-local-css' }, async args => {
|
|
54
59
|
const transformed = await processor.process(args.pluginData.source);
|
|
60
|
+
for(const file of transformed.watchFiles)dependencies.add(file);
|
|
55
61
|
return { contents: transformed.css, loader: 'local-css', resolveDir: path.dirname(args.pluginData.source), watchFiles: transformed.watchFiles };
|
|
56
62
|
});
|
|
57
63
|
esbuild.onLoad({ filter: stylesheetPattern, namespace: 'file' }, async args => {
|
|
58
64
|
const transformed = await processor.process(args.path);
|
|
65
|
+
for(const file of transformed.watchFiles)dependencies.add(file);
|
|
59
66
|
return { contents: transformed.css, loader: 'css', resolveDir: path.dirname(args.path), watchFiles: transformed.watchFiles };
|
|
60
67
|
});
|
|
61
68
|
esbuild.onResolve({ filter: /^\// }, args => args.kind === 'url-token' ? { path: args.path, external: true } : undefined);
|
|
@@ -64,6 +71,7 @@ export function createCssModules({ projectRoot, stage, assetBase = '/_prnext/ass
|
|
|
64
71
|
});
|
|
65
72
|
const javascript = result.outputFiles.find(output => output.path.endsWith('.js'));
|
|
66
73
|
const stylesheet = result.outputFiles.find(output => output.path.endsWith('.css'));
|
|
74
|
+
for(const name of Object.keys(result.metafile.inputs))if(name!=='<stdin>'&&(!name.includes(':')||path.isAbsolute(name)))dependencies.add(path.resolve(projectRoot,name));
|
|
67
75
|
if (!javascript || !stylesheet) throw new Error(`Unable to compile CSS Module ${file}.`);
|
|
68
76
|
// This is esbuild's generated export map, whose only inputs are CSS files.
|
|
69
77
|
// The isolated context deliberately has no require, process, network, or filesystem.
|
|
@@ -73,9 +81,10 @@ export function createCssModules({ projectRoot, stage, assetBase = '/_prnext/ass
|
|
|
73
81
|
await mkdir(path.join(stage, 'assets'), { recursive: true });
|
|
74
82
|
for (const output of result.outputFiles) {
|
|
75
83
|
if (output === javascript || output === stylesheet) continue;
|
|
76
|
-
|
|
84
|
+
const target=path.join(stage,'assets',path.basename(output.path)),temporary=target+'.'+randomUUID()+'.tmp';
|
|
85
|
+
try{await writeFile(temporary,output.contents);await rename(temporary,target);}finally{await rm(temporary,{force:true});}
|
|
77
86
|
}
|
|
78
|
-
return { mapping, css: stylesheet.text };
|
|
87
|
+
return { mapping, css: stylesheet.text,watchFiles:[...dependencies] };
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
function get(file) {
|
|
@@ -84,6 +93,8 @@ export function createCssModules({ projectRoot, stage, assetBase = '/_prnext/ass
|
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
return {
|
|
96
|
+
compile:get,
|
|
97
|
+
async mappings(){return Promise.all([...cache].map(async([file,result])=>({file,mapping:(await result).mapping,watchFiles:(await result).watchFiles})));},
|
|
87
98
|
plugin(browser, { emitStyles = () => true } = {}) {
|
|
88
99
|
return {
|
|
89
100
|
name: 'prnext-shared-css-modules',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function createCompilerHealth({maxRssMb=768,maxHeapMb=384,maxBuilds=256}={}) {
|
|
2
|
+
let builds=0;const samples=[];
|
|
3
|
+
return ({rss,heapUsed})=>{
|
|
4
|
+
builds++;const rssMb=rss/1024/1024,heapMb=heapUsed/1024/1024;
|
|
5
|
+
samples.push(rssMb);if(samples.length>12)samples.shift();
|
|
6
|
+
const floor=Math.min(...samples),growth=rssMb-floor;
|
|
7
|
+
const reason=rssMb>=maxRssMb?'rss-limit':heapMb>=maxHeapMb?'heap-limit':
|
|
8
|
+
samples.length>=8&&growth>128&&rssMb>floor*1.35?'sustained-growth':builds>=maxBuilds?'session-limit':null;
|
|
9
|
+
return {builds,rssMb:Math.round(rssMb),heapMb:Math.round(heapMb),recycle:reason!==null,reason};
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {build as bundle} from 'esbuild';
|
|
2
|
+
import {readFile,writeFile,rm,stat,readdir} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {randomUUID} from 'node:crypto';
|
|
5
|
+
import {rename} from '../runtime/fs.mjs';
|
|
6
|
+
import {createCssModules} from './css-modules.mjs';
|
|
7
|
+
import {recordDevDependencies,developmentStyleDependencies} from './dev-dependencies.mjs';
|
|
8
|
+
import {writeBuildOutput} from './output-contract.mjs';
|
|
9
|
+
|
|
10
|
+
/** Publish only styles when all JS-visible class maps remain identical. */
|
|
11
|
+
export async function tryDevCssUpdate({root,source,destination,manifest,changed,sequence,cssEngine,snapshot,onFallback=()=>{}}){
|
|
12
|
+
const fallback=code=>{onFallback(code);return undefined;};
|
|
13
|
+
if(!changed.length||changed.some(file=>!file.endsWith('.css'))||!manifest.devStyles?.bundles.length||manifest.app?.globalError?.css?.length)return;
|
|
14
|
+
if(!manifest.config.assetBase.startsWith('/'))return fallback('external-asset-base');
|
|
15
|
+
if(JSON.parse(await readFile(path.join(root,'package.json'),'utf8')).postcss!==undefined)return fallback('css-processor');
|
|
16
|
+
// Custom PostCSS/webpack generators can change JS or emit other files.
|
|
17
|
+
for(const name of ['postcss.config.js','postcss.config.cjs','postcss.config.mjs','postcss.config.json','.postcssrc','.postcssrc.json'])if(await stat(path.join(root,name)).catch(()=>null)){
|
|
18
|
+
if(name!=='postcss.config.json')return fallback('css-processor');
|
|
19
|
+
const value=JSON.parse(await readFile(path.join(root,name),'utf8'));
|
|
20
|
+
if(Object.keys(value).some(key=>key!=='plugins')||!value.plugins||Object.keys(value.plugins).length)return fallback('css-processor');
|
|
21
|
+
}
|
|
22
|
+
const plans=manifest.devStyles.bundles,dependencies=new Map(manifest.devStyles.dependencies||[]);
|
|
23
|
+
// Older manifests lack enough provenance to select bundles safely.
|
|
24
|
+
if(plans.some(([name])=>!dependencies.get(name)?.length))return fallback('missing-style-dependencies');
|
|
25
|
+
const changedPaths=new Set(changed.map(file=>path.resolve(root,file)));
|
|
26
|
+
const selected=plans.filter(([name])=>dependencies.get(name).some(file=>changedPaths.has(file)));
|
|
27
|
+
const known=new Set([...dependencies.values()].flat());
|
|
28
|
+
const allRoots=plans.flatMap(([,files])=>files);
|
|
29
|
+
if(selected.flatMap(([,files])=>files).some(file=>!file.endsWith('.css')||!file.startsWith('prnext-compiled-css:')&&!path.isAbsolute(file)))return fallback('stylesheet-language');
|
|
30
|
+
for(const file of allRoots)known.add(file.replace(/^prnext-compiled-css:/,''));
|
|
31
|
+
for(const dependency of manifest.devDependencies||[])for(const file of dependency.files)if(file.endsWith('.css'))known.add(path.join(root,file));
|
|
32
|
+
if(changed.some(file=>!known.has(path.join(root,file))))return fallback('unknown-style-dependency');
|
|
33
|
+
if(!selected.length)return fallback('unknown-style-dependency');
|
|
34
|
+
const stage=destination,modules=createCssModules({projectRoot:root,stage,assetBase:manifest.config.assetBase,cssEngine});
|
|
35
|
+
try{
|
|
36
|
+
await snapshot(source,stage);
|
|
37
|
+
for(const previous of manifest.devStyles.modules.filter(module=>(module.watchFiles||[module.file]).some(file=>changedPaths.has(file)))){
|
|
38
|
+
if(!previous.file.endsWith('.css')){await rm(stage,{recursive:true,force:true});return fallback('stylesheet-language');}
|
|
39
|
+
if((await modules.compile(previous.file)).mapping!==previous.mapping){await rm(stage,{recursive:true,force:true});return fallback('css-module-exports');}
|
|
40
|
+
}
|
|
41
|
+
const state={dev:true,root,stage};
|
|
42
|
+
for(const [name,files]of selected){
|
|
43
|
+
const options={absWorkingDir:root,stdin:{contents:files.map(file=>`@import ${JSON.stringify(file.replace(/^prnext-compiled-css:/,'prnext-css:'))};`).join('\n'),resolveDir:root,loader:'css'},outfile:path.join(stage,'assets',name),bundle:true,write:false,metafile:true,logLevel:'silent',publicPath:manifest.config.assetBase,assetNames:'[name]-[hash]',loader:Object.fromEntries(['.png','.jpg','.jpeg','.svg','.gif','.webp','.avif','.woff','.woff2','.ttf'].map(ext=>[ext,'file'])),plugins:[modules.plugin(true)]};
|
|
44
|
+
const result=await bundle(options);recordDevDependencies(state,options,result);
|
|
45
|
+
for(const output of result.outputFiles){const temporary=output.path+'.'+randomUUID()+'.tmp';try{await writeFile(temporary,output.contents);await rename(temporary,output.path);}finally{await rm(temporary,{force:true});}}
|
|
46
|
+
}
|
|
47
|
+
const compiled=await modules.mappings();
|
|
48
|
+
if(compiled.some(value=>!manifest.devStyles.modules.some(previous=>previous.file===value.file&&previous.mapping===value.mapping))){await rm(stage,{recursive:true,force:true});return fallback('css-module-exports');}
|
|
49
|
+
const updated=manifest.devStyles.modules.map(previous=>compiled.find(value=>value.file===previous.file)||previous);
|
|
50
|
+
for(const [name,files]of developmentStyleDependencies(state)){
|
|
51
|
+
for(const module of updated)if(files.includes(module.file))for(const file of module.watchFiles)if(!files.includes(file))files.push(file);
|
|
52
|
+
dependencies.set(name,files);
|
|
53
|
+
}
|
|
54
|
+
const buildId=randomUUID(),baseClient=manifest.devStyles.baseClient||manifest.devClient,next={...manifest,buildId,devSequence:sequence,devClient:manifest.config.assetBase+'/dev-manifest-'+buildId+'.json',outputDirectory:stage,devStyles:{baseClient,bundles:plans,dependencies:[...dependencies],modules:updated}};
|
|
55
|
+
// Keep route invalidation conservative as CSS imports are added or removed.
|
|
56
|
+
// Unchanged routes retain their original dependency arrays.
|
|
57
|
+
next.devDependencies=(manifest.devDependencies||[]).map(route=>{
|
|
58
|
+
const relevant=selected.filter(([name])=>manifest.devStyles.dependencies.find(([key])=>key===name)[1].some(file=>route.files.includes(path.relative(root,file).replaceAll(path.sep,'/'))));
|
|
59
|
+
if(!relevant.length)return route;
|
|
60
|
+
const currentStyleFiles=new Set([...dependencies.values()].flat());
|
|
61
|
+
const removed=new Set(manifest.devStyles.dependencies.flatMap(([,files])=>files).filter(file=>!currentStyleFiles.has(file)).map(file=>path.relative(root,file).replaceAll(path.sep,'/')));
|
|
62
|
+
const files=new Set(route.files.filter(file=>!removed.has(file)));
|
|
63
|
+
for(const[name]of relevant)for(const file of dependencies.get(name)){
|
|
64
|
+
const relative=path.relative(root,file).replaceAll(path.sep,'/');
|
|
65
|
+
if(relative&&!relative.startsWith('../')&&!relative.includes(':'))files.add(relative);
|
|
66
|
+
}
|
|
67
|
+
return {...route,files:[...files]};
|
|
68
|
+
});
|
|
69
|
+
const old=JSON.parse(await readFile(path.join(source,'assets',path.basename(manifest.devClient)),'utf8'));
|
|
70
|
+
old.buildId=buildId;
|
|
71
|
+
const changedNames=new Set(selected.map(([name])=>name));
|
|
72
|
+
const revise=urls=>urls.map(url=>changedNames.has(path.posix.basename(url.split('?')[0]))?url.split('?')[0]+'?v='+buildId:url);
|
|
73
|
+
for(const page of old.pages)page.css=revise(page.css);if(old.app)old.app.css=revise(old.app.css);
|
|
74
|
+
await writeFile(path.join(stage,'assets',path.basename(next.devClient)),JSON.stringify(old));
|
|
75
|
+
const protectedNames=new Set([path.basename(baseClient),path.basename(next.devClient)]),history=[];
|
|
76
|
+
for(const name of await readdir(path.join(stage,'assets')))if(/^dev-manifest-.*\.json$/.test(name)&&!protectedNames.has(name)){const info=await stat(path.join(stage,'assets',name));history.push({name,size:info.size,time:info.mtimeMs});}
|
|
77
|
+
history.sort((a,b)=>b.time-a.time);let bytes=0;
|
|
78
|
+
for(let i=0;i<history.length;i++){bytes+=history[i].size;if(i>=32||bytes>4*1024*1024)await rm(path.join(stage,'assets',history[i].name));}
|
|
79
|
+
const temporary=path.join(stage,'manifest.'+buildId+'.tmp');await writeFile(temporary,JSON.stringify(next));await rename(temporary,path.join(stage,'manifest.json'));
|
|
80
|
+
await rm(path.join(stage,'build-output.json'));await writeBuildOutput(stage,next);
|
|
81
|
+
return next;
|
|
82
|
+
}catch(error){await rm(stage,{recursive:true,force:true});throw error;}
|
|
83
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export function recordDevDependencies(state,options,result){
|
|
3
|
+
if(!state?.dev||!result.metafile)return;
|
|
4
|
+
state.devDependencies||=new Map();state.devStyleBundles||=new Map();state.devStyleDependencies||=new Map();
|
|
5
|
+
const absolute=name=>path.resolve(options.absWorkingDir||state.root,name.replace(/^[\w-]+:(?=(?:[A-Za-z]:[\\/]|\/))/ ,''));
|
|
6
|
+
for(const [name,input]of Object.entries(result.metafile.inputs)){
|
|
7
|
+
const file=absolute(name),imports=state.devDependencies.get(file)||new Set();
|
|
8
|
+
for(const item of input.imports||[])if(!item.external)imports.add(absolute(item.path));
|
|
9
|
+
state.devDependencies.set(file,imports);
|
|
10
|
+
}
|
|
11
|
+
for(const [name,output]of Object.entries(result.metafile.outputs))if(name.endsWith('.css')){
|
|
12
|
+
const inputs=Object.keys(output.inputs||{}).filter(file=>file!=='<stdin>'),imported=new Set();
|
|
13
|
+
for(const input of inputs)for(const item of result.metafile.inputs[input]?.imports||[])if(item.kind==='import-rule')imported.add(item.path);
|
|
14
|
+
const roots=inputs.filter(input=>!imported.has(input));
|
|
15
|
+
state.devStyleBundles.set(path.basename(name),roots.map(file=>file.startsWith('prnext-compiled-css:')?file:path.isAbsolute(file)?file:path.resolve(state.root,file)));
|
|
16
|
+
state.devStyleDependencies.set(path.basename(name),inputs.map(absolute));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function developmentDependencies(state,project,manifest){
|
|
20
|
+
const graph=state?.devDependencies||new Map(),routes=[];
|
|
21
|
+
let bytes=0;
|
|
22
|
+
for(const route of manifest.routes.filter(route=>!route.internal)){
|
|
23
|
+
const source=project.routes.find(item=>item.id===route.id);if(!source)continue;
|
|
24
|
+
const pending=[],seen=new Set(),visit=value=>{if(typeof value==='string'&&path.isAbsolute(value))pending.push(value);else if(Array.isArray(value))value.forEach(visit);else if(value&&typeof value==='object')Object.values(value).forEach(visit);};
|
|
25
|
+
visit(source);if(project.app)pending.push(project.app);if(project.document)pending.push(project.document);
|
|
26
|
+
while(pending.length){const file=pending.pop();if(seen.has(file))continue;seen.add(file);for(const dependency of graph.get(file)||[])pending.push(dependency);}
|
|
27
|
+
const record={route:route.pattern,files:[...seen].filter(file=>file.startsWith(project.root+path.sep)&&!path.relative(project.root,file).startsWith('.prnext-')).map(file=>path.relative(project.root,file).replaceAll(path.sep,'/')).filter(file=>!file.includes(':')&&!file.includes('?'))};
|
|
28
|
+
bytes+=Buffer.byteLength(JSON.stringify(record));if(bytes>16*1024*1024||routes.length>=50000)return Object.assign([],{incomplete:'Route dependency graph exceeds the 16 MiB / 50,000 record budget'});
|
|
29
|
+
routes.push(record);
|
|
30
|
+
}
|
|
31
|
+
return routes;
|
|
32
|
+
}
|
|
33
|
+
export const developmentStylePlans=state=>[...(state?.devStyleBundles||[])];
|
|
34
|
+
export const developmentStyleDependencies=state=>[...(state?.devStyleDependencies||[])];
|
|
35
|
+
export function developmentModules(state,root){
|
|
36
|
+
const records=[];let bytes=0;
|
|
37
|
+
const relative=file=>{const name=path.relative(root,file).replaceAll(path.sep,'/');return !name||name.startsWith('../')||name.includes(':')||name.includes('?')||name.startsWith('.prnext-')||name.startsWith('node_modules/')?undefined:name;};
|
|
38
|
+
for(const[file,dependencies]of state?.devDependencies||[]){
|
|
39
|
+
const name=relative(file);if(!name)continue;
|
|
40
|
+
const record={route:name,module:true,files:[name,...[...dependencies].map(relative).filter(Boolean)]};
|
|
41
|
+
bytes+=Buffer.byteLength(JSON.stringify(record));if(bytes>16*1024*1024||records.length>=50000)return Object.assign([],{incomplete:'Module dependency graph exceeds the 16 MiB / 50,000 record budget'});records.push(record);
|
|
42
|
+
}
|
|
43
|
+
return records;
|
|
44
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {loadProjectConfig} from './config.mjs';
|
|
2
|
+
import {scanProject} from './scan.mjs';
|
|
3
|
+
import {snapshotEnvConfig} from '../runtime/env.mjs';
|
|
4
|
+
|
|
5
|
+
export function routeFiles(project, patterns) {
|
|
6
|
+
const files=new Set();
|
|
7
|
+
const visit=value=>{
|
|
8
|
+
if(typeof value==='string' && /\.(?:[cm]?js|jsx|tsx?)$/.test(value))files.add(value);
|
|
9
|
+
else if(Array.isArray(value))for(const item of value)visit(item);
|
|
10
|
+
else if(value&&typeof value==='object')for(const item of Object.values(value))visit(item);
|
|
11
|
+
};
|
|
12
|
+
for(const route of project.routes)if(patterns.includes(route.pattern)||route.internal||route.errorStatus)visit(route);
|
|
13
|
+
visit(project.appNotFound);return files;
|
|
14
|
+
}
|
|
15
|
+
export async function discoverDevelopment(root) {
|
|
16
|
+
const restore=snapshotEnvConfig();
|
|
17
|
+
const envMode=process.env.NODE_ENV==='test'?'test':'development';
|
|
18
|
+
process.env.NODE_ENV='development';
|
|
19
|
+
try {
|
|
20
|
+
const config=await loadProjectConfig(root,{dev:true,envMode});
|
|
21
|
+
const project=await scanProject(root,{basePath:config.basePath,pageExtensions:config.pageExtensions,i18n:config.i18n,devAppFiles:new Set()});
|
|
22
|
+
// Dynamic middleware/rewrite destinations and intercepted/parallel trees
|
|
23
|
+
// need the complete graph until the gateway can resolve their routing RPC.
|
|
24
|
+
const eager=!!project.middleware || !!config.rewrites || project.routes.some(route=>route.parallel||route.interception);
|
|
25
|
+
return {project,eager,cssFast:!config.webpack&&!Object.keys(config.turbopack?.rules||{}).length,config:{basePath:config.basePath,assetPrefix:config.assetPrefix,i18n:config.i18n,distDir:config.distDir,
|
|
26
|
+
onDemandEntries:config.onDemandEntries,typescript:config.typescript,typedRoutes:config.typedRoutes,cssEngine:config.cssEngine}};
|
|
27
|
+
} finally {restore();}
|
|
28
|
+
}
|
package/build/dev-worker.mjs
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import {build} from './index.mjs';
|
|
2
|
-
import {closeIncrementalCompiler} from './incremental.mjs';
|
|
2
|
+
import {closeIncrementalCompiler,incrementalCompilerStats} from './incremental.mjs';
|
|
3
3
|
import {realpath} from 'node:fs/promises';
|
|
4
4
|
import {createDevInputSnapshot} from '../runtime/dev-file-changes.mjs';
|
|
5
|
-
|
|
5
|
+
import {discoverDevelopment} from './dev-discovery.mjs';
|
|
6
|
+
import {transformCacheStats} from './transform-cache.mjs';
|
|
7
|
+
import {scheduleCompileCacheFlush} from '../runtime/compile-cache.mjs';
|
|
8
|
+
import {createCompilerHealth} from './dev-compiler-health.mjs';
|
|
9
|
+
const compilerHealth=createCompilerHealth();
|
|
10
|
+
let busy=false;
|
|
6
11
|
process.on('message',async message=>{
|
|
7
|
-
if(message?.type
|
|
12
|
+
if(!['build','discover'].includes(message?.type)||busy)return;
|
|
8
13
|
busy=true;
|
|
9
14
|
try{
|
|
15
|
+
if(message.type==='discover'){process.send({type:'result',ok:true,discovery:await discoverDevelopment(message.root)});return;}
|
|
10
16
|
const started=performance.now();
|
|
11
17
|
const inputs=createDevInputSnapshot([message.root,await realpath(message.root)]);
|
|
12
|
-
await build(message.root,{dev:true,incremental
|
|
18
|
+
const manifest=await build(message.root,{dev:true,incremental:!message.options?.devSafe,...message.options,devInputs:inputs});
|
|
13
19
|
console.log(`PRNext built ${message.root} in ${((performance.now()-started)/1000).toFixed(2)}s`);
|
|
14
|
-
|
|
20
|
+
scheduleCompileCacheFlush();
|
|
21
|
+
const health=compilerHealth(process.memoryUsage());
|
|
22
|
+
if(message.options?.devSafe)Object.assign(health,{recycle:true,reason:'safe-mode'});
|
|
23
|
+
process.send({type:'result',ok:true,manifest,inputs:inputs.entries(),compiler:{...incrementalCompilerStats(),health},transforms:transformCacheStats(),recycle:health.recycle});
|
|
15
24
|
}catch(error){process.send({type:'result',ok:false,error:error.stack||error.message,recycle:true});}
|
|
16
25
|
finally{busy=false;}
|
|
17
26
|
});
|