@thomas.f/prnext 0.1.17-alpha → 0.1.19-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -868
- package/build/actions.mjs +27 -15
- package/build/analyze.mjs +180 -24
- package/build/app-config.mjs +13 -0
- package/build/app-static.mjs +34 -12
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +122 -46
- package/build/bundle-runtime.mjs +22 -9
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/compact-css.mjs +53 -0
- package/build/compilation-cache.mjs +269 -0
- package/build/compilation-store.mjs +142 -0
- package/build/compiler.mjs +34 -10
- package/build/config.mjs +34 -3
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +56 -12
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-css-assets.mjs +173 -0
- package/build/dev-css-update.mjs +18 -14
- package/build/dev-dependencies.mjs +1 -1
- package/build/dev-discovery.mjs +3 -3
- package/build/dev-lazy-build.mjs +77 -0
- package/build/dev.mjs +15 -8
- package/build/dynamic.mjs +14 -7
- package/build/edge.mjs +3 -3
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +15 -5
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +112 -18
- package/build/index.mjs +90 -28
- package/build/instrumentation.mjs +30 -0
- package/build/lazy-dynamic-imports.mjs +137 -0
- package/build/metadata-routes.mjs +1 -1
- package/build/migrate.mjs +4 -4
- package/build/migration-audit.mjs +19 -31
- package/build/migration-imports.mjs +245 -0
- package/build/migration-scenarios-runner.mjs +73 -0
- package/build/migration-scenarios.mjs +72 -0
- package/build/native-discovery.mjs +65 -5
- package/build/native-packages.mjs +2 -0
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +1 -0
- package/build/persistent-cache.mjs +18 -3
- package/build/precompress.mjs +1 -1
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +19 -9
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +5 -5
- package/build/production-builder.mjs +59 -10
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/standalone.mjs +21 -3
- package/build/static-export.mjs +4 -4
- package/build/styles.mjs +6 -4
- package/build/transform.mjs +1 -1
- package/build/typed-routes.mjs +4 -0
- package/build/typescript-dev.mjs +35 -8
- package/build/typescript-native-worker.mjs +55 -0
- package/build/typescript-native.mjs +18 -0
- package/build/typescript-toolchain.mjs +19 -0
- package/build/typescript.mjs +6 -4
- package/build/webpack-graph.mjs +19 -6
- package/build/worker-memory.mjs +9 -0
- package/cli.mjs +70 -5
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-dynamic.cjs +12 -1
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +15 -3
- package/compat/cache-wire.cjs +28 -0
- package/compat/cache.cjs +2 -1
- package/compat/cache.d.cts +4 -0
- package/compat/cookies.cjs +21 -5
- package/compat/data-cache.cjs +12 -8
- package/compat/flight-delta.cjs +152 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +69 -4
- package/compat/link.cjs +13 -10
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/offline-store.cjs +11 -0
- package/compat/offline.cjs +5 -0
- package/compat/offline.d.cts +2 -0
- package/compat/redis-cache.cjs +201 -0
- package/compat/redis-cache.d.cts +37 -0
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +248 -2
- package/docs/ai/data.md +165 -5
- package/docs/ai/deployment.md +158 -0
- package/docs/ai/quickstart.md +29 -4
- package/docs/ai/routing.md +93 -0
- package/docs/ai/troubleshooting.md +116 -2
- package/package.json +40 -9
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/app-client.mjs +316 -92
- package/runtime/app-content.mjs +4 -3
- package/runtime/app-partial-client.mjs +3 -0
- package/runtime/app-partial.mjs +1 -1
- package/runtime/app-prefetch-constants.mjs +4 -0
- package/runtime/app-prefetch.mjs +2 -3
- package/runtime/app-render-key.mjs +5 -0
- package/runtime/app-render.mjs +88 -28
- package/runtime/app-routing.mjs +22 -9
- package/runtime/app-static.mjs +18 -8
- package/runtime/app-stream-html.mjs +10 -53
- package/runtime/app-style-selection.mjs +34 -0
- package/runtime/app-styles.mjs +154 -0
- package/runtime/atomic-file.mjs +27 -0
- package/runtime/cache-components-rsc.mjs +7 -2
- package/runtime/cache-request.mjs +2 -1
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-client.mjs +13 -3
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-file-changes.mjs +123 -8
- package/runtime/dev-indicator.mjs +8 -3
- package/runtime/dev-lazy-imports.mjs +121 -0
- package/runtime/dev-lazy-staging.mjs +14 -0
- package/runtime/dev-lazy-tool.mjs +16 -0
- package/runtime/dev-style-retention.mjs +15 -0
- package/runtime/development.mjs +83 -25
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +74 -0
- package/runtime/flight-tap.mjs +28 -0
- package/runtime/fs.mjs +15 -8
- package/runtime/html-only.mjs +27 -0
- package/runtime/http.mjs +2 -0
- package/runtime/incremental-cache.mjs +25 -4
- package/runtime/instrumentation.mjs +49 -0
- package/runtime/lazy-import-client.mjs +38 -0
- package/runtime/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +14 -7
- package/runtime/pages-navigation.mjs +48 -63
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/profile.mjs +14 -0
- package/runtime/profiles.json +2 -2
- package/runtime/render.mjs +3 -0
- package/runtime/route-pattern.mjs +44 -0
- package/runtime/rsc-worker.mjs +34 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/watch-project.mjs +19 -15
- package/runtime/worker.mjs +70 -26
- package/vendor/nft/LICENSE +7 -0
- package/vendor/nft/README.md +16 -0
- package/vendor/nft/out/analyze.d.ts +8 -0
- package/vendor/nft/out/analyze.js +1216 -0
- package/vendor/nft/out/analyze.js.map +1 -0
- package/vendor/nft/out/cli.d.ts +2 -0
- package/vendor/nft/out/cli.js +94 -0
- package/vendor/nft/out/cli.js.map +1 -0
- package/vendor/nft/out/fs.d.ts +22 -0
- package/vendor/nft/out/fs.js +106 -0
- package/vendor/nft/out/fs.js.map +1 -0
- package/vendor/nft/out/index.d.ts +4 -0
- package/vendor/nft/out/index.js +25 -0
- package/vendor/nft/out/index.js.map +1 -0
- package/vendor/nft/out/node-file-trace.d.ts +41 -0
- package/vendor/nft/out/node-file-trace.js +380 -0
- package/vendor/nft/out/node-file-trace.js.map +1 -0
- package/vendor/nft/out/resolve-dependency.d.ts +6 -0
- package/vendor/nft/out/resolve-dependency.js +427 -0
- package/vendor/nft/out/resolve-dependency.js.map +1 -0
- package/vendor/nft/out/types.d.ts +66 -0
- package/vendor/nft/out/types.js +2 -0
- package/vendor/nft/out/types.js.map +1 -0
- package/vendor/nft/out/utils/ast-helpers.js +49 -0
- package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
- package/vendor/nft/out/utils/binary-locators.js +88 -0
- package/vendor/nft/out/utils/binary-locators.js.map +1 -0
- package/vendor/nft/out/utils/get-package-base.js +30 -0
- package/vendor/nft/out/utils/get-package-base.js.map +1 -0
- package/vendor/nft/out/utils/interop-require.js +24 -0
- package/vendor/nft/out/utils/interop-require.js.map +1 -0
- package/vendor/nft/out/utils/node-version.js +10 -0
- package/vendor/nft/out/utils/node-version.js.map +1 -0
- package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
- package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
- package/vendor/nft/out/utils/special-cases.js +347 -0
- package/vendor/nft/out/utils/special-cases.js.map +1 -0
- package/vendor/nft/out/utils/static-eval.js +579 -0
- package/vendor/nft/out/utils/static-eval.js.map +1 -0
- package/vendor/nft/out/utils/types.js +2 -0
- package/vendor/nft/out/utils/types.js.map +1 -0
- package/vendor/nft/out/utils/wrappers.js +659 -0
- package/vendor/nft/out/utils/wrappers.js.map +1 -0
- package/vendor/nft/package.json +4 -0
- package/vendor/nft/upstream.json +51 -0
package/build/styles.mjs
CHANGED
|
@@ -39,10 +39,12 @@ async function loadConfiguration(root) {
|
|
|
39
39
|
return /\.(?:js|mjs|cjs)$/.test(filename) ? importConfiguration(filename) : JSON.parse(await readFile(filename, 'utf8'));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
export const resolveStandardBrowserTargets = root => browserslist(undefined,{path:path.resolve(root)});
|
|
43
|
+
|
|
44
|
+
async function createProcessor(root, engine, browserTargets) {
|
|
43
45
|
const config = await loadConfiguration(root);
|
|
44
46
|
if (config === undefined && engine !== 'postcss') {
|
|
45
|
-
const targets=browserslistToTargets(
|
|
47
|
+
const targets=browserslistToTargets(browserTargets ? await browserTargets() : resolveStandardBrowserTargets(root));
|
|
46
48
|
return {async process(css,options){
|
|
47
49
|
const result=transformCss({filename:options.from,code:Buffer.from(css),targets,minify:false,sourceMap:!!options.map,
|
|
48
50
|
...(options.map?.prev ? {inputSourceMap:JSON.stringify(options.map.prev)} : {}),drafts:{customMedia:true}});
|
|
@@ -134,7 +136,7 @@ function packageImporter(require) {
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
/** Build-only transformations, shared and deduplicated across all three React graphs. */
|
|
137
|
-
export function createStyleProcessor({ projectRoot, sassOptions = {}, cssEngine = 'auto' }) {
|
|
139
|
+
export function createStyleProcessor({ projectRoot, sassOptions = {}, cssEngine = 'auto', browserTargets }) {
|
|
138
140
|
const root = path.resolve(projectRoot);
|
|
139
141
|
const require = createRequire(path.join(root, 'package.json'));
|
|
140
142
|
const cache = new Map();
|
|
@@ -168,7 +170,7 @@ export function createStyleProcessor({ projectRoot, sassOptions = {}, cssEngine
|
|
|
168
170
|
sourceMap = compiled.sourceMap;
|
|
169
171
|
for (const url of compiled.loadedUrls) if (url.protocol === 'file:') dependencies.add(fileURLToPath(url));
|
|
170
172
|
}
|
|
171
|
-
processor ??= createProcessor(root,cssEngine);
|
|
173
|
+
processor ??= createProcessor(root,cssEngine,browserTargets);
|
|
172
174
|
const processed = await (await processor).process(css, { from: file, to: file, map: sourceMap ? { prev: sourceMap, inline: false, annotation: false } : false });
|
|
173
175
|
for (const message of processed.messages) if (message.type === 'dependency' && message.file) dependencies.add(path.resolve(root, message.file));
|
|
174
176
|
rebaseReferences(processed.root, file);
|
package/build/transform.mjs
CHANGED
|
@@ -51,7 +51,7 @@ export async function rewriteServerChunks(file,chunks,publicPath){
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function parserOptions(filename) {
|
|
54
|
-
return { sourceType: 'unambiguous', sourceFilename: filename, plugins: ['jsx', ...( /\.tsx?$/.test(filename) ? ['typescript'] : [])] };
|
|
54
|
+
return { sourceType: 'unambiguous', sourceFilename: filename, plugins: ['jsx', ...( /\.[cm]?tsx?$/.test(filename) ? ['typescript'] : [])] };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function assertSupportedSource(source, filename) {
|
package/build/typed-routes.mjs
CHANGED
|
@@ -3,6 +3,8 @@ import {createRequire} from 'node:module';
|
|
|
3
3
|
import {randomUUID} from 'node:crypto';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {resolveTypeScript} from './typescript-toolchain.mjs';
|
|
7
|
+
import {nativeTypeConfig} from './native-type-config.mjs';
|
|
6
8
|
|
|
7
9
|
const marker='// Generated by PRNext: typed route registry. Do not edit.';
|
|
8
10
|
async function changed(file,contents,updates){
|
|
@@ -52,6 +54,8 @@ async function bindTypes(filename,ts,updates){
|
|
|
52
54
|
async function includeTypes(root,config,helper,updates){
|
|
53
55
|
const filename=path.resolve(root,config.typescript?.tsconfigPath||'tsconfig.json');
|
|
54
56
|
const source=await readFile(filename,'utf8').catch(error=>{if(error.code==='ENOENT')return;throw error;});if(source===undefined)return;
|
|
57
|
+
const toolchain=await resolveTypeScript(root);
|
|
58
|
+
if(toolchain.native){await changed(filename,await nativeTypeConfig(filename,helper),updates);return;}
|
|
55
59
|
let ts;try{ts=createRequire(path.join(root,'package.json'))('typescript');}catch{throw Error('typedRoutes requires TypeScript installed in the application');}
|
|
56
60
|
const parsed=ts.getParsedCommandLineOfConfigFile(filename,{noEmit:true},{...ts.sys,onUnRecoverableConfigFileDiagnostic:diagnostic=>{throw Error(ts.flattenDiagnosticMessageText(diagnostic.messageText,'\n'));}});
|
|
57
61
|
if(!parsed||parsed.errors.some(error=>error.code!==18003))throw Error('Cannot configure typed routes: invalid tsconfig');
|
package/build/typescript-dev.mjs
CHANGED
|
@@ -1,22 +1,49 @@
|
|
|
1
1
|
import {fork} from 'node:child_process';
|
|
2
2
|
import {access} from 'node:fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import {resolveTypeScript} from './typescript-toolchain.mjs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {privateCacheDirectory} from './persistent-cache.mjs';
|
|
6
|
-
export async function startTypeChecker(root,config,notify){
|
|
6
|
+
export async function startTypeChecker(root,config,notify,{fork:spawnChecker=fork,recheckDelayMs=3000}={}){
|
|
7
7
|
const noop={inactive:true,async close(){}};
|
|
8
8
|
if(config.typescript?.devChecks===false)return noop;
|
|
9
9
|
const filename=path.resolve(root,config.typescript?.tsconfigPath||'tsconfig.json');try{await access(filename);}catch{return noop;}
|
|
10
|
-
let
|
|
10
|
+
let toolchain;try{toolchain=await resolveTypeScript(root);}catch(error){console.warn('TypeScript diagnostics could not start: '+error.message);return noop;}
|
|
11
11
|
const directory=await privateCacheDirectory(root,'typescript-dev');
|
|
12
|
-
let child,closed=false,failures=0,timer;
|
|
12
|
+
let child,closed=false,failures=0,timer,restartTimer,planned,pendingInvalidation;
|
|
13
13
|
function launch(){
|
|
14
14
|
if(closed)return;
|
|
15
|
-
|
|
15
|
+
const worker=toolchain.native?'./typescript-native-worker.mjs':'./typescript-dev-worker.mjs';
|
|
16
|
+
child=spawnChecker(new URL(worker,import.meta.url),[toolchain.compiler,filename,toolchain.native?'watch':directory?path.join(directory,'project.tsbuildinfo'):''],{cwd:root,execArgv:['--max-old-space-size=512'],stdio:['ignore','ignore','pipe','ipc'],windowsHide:true});
|
|
17
|
+
const current=child;
|
|
16
18
|
let details='',healthyAt=0;child.stderr.on('data',chunk=>{details=(details+chunk).slice(-2048);});
|
|
17
|
-
child.on('message',message=>{
|
|
19
|
+
child.on('message',message=>{
|
|
20
|
+
if(closed||child!==current||planned===current)return;
|
|
21
|
+
if(message?.type==='diagnostics'&&Array.isArray(message.errors)){
|
|
22
|
+
// The native watcher normally handles edits incrementally. Its startup
|
|
23
|
+
// can miss an edit; retain one bounded fallback when no report arrives.
|
|
24
|
+
if(pendingInvalidation?.worker===current&&!pendingInvalidation.forced){clearTimeout(restartTimer);pendingInvalidation=undefined;}
|
|
25
|
+
healthyAt ||= Date.now();notify(message.errors.filter(value=>typeof value==='string').slice(0,40));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
18
28
|
child.on('error',error=>{notify(['TypeScript checker: '+error.message]);});
|
|
19
|
-
child.once('exit',()=>{
|
|
29
|
+
child.once('exit',()=>{
|
|
30
|
+
if(pendingInvalidation?.worker===current){clearTimeout(restartTimer);pendingInvalidation=undefined;}
|
|
31
|
+
if(closed)return;if(planned===current){planned=undefined;launch();return;}if(healthyAt&&Date.now()-healthyAt>=60000)failures=0;if(++failures>=3){notify(['TypeScript checker stopped after repeated failures. '+details]);return;}timer=setTimeout(launch,1000*failures);
|
|
32
|
+
});
|
|
20
33
|
}
|
|
21
|
-
launch();return {
|
|
34
|
+
launch();return {
|
|
35
|
+
invalidate(file,event){
|
|
36
|
+
if(!toolchain.native||closed||!child?.connected)return;
|
|
37
|
+
const extension=path.extname(file);if(extension&&!/^\.(?:[cm]?[jt]sx?|json)$/i.test(extension))return;
|
|
38
|
+
const forced=event==='rename'||/\.json$/i.test(file);
|
|
39
|
+
if(!forced&&event!=='change')return;
|
|
40
|
+
clearTimeout(restartTimer);
|
|
41
|
+
const pending={worker:child,forced:forced||pendingInvalidation?.forced===true};pendingInvalidation=pending;
|
|
42
|
+
restartTimer=setTimeout(()=>{
|
|
43
|
+
if(closed||pendingInvalidation!==pending||child!==pending.worker||!child?.connected)return;
|
|
44
|
+
pendingInvalidation=undefined;planned=child;child.disconnect();
|
|
45
|
+
},pending.forced?150:recheckDelayMs);
|
|
46
|
+
},
|
|
47
|
+
async close(){closed=true;clearTimeout(timer);clearTimeout(restartTimer);const old=child;if(!old||old.exitCode!==null||old.signalCode)return;await new Promise(resolve=>{const timer=setTimeout(()=>old.kill('SIGKILL'),5000);old.once('exit',()=>{clearTimeout(timer);resolve();});if(old.connected)old.disconnect();else old.kill();});},
|
|
48
|
+
};
|
|
22
49
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {spawn} from 'node:child_process';
|
|
2
|
+
import {StringDecoder} from 'node:string_decoder';
|
|
3
|
+
import {mkdtemp,rm} from 'node:fs/promises';
|
|
4
|
+
import {tmpdir} from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const [compiler,filename,mode]=process.argv.slice(2);
|
|
8
|
+
const watching=mode==='watch';
|
|
9
|
+
let closing=false,buffer='',output='',overflow=false;
|
|
10
|
+
const cache=await mkdtemp(path.join(tmpdir(),'prnext-typescript-native-'));
|
|
11
|
+
const child=spawn(process.execPath,[compiler,'--project',filename,'--noEmit','--incremental','true','--tsBuildInfoFile',path.join(cache,'project.tsbuildinfo'),'--pretty','false','--locale','en',...(watching?['--watch','--preserveWatchOutput']:[])],{
|
|
12
|
+
cwd:process.cwd(),windowsHide:true,detached:process.platform!=='win32',stdio:['ignore','pipe','pipe'],
|
|
13
|
+
// Native TS respects explicit project watch options. The polling defaults
|
|
14
|
+
// also cover editors/filesystems that silently lose native watch events.
|
|
15
|
+
env:{...process.env,TSC_WATCHFILE:process.env.TSC_WATCHFILE||'DynamicPriorityPolling',TSC_WATCHDIRECTORY:process.env.TSC_WATCHDIRECTORY||'DynamicPriorityPolling'},
|
|
16
|
+
});
|
|
17
|
+
function send(value){if(process.connected)process.send(value);}
|
|
18
|
+
function consume(chunk){
|
|
19
|
+
const text=chunk.toString('utf8').replace(/\x1b\[[0-9;]*[A-Za-z]/g,'');
|
|
20
|
+
if(!watching){output+=text;if(output.length>4*1024*1024){overflow=true;void close();}return;}
|
|
21
|
+
buffer+=text;
|
|
22
|
+
// Consume complete lines, not arbitrary OS chunks. Start/completion markers
|
|
23
|
+
// delimit each diagnostic generation; a successful edit clears old errors.
|
|
24
|
+
let end;
|
|
25
|
+
while((end=buffer.indexOf('\n'))!==-1){
|
|
26
|
+
const line=buffer.slice(0,end).replace(/\r$/,'');buffer=buffer.slice(end+1);
|
|
27
|
+
if(/Starting (?:incremental )?compilation in watch mode|File change detected/.test(line))output='';
|
|
28
|
+
else if(/Found \d+ errors?\. Watching for file changes\./.test(line)){
|
|
29
|
+
send({type:'diagnostics',errors:/Found 0 errors/.test(line)?[]:output.trim()?[output.trim()]:['TypeScript reported errors. Run prnext typecheck for details.']});output='';
|
|
30
|
+
}else if(line.trim()&&output.length<20000)output+=(line+'\n').slice(0,20000-output.length);
|
|
31
|
+
}
|
|
32
|
+
if(buffer.length>20000)buffer=buffer.slice(-20000);
|
|
33
|
+
}
|
|
34
|
+
for(const stream of [child.stdout,child.stderr]){
|
|
35
|
+
const decoder=new StringDecoder('utf8');stream.on('data',chunk=>consume(decoder.write(chunk)));stream.on('end',()=>consume(decoder.end()));
|
|
36
|
+
}
|
|
37
|
+
child.once('error',error=>{send({type:'failure',message:error.message});process.exitCode=1;});
|
|
38
|
+
child.once('close',async(code,signal)=>{
|
|
39
|
+
// Only the directory returned by mkdtemp above is removed.
|
|
40
|
+
if(path.dirname(cache)===path.resolve(tmpdir())&&path.basename(cache).startsWith('prnext-typescript-native-'))await rm(cache,{recursive:true,force:true,maxRetries:5,retryDelay:100});
|
|
41
|
+
send({type:'complete',code:overflow?1:code??1,output:overflow?'TypeScript output exceeded 4 MiB. Run tsc directly for the complete output.':output,signal});
|
|
42
|
+
if(process.connected)process.disconnect();
|
|
43
|
+
});
|
|
44
|
+
async function close(){
|
|
45
|
+
if(closing)return;closing=true;
|
|
46
|
+
if(child.exitCode!==null||child.signalCode)return;
|
|
47
|
+
if(process.platform==='win32'){
|
|
48
|
+
// Own exactly this compiler subtree; TypeScript's official Node launcher
|
|
49
|
+
// starts its native executable as a child on Windows.
|
|
50
|
+
const killer=spawn('taskkill',['/PID',String(child.pid),'/T','/F'],{windowsHide:true,stdio:'ignore'});
|
|
51
|
+
killer.on('error',()=>child.kill('SIGKILL'));
|
|
52
|
+
}else{try{process.kill(-child.pid,'SIGTERM');}catch{child.kill('SIGTERM');}}
|
|
53
|
+
}
|
|
54
|
+
process.once('disconnect',()=>{void close();});
|
|
55
|
+
process.once('SIGTERM',()=>{void close();});process.once('SIGINT',()=>{void close();});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {fork} from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function checkNativeTypeScript(compiler,filename,root){
|
|
4
|
+
return new Promise((resolve,reject)=>{
|
|
5
|
+
const child=fork(new URL('./typescript-native-worker.mjs',import.meta.url),[compiler,filename,'check'],{cwd:root,windowsHide:true,execArgv:[],stdio:['ignore','ignore','pipe','ipc']});
|
|
6
|
+
let result,details='',timedOut=false,force;
|
|
7
|
+
const deadline=setTimeout(()=>{timedOut=true;if(child.connected)child.disconnect();force=setTimeout(()=>child.kill('SIGKILL'),5000);},120000);
|
|
8
|
+
child.stderr.on('data',chunk=>{details=(details+chunk).slice(-8192);});
|
|
9
|
+
child.on('message',message=>{if(message?.type==='complete')result=message;else if(message?.type==='failure')details=String(message.message).slice(0,8192);});
|
|
10
|
+
child.once('error',error=>{clearTimeout(deadline);clearTimeout(force);reject(error);});
|
|
11
|
+
child.once('exit',()=>{
|
|
12
|
+
clearTimeout(deadline);clearTimeout(force);
|
|
13
|
+
if(timedOut)reject(new Error('TypeScript check exceeded 120 seconds. Run tsc directly to inspect the project.'));
|
|
14
|
+
else if(result?.code===0)resolve();
|
|
15
|
+
else reject(new Error(result?.output||details||'TypeScript native checker exited before returning diagnostics.'));
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {readFile,stat} from 'node:fs/promises';
|
|
2
|
+
import {createRequire} from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
// Select from the application's installation, never from the framework's own
|
|
6
|
+
// development dependencies. Native TypeScript deliberately lacks the old API.
|
|
7
|
+
export async function resolveTypeScript(root) {
|
|
8
|
+
const require=createRequire(path.join(root,'package.json'));
|
|
9
|
+
const manifest=require.resolve('typescript/package.json');
|
|
10
|
+
const metadata=JSON.parse(await readFile(manifest,'utf8'));
|
|
11
|
+
const major=Number(/^([0-9]+)\./.exec(metadata.version)?.[1]);
|
|
12
|
+
if(!Number.isInteger(major))throw new Error('The installed TypeScript package has no valid version.');
|
|
13
|
+
if(major<7)return {native:false,compiler:require.resolve('typescript'),version:metadata.version};
|
|
14
|
+
const entry=typeof metadata.bin==='string'?metadata.bin:metadata.bin?.tsc;
|
|
15
|
+
if(typeof entry!=='string'||!entry)throw new Error('The installed TypeScript package has no tsc command. Reinstall TypeScript with its optional native dependencies.');
|
|
16
|
+
const directory=path.dirname(manifest),bin=path.resolve(directory,entry);
|
|
17
|
+
if(!bin.startsWith(directory+path.sep)||!(await stat(bin)).isFile())throw new Error('The installed TypeScript tsc command is invalid.');
|
|
18
|
+
return {native:true,compiler:bin,version:metadata.version};
|
|
19
|
+
}
|
package/build/typescript.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
2
|
import { execFile } from 'node:child_process';
|
|
4
3
|
import { promisify } from 'node:util';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
import path from 'node:path';
|
|
6
|
+
import {resolveTypeScript} from './typescript-toolchain.mjs';
|
|
7
|
+
import {checkNativeTypeScript} from './typescript-native.mjs';
|
|
7
8
|
|
|
8
9
|
const execute = promisify(execFile);
|
|
9
10
|
|
|
@@ -37,11 +38,12 @@ export async function checkTypeScript(root, config, { dev = false, tsconfig, for
|
|
|
37
38
|
if (!force && (dev || !config.typescript || config.typescript.ignoreBuildErrors)) return;
|
|
38
39
|
tsconfig ??= await projectTsconfig(root, config);
|
|
39
40
|
if (!tsconfig) throw new Error('TypeScript checking was explicitly enabled, but tsconfig.json does not exist. Create a TypeScript configuration, set typescript.tsconfigPath to an existing configuration, or explicitly set typescript.ignoreBuildErrors: true if types are checked separately.');
|
|
40
|
-
let
|
|
41
|
-
try {
|
|
41
|
+
let toolchain;
|
|
42
|
+
try { toolchain = await resolveTypeScript(root); }
|
|
42
43
|
catch (error) { throw new Error('TypeScript checking requires typescript in the project. Install it as a dev dependency, or explicitly set typescript.ignoreBuildErrors: true if a separate process checks types.', { cause: error }); }
|
|
43
44
|
try {
|
|
44
|
-
|
|
45
|
+
if(toolchain.native){await checkNativeTypeScript(toolchain.compiler,tsconfig,root);return;}
|
|
46
|
+
await execute(process.execPath, [fileURLToPath(new URL('./typescript-check.mjs', import.meta.url)), toolchain.compiler, tsconfig], {
|
|
45
47
|
cwd: root, windowsHide: true, maxBuffer: 4 * 1024 * 1024,
|
|
46
48
|
});
|
|
47
49
|
} catch (error) {
|
package/build/webpack-graph.mjs
CHANGED
|
@@ -77,6 +77,13 @@ function pluginIdentity(plugin,webpack){
|
|
|
77
77
|
export const webpackCompilerStats=()=>({contexts:sessions.size,created,reused,limit:sessionLimit,buildLimit:sessionBuildLimit});
|
|
78
78
|
function queued(callback){const task=graphQueue.then(callback);graphQueue=task.catch(()=>{});return task;}
|
|
79
79
|
export function closeWebpackCompiler(root){return queued(async()=>{
|
|
80
|
+
if(root!==undefined){
|
|
81
|
+
try{root=await realpath(root);}
|
|
82
|
+
catch(error){
|
|
83
|
+
if(error.code!=='ENOENT'&&error.code!=='ENOTDIR')throw error;
|
|
84
|
+
root=path.resolve(root);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
80
87
|
const closing=[];for(const[key,session]of sessions)if(root===undefined||session.root===root){sessions.delete(key);closing.push(session.dispose());}
|
|
81
88
|
const results=await Promise.allSettled(closing),errors=results.filter(result=>result.status==='rejected').map(result=>result.reason);
|
|
82
89
|
if(errors.length)throw new AggregateError(errors,'Unable to dispose webpack compiler sessions');
|
|
@@ -84,6 +91,9 @@ export function closeWebpackCompiler(root){return queued(async()=>{
|
|
|
84
91
|
|
|
85
92
|
async function compileGraph(resolver,loads,options,{state,config,target}){
|
|
86
93
|
return queued(async()=>{
|
|
94
|
+
// Ownership uses one canonical identity for invalidation and disposal.
|
|
95
|
+
// Compiler paths and graph fingerprints retain the caller's lexical root.
|
|
96
|
+
const ownerRoot=await realpath(state.root);
|
|
87
97
|
const webpack=(await import('webpack')).default;
|
|
88
98
|
let reusable=state.dev&&state.incremental,identity;
|
|
89
99
|
if(reusable){try{identity=await buildCacheIdentity(state.root);}catch{}if(!identity)reusable=false;}
|
|
@@ -97,17 +107,17 @@ async function compileGraph(resolver,loads,options,{state,config,target}){
|
|
|
97
107
|
// Unknown data structures, safe mode and unavailable identities all keep
|
|
98
108
|
// compilation correct through a one-shot compiler. Hidden mutable closure
|
|
99
109
|
// state cannot be fingerprinted: replace the plugin instance when it changes.
|
|
100
|
-
if(state.dev&&!reusable)for(const[key,session]of sessions)if(session.root===
|
|
110
|
+
if(state.dev&&!reusable)for(const[key,session]of sessions)if(session.root===ownerRoot){sessions.delete(key);await session.dispose();}
|
|
101
111
|
let session=reusable&&sessions.get(key);
|
|
102
112
|
if(session&&session.builds>=sessionBuildLimit){sessions.delete(key);await session.dispose();session=undefined;}
|
|
103
113
|
if(reusable){
|
|
104
|
-
for(const[old,previous]of sessions)if(previous.root===
|
|
114
|
+
for(const[old,previous]of sessions)if(previous.root===ownerRoot&&(previous.identity!==identity||previous.base===base&&old!==key)){sessions.delete(old);await previous.dispose();}
|
|
105
115
|
}
|
|
106
116
|
if(session){sessions.delete(key);reused++;}
|
|
107
117
|
else{
|
|
108
118
|
if(reusable)while(sessions.size>=sessionLimit){const[old,previous]=sessions.entries().next().value;sessions.delete(old);await previous.dispose();}
|
|
109
119
|
session=await createGraphSession(state.root,{webpack,reusable,stage:state.stage});created++;
|
|
110
|
-
Object.assign(session,{root:
|
|
120
|
+
Object.assign(session,{root:ownerRoot,base,identity,builds:0});
|
|
111
121
|
}
|
|
112
122
|
if(reusable)sessions.set(key,session);
|
|
113
123
|
try{session.builds++;return await session.run(resolver,loads,options,{state,config,target});}
|
|
@@ -233,7 +243,7 @@ async function createGraphSession(root,{webpack,reusable,stage}) {
|
|
|
233
243
|
cssChunkFilename:'style-[contenthash:16].css',module:true,library:{type:'module'},chunkFormat:'module',chunkLoading:'import',
|
|
234
244
|
publicPath:target==='node'?'./':state.assetBase+'/',environment:{module:true,dynamicImport:true},compareBeforeEmit:false,clean:false},
|
|
235
245
|
performance:false,
|
|
236
|
-
optimization:{minimize:!!options.minify,minimizer:[{apply(compiler){
|
|
246
|
+
optimization:{minimize:!!options.minify,mangleExports:options.minify?'size':false,minimizer:[{apply(compiler){
|
|
237
247
|
compiler.hooks.compilation.tap('PRNextMinify',compilation=>{
|
|
238
248
|
compilation.hooks.processAssets.tapPromise({name:'PRNextMinify',stage:webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE},async()=>{
|
|
239
249
|
// Sequential native esbuild transforms avoid a Terser worker pool.
|
|
@@ -247,7 +257,7 @@ async function createGraphSession(root,{webpack,reusable,stage}) {
|
|
|
247
257
|
});
|
|
248
258
|
});
|
|
249
259
|
}}],runtimeChunk:options.splitting?{name:()=>runtime}:false,
|
|
250
|
-
splitChunks:options.splitting?{chunks:'all',minSize:0,cacheGroups:{default:false,defaultVendors:false,shared:{minChunks:2,test:module=>!module.type.startsWith('css'),name:false}}}:false},
|
|
260
|
+
splitChunks:options.splitting?{chunks:'all',minSize:0,cacheGroups:{default:false,defaultVendors:false,shared:{minChunks:2,reuseExistingChunk:true,test:module=>!module.type.startsWith('css'),name:false}}}:false},
|
|
251
261
|
resolve:{alias:config.resolution.resolveAlias || {},conditionNames:[...(options.conditions || []),'...'],extensions:config.resolution.resolveExtensions || ['.tsx','.ts','.jsx','.js','.mjs','.json'],fullySpecified:false},
|
|
252
262
|
module:{unsafeCache:false,rules:[{test:/./,type:'javascript/auto',enforce:'post',use:[{loader:loaderPath,options:{bridge:bridgeId}}]},
|
|
253
263
|
{test:/\.json$/,type:'json'},{test:/\.(?:css|scss|sass)$/,type:'css'},
|
|
@@ -379,7 +389,10 @@ async function createGraphSession(root,{webpack,reusable,stage}) {
|
|
|
379
389
|
for(const name of previousOutputs)if(!currentOutputs.has(name))await discardPrivateFile(path.join(actualOut,name),actualOut);
|
|
380
390
|
previousOutputs.clear();for(const name of currentOutputs)previousOutputs.add(name);
|
|
381
391
|
}
|
|
382
|
-
|
|
392
|
+
// This compatibility metafile does not yet encode webpack's runtime and
|
|
393
|
+
// async chunk-group edges. Consumers must not mistake empty import lists
|
|
394
|
+
// for a complete initial browser graph.
|
|
395
|
+
return {errors:[],warnings:[],metafile:{inputs,outputs,initialGraphComplete:false},...(options.write===false?{outputFiles}:{})};
|
|
383
396
|
} finally {
|
|
384
397
|
active.delete(bridgeId);
|
|
385
398
|
for(const file of virtualFiles)if(!usedVirtualFiles.has(file)){virtualFiles.delete(file);await discardPrivateFile(file,home);}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function validateWorkerMemory(value) {
|
|
2
|
+
if (value === undefined || value === false) return undefined;
|
|
3
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) ||
|
|
4
|
+
Object.keys(value).some(key => key !== 'maxRssMb') ||
|
|
5
|
+
!Number.isSafeInteger(value.maxRssMb) || value.maxRssMb < 64 || value.maxRssMb > 65536) {
|
|
6
|
+
throw new TypeError('workerMemory must be false or { maxRssMb: 64..65536 }');
|
|
7
|
+
}
|
|
8
|
+
return { maxRssMb: value.maxRssMb };
|
|
9
|
+
}
|
package/cli.mjs
CHANGED
|
@@ -11,6 +11,9 @@ const help = `PRNext ${packageManifest.version}
|
|
|
11
11
|
|
|
12
12
|
Use prnext for portable scripts; prn is a shortcut on compatible shells.
|
|
13
13
|
|
|
14
|
+
prnext create <directory> Create a project and install dependencies
|
|
15
|
+
prnext create <directory> --no-install Prepare project files without installing
|
|
16
|
+
prnext create --help TypeScript, JavaScript and creation options
|
|
14
17
|
prnext build [directory] Build React/TypeScript and static HTML
|
|
15
18
|
prnext build [directory] --watch Incremental production rebuilds on changes
|
|
16
19
|
prnext build [directory] --adapter static [--from dist] [--spa]
|
|
@@ -40,6 +43,7 @@ Use prnext for portable scripts; prn is a shortcut on compatible shells.
|
|
|
40
43
|
prnext typecheck [directory] [--json] Generate types and check TypeScript without building
|
|
41
44
|
prnext inspect [directory] [--json|--otlp] Explain routes, health and sampled timings
|
|
42
45
|
prnext analyze [directory] [--json] [--max-js 150] Browser bundles and budget (kB)
|
|
46
|
+
prnext compiler [directory] [--json] React Compiler results from the last successful build
|
|
43
47
|
prnext host [prnext.host.json] Host built apps by hostname; wake on demand
|
|
44
48
|
prnext host [config] --check Validate hosting configuration and builds
|
|
45
49
|
prnext host [config] --status [--json] Show memory, process and idle status
|
|
@@ -54,6 +58,8 @@ Migration options: --dry-run (preview), --no-install (prepare only), --json
|
|
|
54
58
|
prnext migrate --check Audit source compatibility without migrating
|
|
55
59
|
prnext migrate --check --against URL --candidate URL [--routes paths.json]
|
|
56
60
|
Compare GET responses before switching
|
|
61
|
+
prnext migrate --check --against URL --candidate URL --browser-scenarios ./scenarios.mjs
|
|
62
|
+
Run trusted application browser assertions
|
|
57
63
|
Migration keeps Next.js and backs up package.json and existing lockfiles.
|
|
58
64
|
Simple next dev/build/start scripts become prnext commands; custom shell scripts need a manual edit.
|
|
59
65
|
Installation honors npm, pnpm, Yarn Classic/Berry and the workspace root.
|
|
@@ -69,10 +75,12 @@ Analysis options: --metric raw|gzip|brotli (default raw), --baseline analysis.js
|
|
|
69
75
|
--max-increase-bytes 1024 --max-increase-percent 5
|
|
70
76
|
--budgets budgets.json Exact route keys with maxJs (kB),
|
|
71
77
|
maxIncreaseBytes and/or maxIncreasePercent
|
|
72
|
-
Analysis exit codes: 0 passed, 1 budget exceeded
|
|
78
|
+
Analysis exit codes: 0 passed, 1 budget exceeded or initial total unverifiable,
|
|
79
|
+
2 invalid input or missing build. Request-dependent totals are lower bounds.
|
|
73
80
|
|
|
74
81
|
Server options: --port 3000 --hostname 127.0.0.1 --workers 1 --inspect
|
|
75
82
|
Production profiles: --profile balanced|speed|memory|classic
|
|
83
|
+
Render readiness: --prewarm --readiness-file /private/path/ready.json
|
|
76
84
|
Worker retirement: --worker-idle-seconds 300 --lane-idle-seconds 30 --keep-warm [true|false]
|
|
77
85
|
Memory favors lower RAM under concurrency, accepting more waiting and CPU per response.
|
|
78
86
|
Without an option: PRNEXT_PROFILE, legacy PRNEXT_MEMORY_PROFILE=compact, then balanced.
|
|
@@ -101,6 +109,30 @@ function stop(child) {
|
|
|
101
109
|
async function main() {
|
|
102
110
|
if (['help', '--help', '-h'].includes(command)) { console.log(help); return; }
|
|
103
111
|
if (['--version', '-v'].includes(command)) { console.log(packageManifest.version); return; }
|
|
112
|
+
if (command === 'create') {
|
|
113
|
+
const { createHelp, parseCreateArgs, createProject } = await import('./build/create.mjs');
|
|
114
|
+
if (argv.length === 1 && ['--help', '-h'].includes(argv[0])) { console.log(createHelp); return; }
|
|
115
|
+
const { directory, options } = parseCreateArgs(argv);
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
let interrupted;
|
|
118
|
+
const interrupt = signal => { interrupted = signal; controller.abort(); };
|
|
119
|
+
const onInt = () => interrupt('SIGINT'), onTerm = () => interrupt('SIGTERM');
|
|
120
|
+
process.on('SIGINT', onInt); process.on('SIGTERM', onTerm);
|
|
121
|
+
try {
|
|
122
|
+
const report = await createProject(directory, { ...options, signal: controller.signal,
|
|
123
|
+
onOutput: options.json ? () => {} : (chunk, stream) => process[stream].write(chunk) });
|
|
124
|
+
if (options.json) console.log(JSON.stringify(report, null, 2));
|
|
125
|
+
else {
|
|
126
|
+
console.log(`PRNext project ${report.status}: ${report.root}`);
|
|
127
|
+
if (report.error) console.error(report.error);
|
|
128
|
+
if (!report.ok) console.log('Project files were preserved. Retry installation from this directory.');
|
|
129
|
+
if (path.resolve(process.cwd()) !== directory) console.log(`\nOpen ${report.root} in your terminal, then run:`);
|
|
130
|
+
for (const next of report.next) console.log(` ${next}`);
|
|
131
|
+
}
|
|
132
|
+
if (!report.ok) process.exitCode = interrupted === 'SIGINT' ? 130 : interrupted === 'SIGTERM' ? 143 : 1;
|
|
133
|
+
} finally { process.removeListener('SIGINT', onInt); process.removeListener('SIGTERM', onTerm); }
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
104
136
|
if (command === 'docs') {
|
|
105
137
|
const { executeAiDocs } = await import('./runtime/ai-docs.mjs');
|
|
106
138
|
const result = await executeAiDocs(argv);
|
|
@@ -161,13 +193,20 @@ async function main() {
|
|
|
161
193
|
report.comparison = await compareServers({ ...options, routes });
|
|
162
194
|
report.ok &&= report.comparison.ok;
|
|
163
195
|
}
|
|
196
|
+
if (options.browserScenarios) {
|
|
197
|
+
const { runMigrationScenarios } = await import('./build/migration-scenarios.mjs');
|
|
198
|
+
report.browser = await runMigrationScenarios({ directory, ...options, scenarios: options.browserScenarios });
|
|
199
|
+
report.ok &&= report.browser.ok;
|
|
200
|
+
}
|
|
164
201
|
if (options.json) console.log(JSON.stringify(report, null, 2));
|
|
165
202
|
else {
|
|
166
203
|
console.log(`PRNext migration check ${report.ok ? 'passed' : 'failed'}: ${directory}`);
|
|
167
204
|
console.log(`${report.files} source files; ${report.routes.length} routes.`);
|
|
168
205
|
for (const item of report.findings) console.log(`${item.severity.toUpperCase()} ${item.file || 'project'}${item.line ? ':' + item.line : ''}: ${item.message}\n ${item.action}`);
|
|
169
206
|
for (const result of report.comparison?.results || []) console.log(`${result.equal ? 'MATCH' : 'DIFFERENT'} ${result.route}: ${result.error || result.differences.join(', ') || 'responses match'}`);
|
|
170
|
-
for (const
|
|
207
|
+
for (const result of report.browser?.results || []) console.log(`BROWSER ${result.scenario} (${result.target}): ${result.status}${result.reason ? ' — ' + result.reason : ''}`);
|
|
208
|
+
if (report.browser?.reason) console.log(`BROWSER unknown: ${report.browser.reason}`);
|
|
209
|
+
for (const note of [...report.notes, ...(report.comparison ? [report.comparison.scope] : []), ...(report.browser ? [report.browser.scope] : [])]) console.log(note);
|
|
171
210
|
}
|
|
172
211
|
if (!report.ok) process.exitCode = 1;
|
|
173
212
|
return;
|
|
@@ -185,10 +224,28 @@ async function main() {
|
|
|
185
224
|
if (!report.ok) process.exitCode = 1;
|
|
186
225
|
return;
|
|
187
226
|
}
|
|
188
|
-
if (!['build', 'start', 'dev', 'routes', 'check', 'typegen', 'typecheck', 'inspect', 'analyze', 'output'].includes(command)) throw new Error(`Unknown command: ${command}\n${help}`);
|
|
227
|
+
if (!['build', 'start', 'dev', 'routes', 'check', 'typegen', 'typecheck', 'inspect', 'analyze', 'compiler', 'output'].includes(command)) throw new Error(`Unknown command: ${command}\n${help}`);
|
|
189
228
|
const args = [...argv];
|
|
190
229
|
const root = path.resolve(args[0] && !args[0].startsWith('-') ? args.shift() : '.');
|
|
191
|
-
if (['check','typegen','typecheck','analyze'].includes(command) && args.some(arg => ['--help','-h'].includes(arg))) { console.log(help); return; }
|
|
230
|
+
if (['check','typegen','typecheck','analyze','compiler'].includes(command) && args.some(arg => ['--help','-h'].includes(arg))) { console.log(help); return; }
|
|
231
|
+
if(command==='compiler'){
|
|
232
|
+
try {
|
|
233
|
+
if(args.some(arg=>arg!=='--json') || args.filter(arg=>arg==='--json').length>1)throw new Error('compiler accepts only [directory] and --json');
|
|
234
|
+
const {readReactCompilerReport,printReactCompilerReport}=await import('./build/react-compiler-report.mjs');
|
|
235
|
+
const {readBuildOutput}=await import('./build/output-contract.mjs');
|
|
236
|
+
const directory=path.join(root,await readBuildDirectory(root));
|
|
237
|
+
const output=await readBuildOutput(directory);
|
|
238
|
+
if(output.adapter!=='react')throw new Error('React Compiler reports apply to React builds. This application uses the static adapter.');
|
|
239
|
+
const report=await readReactCompilerReport(directory,{buildId:output.buildId});
|
|
240
|
+
if(!report)throw new Error('No React Compiler report is available. Rebuild this application with the installed PRNext version.');
|
|
241
|
+
if(args.includes('--json'))console.log(JSON.stringify(report,null,2));else printReactCompilerReport(report,{details:true});
|
|
242
|
+
} catch(error) {
|
|
243
|
+
if(args.includes('--json'))console.log(JSON.stringify({ok:false,error:error.message}));
|
|
244
|
+
else console.error(`PRNext compiler: ${error.message}`);
|
|
245
|
+
process.exitCode=2;
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
192
249
|
if(command==='analyze'){
|
|
193
250
|
try {
|
|
194
251
|
const {analyzeBuild,printAnalysis,parseAnalysisArgs}=await import('./build/analyze.mjs');
|
|
@@ -307,7 +364,15 @@ async function main() {
|
|
|
307
364
|
await buildStatic(root, options);
|
|
308
365
|
} else {
|
|
309
366
|
const { build } = await import('./build/index.mjs');
|
|
310
|
-
await build(root, { ...options, dev, envMode });
|
|
367
|
+
const result=await build(root, { ...options, dev, envMode });
|
|
368
|
+
if(!dev&&result.compilationCache?.hits){
|
|
369
|
+
const {hits,reusedBytes}=result.compilationCache;
|
|
370
|
+
console.log(`Compilation cache: ${hits} ${hits===1?'graph':'graphs'} reused (${Math.ceil(reusedBytes/1024)} KiB).`);
|
|
371
|
+
}
|
|
372
|
+
if(result.reactCompilerReport?.enabled){
|
|
373
|
+
const {printReactCompilerReport}=await import('./build/react-compiler-report.mjs');
|
|
374
|
+
printReactCompilerReport(result.reactCompilerReport,{details:false});
|
|
375
|
+
}
|
|
311
376
|
}
|
|
312
377
|
console.log(`PRNext built ${root} in ${((performance.now() - started) / 1000).toFixed(2)}s`);
|
|
313
378
|
return;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// A shared budget across layout slots, rather than a separate page budget at
|
|
4
|
+
// every nesting level. Only inactive branches enter this registry.
|
|
5
|
+
function createActivityCache({ limit = 4, ttl = 120000, now = Date.now, schedule = setTimeout, cancel = clearTimeout } = {}) {
|
|
6
|
+
const entries = new Map(); let timer;
|
|
7
|
+
function arm() {
|
|
8
|
+
cancel(timer); timer = undefined;
|
|
9
|
+
if (entries.size) {
|
|
10
|
+
timer = schedule(prune, Math.max(1, Math.min(...[...entries.values()].map(entry => entry.expires)) - now()));
|
|
11
|
+
timer?.unref?.();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function prune() {
|
|
15
|
+
for (const [key, entry] of entries) if (entry.expires <= now()) { entries.delete(key); entry.evict(); }
|
|
16
|
+
while (entries.size > limit) { const [key, entry] = entries.entries().next().value; entries.delete(key); entry.evict(); }
|
|
17
|
+
arm();
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
retain(key, evict) { if (!entries.has(key)) entries.set(key, { evict, expires: now() + ttl }); prune(); },
|
|
21
|
+
release(key) { entries.delete(key); arm(); },
|
|
22
|
+
clear() { const previous = [...entries.values()]; entries.clear(); cancel(timer); timer = undefined; for (const entry of previous) entry.evict(); },
|
|
23
|
+
get size() { return entries.size; },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
module.exports = { createActivityCache };
|
package/compat/after.cjs
CHANGED
|
@@ -4,11 +4,11 @@ const { AsyncLocalStorage } = require('node:async_hooks');
|
|
|
4
4
|
function after(task) {
|
|
5
5
|
const context = require('./headers.cjs').currentRequest();
|
|
6
6
|
if (require('./data-cache.cjs').inCacheScope()) throw new Error('after() cannot be registered inside a cache scope');
|
|
7
|
-
if (!context.afterScope || !['route', 'action', 'after'].includes(context.phase)) throw new Error('after()
|
|
7
|
+
if (!context.afterScope || !['route', 'action', 'render', 'after'].includes(context.phase)) throw new Error('after() requires a Node Server Component, Route Handler or Server Action. Edge after() is not implemented.');
|
|
8
8
|
context.afterScope.add(task, context, AsyncLocalStorage.snapshot());
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function createAfterScope({ signal, timeoutMs = 25000, onTimeout = () => {}, onComplete, report = error => console.error('[prnext] after() failed:', error?.stack || error) } = {}) {
|
|
11
|
+
function createAfterScope({ signal, timeoutMs = 25000, onTimeout = () => {}, onComplete, onRegistered, report = error => console.error('[prnext] after() failed:', error?.stack || error) } = {}) {
|
|
12
12
|
const queue = []; let count = 0, closed = false, finishing;
|
|
13
13
|
const deadline = Date.now() + timeoutMs;
|
|
14
14
|
const track = promise => signal?.[Symbol.for('prnext.request-work')]?.(promise, { timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
@@ -18,6 +18,7 @@ function createAfterScope({ signal, timeoutMs = 25000, onTimeout = () => {}, onC
|
|
|
18
18
|
if (closed) throw new Error('after() request has already completed');
|
|
19
19
|
if (typeof task !== 'function' && (!task || typeof task.then !== 'function')) throw new TypeError('after() expects a callback or a Promise');
|
|
20
20
|
if (++count > 128) throw new Error('after() exceeds 128 tasks per request');
|
|
21
|
+
if (count === 1) onRegistered?.();
|
|
21
22
|
const pending = typeof task === 'function' ? null : observe(task);
|
|
22
23
|
if (pending) track(pending);
|
|
23
24
|
queue.push({ task: pending ? null : task, pending, context, run });
|
|
@@ -27,8 +28,9 @@ function createAfterScope({ signal, timeoutMs = 25000, onTimeout = () => {}, onC
|
|
|
27
28
|
try {
|
|
28
29
|
while (queue.length) {
|
|
29
30
|
const item = queue.shift(), remaining = deadline - Date.now();
|
|
30
|
-
if (remaining <= 0) { const error = new Error('after() exceeded the request maxDuration'); report(error); onTimeout(error); break; }
|
|
31
|
+
if (remaining <= 0) { const error = new Error('after() exceeded the request maxDuration'); await item.run(()=>report(error)); onTimeout(error); break; }
|
|
31
32
|
const abort = new AbortController();
|
|
33
|
+
item.context.afterRestricted ||= item.context.phase === 'render';
|
|
32
34
|
item.context.cookies._mutable = false;
|
|
33
35
|
item.context.phase = 'after';
|
|
34
36
|
item.context.signal = abort.signal;
|
|
@@ -38,7 +40,7 @@ function createAfterScope({ signal, timeoutMs = 25000, onTimeout = () => {}, onC
|
|
|
38
40
|
let timer;
|
|
39
41
|
try {
|
|
40
42
|
const outcome = await Promise.race([work, new Promise(resolve => { timer = setTimeout(() => { const error = new Error('after() exceeded the request maxDuration'); abort.abort(error); resolve({ error }); }, remaining); })]);
|
|
41
|
-
if ('error' in outcome) report(outcome.error);
|
|
43
|
+
if ('error' in outcome) await item.run(()=>report(outcome.error));
|
|
42
44
|
if (abort.signal.aborted) { onTimeout(outcome.error); break; }
|
|
43
45
|
} finally { clearTimeout(timer); item.context.cacheState.closed = true; await onComplete?.(item.context); }
|
|
44
46
|
}
|
package/compat/app-dynamic.cjs
CHANGED
|
@@ -19,7 +19,18 @@ function dynamic(input, options) {
|
|
|
19
19
|
if (!opts.ssr && typeof React.useEffect !== 'function') {
|
|
20
20
|
throw new Error('ssr: false is not supported with next/dynamic in Server Components. Move it into a Client Component.');
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
let Lazy;
|
|
23
|
+
if(process.env.NODE_ENV==='development'&&typeof window!=='undefined'&&typeof opts.__prnextDevIdentity==='string'){
|
|
24
|
+
const registry=globalThis.__PRNEXT_DEV_DYNAMIC__||=new Map();
|
|
25
|
+
let entry=registry.get(opts.__prnextDevIdentity);
|
|
26
|
+
if(!entry||entry.failed){
|
|
27
|
+
entry={loader:opts.loader};
|
|
28
|
+
entry.lazy=React.lazy(()=>Promise.resolve().then(()=>entry.loader()).then(moduleOf,error=>{entry.failed=true;throw error;}));
|
|
29
|
+
if(registry.size>=2048)registry.delete(registry.keys().next().value);
|
|
30
|
+
registry.set(opts.__prnextDevIdentity,entry);
|
|
31
|
+
}else entry.loader=opts.loader;
|
|
32
|
+
Lazy=entry.lazy;
|
|
33
|
+
}else Lazy=React.lazy(() => Promise.resolve().then(opts.loader).then(moduleOf));
|
|
23
34
|
function DynamicComponent(props) {
|
|
24
35
|
const fallback = opts.loading ? React.createElement(opts.loading, { isLoading: true, pastDelay: true, error: null }) : null;
|
|
25
36
|
const child = React.createElement(Lazy, props);
|