@thomas.f/prnext 0.1.16-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 +384 -9
- 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 +25 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {createRequire} from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {createTypeScriptWatchSystem} from './typescript-watch.mjs';
|
|
4
|
+
const [compiler,filename,cache]=process.argv.slice(2),ts=createRequire(import.meta.url)(compiler);
|
|
5
|
+
const format={getCanonicalFileName:name=>name,getCurrentDirectory:()=>path.dirname(filename),getNewLine:()=>ts.sys.newLine};
|
|
6
|
+
const send=diagnostics=>{
|
|
7
|
+
let bytes=0;const errors=[];
|
|
8
|
+
for(const diagnostic of diagnostics){if(diagnostic.category!==ts.DiagnosticCategory.Error)continue;const text=ts.formatDiagnostics([diagnostic],format);if(bytes+text.length>20000||errors.length>=40)break;bytes+=text.length;errors.push(text);}
|
|
9
|
+
if(process.connected)process.send({type:'diagnostics',errors});
|
|
10
|
+
};
|
|
11
|
+
const host=ts.createWatchCompilerHost(filename,{noEmit:true,incremental:true,...(cache?{tsBuildInfoFile:cache}:{})},createTypeScriptWatchSystem(ts,process.cwd()),ts.createSemanticDiagnosticsBuilderProgram,diagnostic=>send([diagnostic]),()=>{});
|
|
12
|
+
host.writeFile=(filename,contents)=>{if(cache&&path.resolve(filename)===path.resolve(cache))ts.sys.writeFile(filename,contents);};
|
|
13
|
+
host.afterProgramCreate=builder=>{
|
|
14
|
+
try{send(ts.getPreEmitDiagnostics(builder.getProgram()));builder.emit(undefined,host.writeFile);}catch(error){send([{category:ts.DiagnosticCategory.Error,code:0,messageText:error.message}]);}
|
|
15
|
+
};
|
|
16
|
+
const watcher=ts.createWatchProgram(host);
|
|
17
|
+
const close=()=>{watcher.close();process.exit(0);};
|
|
18
|
+
process.once('disconnect',close);process.once('SIGTERM',close);process.once('SIGINT',close);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {fork} from 'node:child_process';
|
|
2
|
+
import {access} from 'node:fs/promises';
|
|
3
|
+
import {createRequire} from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {privateCacheDirectory} from './persistent-cache.mjs';
|
|
6
|
+
export async function startTypeChecker(root,config,notify){
|
|
7
|
+
const noop={inactive:true,async close(){}};
|
|
8
|
+
if(config.typescript?.devChecks===false)return noop;
|
|
9
|
+
const filename=path.resolve(root,config.typescript?.tsconfigPath||'tsconfig.json');try{await access(filename);}catch{return noop;}
|
|
10
|
+
let compiler;try{compiler=createRequire(path.join(root,'package.json')).resolve('typescript');}catch{console.warn('TypeScript diagnostics require typescript installed in the application.');return noop;}
|
|
11
|
+
const directory=await privateCacheDirectory(root,'typescript-dev');
|
|
12
|
+
let child,closed=false,failures=0,timer;
|
|
13
|
+
function launch(){
|
|
14
|
+
if(closed)return;
|
|
15
|
+
child=fork(new URL('./typescript-dev-worker.mjs',import.meta.url),[compiler,filename,directory?path.join(directory,'project.tsbuildinfo'):''],{cwd:root,execArgv:['--max-old-space-size=512'],stdio:['ignore','ignore','pipe','ipc'],windowsHide:true});
|
|
16
|
+
let details='',healthyAt=0;child.stderr.on('data',chunk=>{details=(details+chunk).slice(-2048);});
|
|
17
|
+
child.on('message',message=>{if(message?.type==='diagnostics'&&Array.isArray(message.errors)){healthyAt ||= Date.now();notify(message.errors.filter(value=>typeof value==='string').slice(0,40));}});
|
|
18
|
+
child.on('error',error=>{notify(['TypeScript checker: '+error.message]);});
|
|
19
|
+
child.once('exit',()=>{if(closed)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);});
|
|
20
|
+
}
|
|
21
|
+
launch();return {async close(){closed=true;clearTimeout(timer);const old=child;if(!old||old.exitCode!==null||old.signalCode)return;await new Promise(resolve=>{const timer=setTimeout(()=>old.kill('SIGKILL'),3000);old.once('exit',()=>{clearTimeout(timer);resolve();});if(old.connected)old.disconnect();else old.kill();});}};
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
// macOS native file notifications can miss an edit while the watch program is
|
|
4
|
+
// compiling. Use TypeScript's adaptive metadata polling for project inputs;
|
|
5
|
+
// installed dependencies keep native watchers and incur no extra polling.
|
|
6
|
+
export function createTypeScriptWatchSystem(ts,root,{platform=process.platform,env=process.env}={}){
|
|
7
|
+
const system=ts.sys;
|
|
8
|
+
if(platform!=='darwin'||env.TSC_WATCHFILE||env.TSC_NONPOLLING_WATCHER)return system;
|
|
9
|
+
const requestedRoot=path.resolve(root);
|
|
10
|
+
const physical=filename=>{try{return path.resolve(system.realpath?.(filename)||filename);}catch{return path.resolve(filename);}};
|
|
11
|
+
const physicalRoot=physical(requestedRoot),normalize=filename=>system.useCaseSensitiveFileNames?filename:filename.toLowerCase();
|
|
12
|
+
const inside=(base,filename)=>{
|
|
13
|
+
const relative=path.relative(normalize(base),normalize(filename));
|
|
14
|
+
return relative&&relative!=='..'&&!relative.startsWith('..'+path.sep)&&!path.isAbsolute(relative)&&!relative.split(path.sep).includes('node_modules');
|
|
15
|
+
};
|
|
16
|
+
return {...system,watchFile(filename,callback,interval,options){
|
|
17
|
+
const absolute=path.resolve(filename);
|
|
18
|
+
if(options?.watchFile===undefined&&(inside(requestedRoot,absolute)||inside(physicalRoot,absolute))&&inside(physicalRoot,physical(absolute)))
|
|
19
|
+
options={...options,watchFile:ts.WatchFileKind.DynamicPriorityPolling};
|
|
20
|
+
return system.watchFile(filename,callback,interval,options);
|
|
21
|
+
}};
|
|
22
|
+
}
|
package/build/typescript.mjs
CHANGED
|
@@ -10,11 +10,12 @@ const execute = promisify(execFile);
|
|
|
10
10
|
export function validateTypeScript(input) {
|
|
11
11
|
if (input === undefined) return undefined;
|
|
12
12
|
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('typescript must be an object');
|
|
13
|
-
for (const key of Object.keys(input)) if (!['ignoreBuildErrors', 'tsconfigPath'].includes(key)) throw new TypeError(`typescript.${key} is not supported`);
|
|
13
|
+
for (const key of Object.keys(input)) if (!['ignoreBuildErrors', 'tsconfigPath', 'devChecks'].includes(key)) throw new TypeError(`typescript.${key} is not supported`);
|
|
14
14
|
const { ignoreBuildErrors = false, tsconfigPath = 'tsconfig.json' } = input;
|
|
15
15
|
if (typeof ignoreBuildErrors !== 'boolean') throw new TypeError('typescript.ignoreBuildErrors must be a boolean');
|
|
16
16
|
if (typeof tsconfigPath !== 'string' || !tsconfigPath || tsconfigPath.includes('\0') || Buffer.byteLength(tsconfigPath) > 4096) throw new TypeError('typescript.tsconfigPath must be a nonempty filesystem path of at most 4096 bytes');
|
|
17
|
-
|
|
17
|
+
if(input.devChecks!==undefined&&typeof input.devChecks!=='boolean')throw new TypeError('typescript.devChecks must be a boolean');
|
|
18
|
+
return { ignoreBuildErrors, tsconfigPath, ...(input.devChecks===undefined?{}:{devChecks:input.devChecks}) };
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export async function projectTsconfig(root, config) {
|
|
@@ -32,8 +33,8 @@ export async function projectTsconfig(root, config) {
|
|
|
32
33
|
|
|
33
34
|
/** Run the checker outside the build/dev process so its graph is released on exit.
|
|
34
35
|
* Never emit files, rewrite the user's config, or install packages implicitly. */
|
|
35
|
-
export async function checkTypeScript(root, config, { dev = false, tsconfig } = {}) {
|
|
36
|
-
if (dev || !config.typescript || config.typescript.ignoreBuildErrors) return;
|
|
36
|
+
export async function checkTypeScript(root, config, { dev = false, tsconfig, force = false } = {}) {
|
|
37
|
+
if (!force && (dev || !config.typescript || config.typescript.ignoreBuildErrors)) return;
|
|
37
38
|
tsconfig ??= await projectTsconfig(root, config);
|
|
38
39
|
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.');
|
|
39
40
|
let compiler;
|
package/build/webpack-graph.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {build as esbuild, transform} from 'esbuild';
|
|
2
|
-
import {mkdir, mkdtemp, readFile, writeFile, rm, realpath} from 'node:fs/promises';
|
|
2
|
+
import {mkdir, mkdtemp, readFile, writeFile, rm, rmdir, realpath} from 'node:fs/promises';
|
|
3
3
|
import {createHash, randomUUID} from 'node:crypto';
|
|
4
4
|
import {createRequire} from 'node:module';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { cachedAsyncTransform } from './transform-cache.mjs';
|
|
8
8
|
import {inlineSourceMap} from './source-maps.mjs';
|
|
9
|
+
import {buildCacheIdentity} from './persistent-cache.mjs';
|
|
9
10
|
|
|
10
11
|
const require = createRequire(import.meta.url);
|
|
11
12
|
const {active} = require('./webpack-bridge.cjs');
|
|
@@ -13,6 +14,107 @@ const loaderPath = fileURLToPath(new URL('./webpack-loader.cjs', import.meta.url
|
|
|
13
14
|
const hash = value => createHash('sha256').update(value).digest('hex').slice(0, 20);
|
|
14
15
|
const slash = value => value.replaceAll(path.sep, '/');
|
|
15
16
|
const safeAsset = name => { if (path.isAbsolute(name) || /[\\\0?#]/.test(name) || name.split('/').some(part => !part || part === '.' || part === '..')) throw new Error(`Unsafe webpack asset path: ${name}`); };
|
|
17
|
+
async function discardPrivateFile(file,boundary){
|
|
18
|
+
await rm(file,{force:true});
|
|
19
|
+
for(let directory=path.dirname(file);directory!==boundary&&directory.startsWith(boundary+path.sep);directory=path.dirname(directory)){
|
|
20
|
+
try{await rmdir(directory);}catch(error){if(['ENOTEMPTY','EEXIST'].includes(error.code))break;if(error.code!=='ENOENT')throw error;}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const sessions=new Map(),sessionLimit=6,sessionBuildLimit=64,objectIds=new WeakMap();
|
|
24
|
+
let nextId=0,created=0,reused=0,graphQueue=Promise.resolve();
|
|
25
|
+
const objectId=value=>{if(!objectIds.has(value))objectIds.set(value,++nextId);return objectIds.get(value);};
|
|
26
|
+
// Arbitrary plugin closures are not interchangeable just because their source
|
|
27
|
+
// text matches. Only known webpack constructors with the same options may be
|
|
28
|
+
// recreated; other plugins must retain their actual instance identity.
|
|
29
|
+
function fingerprint(value,seen=new Set()){
|
|
30
|
+
if(typeof value==='function')return ['function',objectId(value)];
|
|
31
|
+
if(value instanceof RegExp)return ['regexp',value.source,value.flags];
|
|
32
|
+
if(value===undefined)return ['undefined'];
|
|
33
|
+
if(typeof value==='symbol')throw new Error('Opaque symbol in webpack configuration');
|
|
34
|
+
if(typeof value==='bigint')return ['bigint',String(value)];
|
|
35
|
+
if(typeof value==='number'&&(!Number.isFinite(value)||Object.is(value,-0)))return ['number',String(value),Object.is(value,-0)];
|
|
36
|
+
if(!value||typeof value!=='object')return value;
|
|
37
|
+
if(seen.has(value))return ['object',objectId(value)];
|
|
38
|
+
seen.add(value);
|
|
39
|
+
const properties=(skipIndices=false)=>{
|
|
40
|
+
const keys=Reflect.ownKeys(value);
|
|
41
|
+
if(keys.length>10000||keys.some(key=>typeof key==='symbol'))throw new Error('Opaque or excessive webpack configuration');
|
|
42
|
+
return keys.filter(key=>!skipIndices||!/^(?:0|[1-9]\d*)$/.test(key)).sort().map(key=>{
|
|
43
|
+
const descriptor=Object.getOwnPropertyDescriptor(value,key);if(descriptor.get||descriptor.set)throw new Error('Accessor in webpack configuration');
|
|
44
|
+
return[key,descriptor.enumerable,descriptor.configurable,descriptor.writable,fingerprint(descriptor.value,seen)];
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const prototype=Object.getPrototypeOf(value);let result;
|
|
48
|
+
if(Array.isArray(value))result=['array',properties()];
|
|
49
|
+
else if(prototype===Map.prototype){
|
|
50
|
+
const extras=properties();
|
|
51
|
+
if(value.size>10000)throw new Error('Excessive webpack Map configuration');
|
|
52
|
+
const key=item=>item&&(typeof item==='object'||typeof item==='function')?['identity',objectId(item),fingerprint(item,seen)]:fingerprint(item,seen);
|
|
53
|
+
result=['map',[...Map.prototype.entries.call(value)].map(([name,item])=>[key(name),fingerprint(item,seen)]),extras];
|
|
54
|
+
}else if(prototype===Set.prototype){
|
|
55
|
+
const extras=properties();
|
|
56
|
+
if(value.size>10000)throw new Error('Excessive webpack Set configuration');
|
|
57
|
+
result=['set',[...Set.prototype.values.call(value)].map(item=>item&&(typeof item==='object'||typeof item==='function')?['identity',objectId(item),fingerprint(item,seen)]:fingerprint(item,seen)),extras];
|
|
58
|
+
}else if(prototype===Date.prototype)result=['date',String(Date.prototype.getTime.call(value)),properties()];
|
|
59
|
+
else if(prototype===ArrayBuffer.prototype){
|
|
60
|
+
if(value.byteLength>1024*1024)throw new Error('Excessive webpack binary configuration');
|
|
61
|
+
result=['array-buffer',Buffer.from(value).toString('base64'),value.resizable,value.maxByteLength,properties()];
|
|
62
|
+
}else if(ArrayBuffer.isView(value)){
|
|
63
|
+
if(value.byteLength>10000)throw new Error('Excessive webpack binary view configuration');
|
|
64
|
+
const types=[Buffer,Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array,DataView];
|
|
65
|
+
const type=types.find(type=>prototype===type.prototype);
|
|
66
|
+
if(!type)throw new Error('Opaque webpack binary configuration');
|
|
67
|
+
result=[type.name,fingerprint(value.buffer,seen),value.byteOffset,value.byteLength,properties(true)];
|
|
68
|
+
}else if(prototype===Object.prototype||prototype===null)result=['record',prototype===null,properties()];
|
|
69
|
+
else throw new Error('Opaque webpack configuration instance');
|
|
70
|
+
seen.delete(value);return result;
|
|
71
|
+
}
|
|
72
|
+
function pluginIdentity(plugin,webpack){
|
|
73
|
+
const fields={NormalModuleReplacementPlugin:['resourceRegExp','newResource'],IgnorePlugin:['options'],ProvidePlugin:['definitions'],DefinePlugin:['definitions'],BannerPlugin:['options']};
|
|
74
|
+
for(const[name,keys]of Object.entries(fields))if(plugin.constructor===webpack[name])return[name,fingerprint(Object.fromEntries(keys.map(key=>[key,plugin[key]])))];
|
|
75
|
+
return ['instance',objectId(plugin),fingerprint(plugin)];
|
|
76
|
+
}
|
|
77
|
+
export const webpackCompilerStats=()=>({contexts:sessions.size,created,reused,limit:sessionLimit,buildLimit:sessionBuildLimit});
|
|
78
|
+
function queued(callback){const task=graphQueue.then(callback);graphQueue=task.catch(()=>{});return task;}
|
|
79
|
+
export function closeWebpackCompiler(root){return queued(async()=>{
|
|
80
|
+
const closing=[];for(const[key,session]of sessions)if(root===undefined||session.root===root){sessions.delete(key);closing.push(session.dispose());}
|
|
81
|
+
const results=await Promise.allSettled(closing),errors=results.filter(result=>result.status==='rejected').map(result=>result.reason);
|
|
82
|
+
if(errors.length)throw new AggregateError(errors,'Unable to dispose webpack compiler sessions');
|
|
83
|
+
});}
|
|
84
|
+
|
|
85
|
+
async function compileGraph(resolver,loads,options,{state,config,target}){
|
|
86
|
+
return queued(async()=>{
|
|
87
|
+
const webpack=(await import('webpack')).default;
|
|
88
|
+
let reusable=state.dev&&state.incremental,identity;
|
|
89
|
+
if(reusable){try{identity=await buildCacheIdentity(state.root);}catch{}if(!identity)reusable=false;}
|
|
90
|
+
let base,key;
|
|
91
|
+
if(reusable)try{
|
|
92
|
+
const normalize=value=>typeof value==='string'?value.replaceAll(state.stage,'<stage>'):Array.isArray(value)?value.map(normalize):value&&typeof value==='object'&&[Object.prototype,null].includes(Object.getPrototypeOf(value))?Object.fromEntries(Object.entries(value).map(([key,item])=>[key,normalize(item)])):value;
|
|
93
|
+
const normalized=normalize({...options,plugins:(options.plugins||[]).map(plugin=>plugin.name),stdin:options.stdin&&{...options.stdin,contents:undefined}});
|
|
94
|
+
base=hash(JSON.stringify(fingerprint([state.root,target,normalized])));
|
|
95
|
+
key=hash(JSON.stringify([base,identity,fingerprint({...config,graphPlugins:undefined}),config.graphPlugins.map(plugin=>pluginIdentity(plugin,webpack))]));
|
|
96
|
+
}catch{reusable=false;}
|
|
97
|
+
// Unknown data structures, safe mode and unavailable identities all keep
|
|
98
|
+
// compilation correct through a one-shot compiler. Hidden mutable closure
|
|
99
|
+
// 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===state.root){sessions.delete(key);await session.dispose();}
|
|
101
|
+
let session=reusable&&sessions.get(key);
|
|
102
|
+
if(session&&session.builds>=sessionBuildLimit){sessions.delete(key);await session.dispose();session=undefined;}
|
|
103
|
+
if(reusable){
|
|
104
|
+
for(const[old,previous]of sessions)if(previous.root===state.root&&(previous.identity!==identity||previous.base===base&&old!==key)){sessions.delete(old);await previous.dispose();}
|
|
105
|
+
}
|
|
106
|
+
if(session){sessions.delete(key);reused++;}
|
|
107
|
+
else{
|
|
108
|
+
if(reusable)while(sessions.size>=sessionLimit){const[old,previous]=sessions.entries().next().value;sessions.delete(old);await previous.dispose();}
|
|
109
|
+
session=await createGraphSession(state.root,{webpack,reusable,stage:state.stage});created++;
|
|
110
|
+
Object.assign(session,{root:state.root,base,identity,builds:0});
|
|
111
|
+
}
|
|
112
|
+
if(reusable)sessions.set(key,session);
|
|
113
|
+
try{session.builds++;return await session.run(resolver,loads,options,{state,config,target});}
|
|
114
|
+
catch(error){if(reusable)sessions.delete(key);await session.dispose();throw error;}
|
|
115
|
+
finally{if(!reusable)await session.dispose();}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
16
118
|
|
|
17
119
|
/** Webpack owns the actual module/chunk graph. PRNext's resolve/load transforms
|
|
18
120
|
* remain authoritative for React boundaries and the Node/Edge/browser targets.
|
|
@@ -37,27 +139,41 @@ export async function webpackGraph(options, {state, config, target}) {
|
|
|
37
139
|
finally { for (const dispose of disposes) await dispose(); }
|
|
38
140
|
}
|
|
39
141
|
|
|
40
|
-
async function
|
|
41
|
-
const webpack = (await import('webpack')).default;
|
|
142
|
+
async function createGraphSession(root,{webpack,reusable,stage}) {
|
|
42
143
|
const bridgeId = randomUUID();
|
|
43
|
-
await mkdir(path.join(
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
144
|
+
await mkdir(path.join(stage,'.entries'),{recursive:true});
|
|
145
|
+
const home=await realpath(await mkdtemp(path.join(reusable?root:path.join(stage,'.entries'),'.prnext-webpack-')));
|
|
146
|
+
const virtualDirectory=path.join(home,'modules'),virtualStage=path.join(home,'stage'),scratchOut=path.join(home,'output');
|
|
147
|
+
await mkdir(virtualDirectory,{recursive:true});
|
|
148
|
+
const projectRequire = createRequire(path.join(root,'package.json'));
|
|
149
|
+
let compiler,resolver,loads,options,state,config,target,outdir,actualOut,extension,modules,entries,runtime,disposed=false;
|
|
150
|
+
const virtualFiles=new Set(),loaderFiles=new Set(),previousOutputs=new Set();let usedVirtualFiles;
|
|
151
|
+
const fromVirtual=value=>typeof value==='string'&&(value===virtualStage||value.startsWith(virtualStage+path.sep))?state.stage+value.slice(virtualStage.length):value;
|
|
152
|
+
const toVirtual=value=>reusable&&typeof value==='string'&&(value===state.stage||value.startsWith(state.stage+path.sep))?virtualStage+value.slice(state.stage.length):value;
|
|
153
|
+
const stageFile=async file=>{
|
|
154
|
+
const mapped=toVirtual(file);if(mapped===file)return file;
|
|
155
|
+
virtualFiles.add(mapped);usedVirtualFiles.add(mapped);
|
|
156
|
+
const contents=await readFile(file);await mkdir(path.dirname(mapped),{recursive:true});
|
|
157
|
+
let previous;try{previous=await readFile(mapped);}catch(error){if(error.code!=='ENOENT')throw error;}
|
|
158
|
+
if(!previous?.equals(contents))await writeFile(mapped,contents);return mapped;
|
|
159
|
+
};
|
|
160
|
+
const run=async(nextResolver,nextLoads,nextOptions,next)=>{
|
|
161
|
+
resolver=nextResolver;loads=nextLoads;options=nextOptions;({state,config,target}=next);
|
|
162
|
+
usedVirtualFiles=new Set();
|
|
163
|
+
outdir=options.outdir||(options.outfile?path.dirname(options.outfile):scratchOut);actualOut=reusable||options.write===false?scratchOut:outdir;extension=options.outExtension?.['.js']||'.js';modules=new Map();
|
|
164
|
+
// Webpack may retain SizeOnlySource for unchanged assets and assume the last
|
|
165
|
+
// emitted file still exists. Keep current outputs until the next run finishes.
|
|
166
|
+
await mkdir(actualOut,{recursive:true});
|
|
167
|
+
entries=options.stdin?{entry:path.join(virtualDirectory,'entry.js')}:Array.isArray(options.entryPoints)?Object.fromEntries(options.entryPoints.map(file=>[path.basename(file,path.extname(file)),toVirtual(file)])):Object.fromEntries(Object.entries(options.entryPoints).map(([name,file])=>[name,toVirtual(file)]));
|
|
168
|
+
for(const file of Object.values(entries))if(file.startsWith(virtualStage+path.sep))await stageFile(fromVirtual(file));
|
|
54
169
|
if (options.stdin) {
|
|
55
170
|
const file = entries.entry;
|
|
56
171
|
await writeFile(file,options.stdin.contents);
|
|
172
|
+
virtualFiles.add(file);usedVirtualFiles.add(file);
|
|
57
173
|
modules.set(file,{path:file,namespace:'file',resolveDir:options.stdin.resolveDir || state.root,stdin:true});
|
|
58
174
|
}
|
|
59
175
|
const identify = record => record.namespace === 'file' ? slash(path.relative(state.root,record.path)) : record.namespace+':'+slash(path.relative(state.root,record.path));
|
|
60
|
-
const lookup = resource => modules.get(resource) || modules.get(resource?.split('?')[0]) || {path:resource?.split('?')[0],namespace:'file',suffix:resource?.includes('?')?'?'+resource.split('?').slice(1).join('?'):''};
|
|
176
|
+
const lookup = resource => modules.get(resource) || modules.get(resource?.split('?')[0]) || {path:fromVirtual(resource?.split('?')[0]),namespace:'file',suffix:resource?.includes('?')?'?'+resource.split('?').slice(1).join('?'):''};
|
|
61
177
|
const load = async(record,source,webpackContext,sourceMap) => {
|
|
62
178
|
if (record.stdin) return {contents:source,loader:options.stdin.loader || 'js',resolveDir:record.resolveDir};
|
|
63
179
|
for (const {settings,callback,framework,configuredLoaders} of loads) {
|
|
@@ -106,16 +222,16 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
106
222
|
// A changed synchronous chunk may leave webpack's runtime bytes unchanged.
|
|
107
223
|
// Give each published build its own module cache; React's dev singletons
|
|
108
224
|
// still retain their identities across the new runtime instance.
|
|
109
|
-
|
|
225
|
+
runtime = 'webpack-runtime-'+hash(JSON.stringify([state.stage,Object.keys(entries),options.conditions,target]));
|
|
110
226
|
try {
|
|
111
|
-
compiler = webpack({name:target,context:state.root,mode:state.dev?'development':'production',
|
|
227
|
+
if(!compiler)compiler = webpack({name:target,context:state.root,mode:state.dev?'development':'production',
|
|
112
228
|
...(config.watchOptions ? {watchOptions:config.watchOptions} : {}),
|
|
113
|
-
target:target==='node'?'node22':['web','es2022'],entry:entries,cache:false,devtool:options.sourcemap?'source-map':false,
|
|
229
|
+
target:target==='node'?'node22':['web','es2022'],entry:entries,cache:reusable?{type:'memory',maxGenerations:1}:false,devtool:options.sourcemap?'source-map':false,
|
|
114
230
|
experiments:{outputModule:true,css:true},externalsType:'module',
|
|
115
231
|
output:{path:actualOut,filename:options.outfile?path.basename(options.outfile):naming(options.entryNames)+extension,
|
|
116
232
|
chunkFilename:naming(options.chunkNames || 'chunk-[hash]')+extension,cssFilename:naming(options.entryNames)+'.css',
|
|
117
233
|
cssChunkFilename:'style-[contenthash:16].css',module:true,library:{type:'module'},chunkFormat:'module',chunkLoading:'import',
|
|
118
|
-
publicPath:target==='node'?'./':state.assetBase+'/',environment:{module:true,dynamicImport:true},clean:false},
|
|
234
|
+
publicPath:target==='node'?'./':state.assetBase+'/',environment:{module:true,dynamicImport:true},compareBeforeEmit:false,clean:false},
|
|
119
235
|
performance:false,
|
|
120
236
|
optimization:{minimize:!!options.minify,minimizer:[{apply(compiler){
|
|
121
237
|
compiler.hooks.compilation.tap('PRNextMinify',compilation=>{
|
|
@@ -130,10 +246,10 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
130
246
|
}
|
|
131
247
|
});
|
|
132
248
|
});
|
|
133
|
-
}}],runtimeChunk:options.splitting?{name:runtime}:false,
|
|
249
|
+
}}],runtimeChunk:options.splitting?{name:()=>runtime}:false,
|
|
134
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},
|
|
135
251
|
resolve:{alias:config.resolution.resolveAlias || {},conditionNames:[...(options.conditions || []),'...'],extensions:config.resolution.resolveExtensions || ['.tsx','.ts','.jsx','.js','.mjs','.json'],fullySpecified:false},
|
|
136
|
-
module:{rules:[{test:/./,type:'javascript/auto',enforce:'post',use:[{loader:loaderPath,options:{bridge:bridgeId}}]},
|
|
252
|
+
module:{unsafeCache:false,rules:[{test:/./,type:'javascript/auto',enforce:'post',use:[{loader:loaderPath,options:{bridge:bridgeId}}]},
|
|
137
253
|
{test:/\.json$/,type:'json'},{test:/\.(?:css|scss|sass)$/,type:'css'},
|
|
138
254
|
{dependency:'url',type:'asset/resource'}]},
|
|
139
255
|
plugins:[...config.graphPlugins,
|
|
@@ -143,7 +259,7 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
143
259
|
{apply(compiler) {
|
|
144
260
|
if (state.devInputs && config.graphPlugins.some(plugin=>plugin.constructor?.name==='ContentlayerWebpackPlugin')) {
|
|
145
261
|
compiler.hooks.beforeCompile.tapPromise({name:'PRNextGeneratedInputs',stage:Number.MAX_SAFE_INTEGER},()=>
|
|
146
|
-
state.generatedInputs ||= state.devInputs.captureDirectory('.contentlayer/generated'));
|
|
262
|
+
state.devInputs&&(state.generatedInputs ||= state.devInputs.captureDirectory('.contentlayer/generated')));
|
|
147
263
|
}
|
|
148
264
|
compiler.hooks.normalModuleFactory.tap('PRNextFramework',factory=>{
|
|
149
265
|
factory.hooks.beforeResolve.tapPromise({name:'PRNextFramework',stage:10000},async data=>{
|
|
@@ -151,12 +267,12 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
151
267
|
const importer = lookup(data.contextInfo.issuer);
|
|
152
268
|
const dependency = data.dependencies[0];
|
|
153
269
|
const kind = dependency?.type?.includes('url') ? 'url-token' : dependency?.type==='css @import'?'import-rule':data.dependencyType==='commonjs'?'require-call':dependency?.type==='import()'?'dynamic-import':data.contextInfo.issuer?'import-statement':'entry-point';
|
|
154
|
-
const settings={resolveDir:importer.resolveDir || data.context || state.root,importer:importer.path || '',namespace:importer.namespace || 'file',kind};
|
|
270
|
+
const settings={resolveDir:fromVirtual(importer.resolveDir || data.context || state.root),importer:importer.path || '',namespace:importer.namespace || 'file',kind};
|
|
155
271
|
// Let webpack resolve/execute the inline loader chain itself. Only
|
|
156
272
|
// its resource passes through framework resolution.
|
|
157
273
|
const bang=data.request.lastIndexOf('!');
|
|
158
274
|
const inline=bang<0?'':data.request.slice(0,bang+1);
|
|
159
|
-
const request=bang<0?data.request:data.request.slice(bang+1);
|
|
275
|
+
const request=fromVirtual(bang<0?data.request:data.request.slice(bang+1));
|
|
160
276
|
const resolved = await resolver.resolve(request,settings);
|
|
161
277
|
if (resolved.errors.length) throw new Error(resolved.errors.map(error=>error.text).join('\n'));
|
|
162
278
|
if (resolved.external) {
|
|
@@ -174,6 +290,9 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
174
290
|
if(record.namespace!=='file'){
|
|
175
291
|
resource=path.join(virtualDirectory,hash(JSON.stringify([record.namespace,record.path,record.suffix]))+(/css$/.test(record.namespace)?'.css':'.js'));
|
|
176
292
|
if(!modules.has(resource))await writeFile(resource,'');
|
|
293
|
+
virtualFiles.add(resource);usedVirtualFiles.add(resource);
|
|
294
|
+
}else if(toVirtual(record.path)!==record.path){
|
|
295
|
+
resource=await stageFile(record.path)+(record.suffix||'');
|
|
177
296
|
}
|
|
178
297
|
data.request=inline+resource;
|
|
179
298
|
modules.set(data.request,record);
|
|
@@ -206,6 +325,11 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
206
325
|
});
|
|
207
326
|
}},
|
|
208
327
|
]});
|
|
328
|
+
// Native loader modules live outside webpack's graph cache. Reload tracked
|
|
329
|
+
// implementations so editing a loader cannot retain its require() closure.
|
|
330
|
+
const clearLoader=file=>{const loaded=require.cache[file];delete require.cache[file];for(const child of loaded?.children||[])if(child.filename!==loaderPath)clearLoader(child.filename);};
|
|
331
|
+
for(const file of loaderFiles)clearLoader(file);
|
|
332
|
+
compiler.inputFileSystem.purge?.();
|
|
209
333
|
const stats=await new Promise((resolve,reject)=>compiler.run((error,stats)=>error?reject(error):resolve(stats)));
|
|
210
334
|
if(stats.hasErrors())throw new Error(stats.toString({all:false,errors:true,errorDetails:true}));
|
|
211
335
|
for(const warning of stats.compilation.warnings){
|
|
@@ -218,10 +342,11 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
218
342
|
for(const chunk of compilation.chunks)for(const file of chunk.files)chunkFiles.add(file);
|
|
219
343
|
for(const [name,entry] of compilation.entrypoints){
|
|
220
344
|
const file=[...entry.getEntrypointChunk().files].find(file=>file.endsWith(extension));
|
|
221
|
-
if(file)entryFiles.set(file,entries[name]);
|
|
345
|
+
if(file)entryFiles.set(file,fromVirtual(entries[name]));
|
|
222
346
|
const css=entry.getFiles().find(file=>file.endsWith('.css'));if(file&&css)styles.set(file,css);
|
|
223
347
|
}
|
|
224
348
|
for(const module of compilation.modules){
|
|
349
|
+
for(const loader of module.loaders||[])if(loader.loader!==loaderPath)loaderFiles.add(loader.loader);
|
|
225
350
|
if(!module.resource)continue;
|
|
226
351
|
const record=lookup(module.resource), imports=[];
|
|
227
352
|
for(const connection of compilation.moduleGraph.getOutgoingConnections(module)){
|
|
@@ -234,6 +359,7 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
234
359
|
safeAsset(asset.name);
|
|
235
360
|
const contents=await readFile(path.join(actualOut,asset.name));
|
|
236
361
|
const file=path.join(outdir,asset.name), key=slash(path.relative(state.root,file));
|
|
362
|
+
if(options.write!==false&&file!==path.join(actualOut,asset.name)){await mkdir(path.dirname(file),{recursive:true});await writeFile(file,contents);}
|
|
237
363
|
outputs[key]={bytes:contents.length,imports:[],exports:[],inputs:inputSizes,
|
|
238
364
|
...(entryFiles.has(asset.name)?{entryPoint:slash(path.relative(state.root,entryFiles.get(asset.name)))}:{}),
|
|
239
365
|
...(styles.has(asset.name)?{cssBundle:slash(path.relative(state.root,path.join(outdir,styles.get(asset.name))))}:{})};
|
|
@@ -248,9 +374,20 @@ async function compileGraph(resolver, loads, options, {state,config,target}) {
|
|
|
248
374
|
}
|
|
249
375
|
const rank = file => entryFiles.has(slash(path.relative(outdir,file.path))) ? 0 : file.path.endsWith(extension) ? 1 : 2;
|
|
250
376
|
outputFiles.sort((a,b)=>rank(a)-rank(b));
|
|
377
|
+
if(reusable){
|
|
378
|
+
const currentOutputs=new Set(compilation.getAssets().map(asset=>asset.name));
|
|
379
|
+
for(const name of previousOutputs)if(!currentOutputs.has(name))await discardPrivateFile(path.join(actualOut,name),actualOut);
|
|
380
|
+
previousOutputs.clear();for(const name of currentOutputs)previousOutputs.add(name);
|
|
381
|
+
}
|
|
251
382
|
return {errors:[],warnings:[],metafile:{inputs,outputs},...(options.write===false?{outputFiles}:{})};
|
|
252
383
|
} finally {
|
|
253
|
-
|
|
254
|
-
|
|
384
|
+
active.delete(bridgeId);
|
|
385
|
+
for(const file of virtualFiles)if(!usedVirtualFiles.has(file)){virtualFiles.delete(file);await discardPrivateFile(file,home);}
|
|
255
386
|
}
|
|
387
|
+
};
|
|
388
|
+
return {run,async dispose(){
|
|
389
|
+
if(disposed)return;disposed=true;
|
|
390
|
+
try{if(compiler)await new Promise((resolve,reject)=>compiler.close(error=>error?reject(error):resolve()));}
|
|
391
|
+
finally{active.delete(bridgeId);await rm(home,{recursive:true,force:true});compiler=resolver=state=config=options=modules=undefined;loads=[];virtualFiles.clear();loaderFiles.clear();previousOutputs.clear();}
|
|
392
|
+
}};
|
|
256
393
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function validateWorkerPreload(value){
|
|
2
|
+
if(value===undefined||value===false)return undefined;
|
|
3
|
+
if(!value||typeof value!=='object'||Array.isArray(value))throw new TypeError('workerPreload must be false or an object');
|
|
4
|
+
for(const key of Object.keys(value))if(!['routes','maxRssMb','timeoutMs'].includes(key))throw new TypeError(`workerPreload.${key} is not supported`);
|
|
5
|
+
if(!Array.isArray(value.routes)||!value.routes.length||value.routes.length>16||value.routes.some(route=>typeof route!=='string'||!route.startsWith('/')||route.length>1024||/[?#\x00-\x1f]/.test(route))||new Set(value.routes).size!==value.routes.length)throw new TypeError('workerPreload.routes must contain 1 to 16 distinct route patterns');
|
|
6
|
+
const options={routes:value.routes,maxRssMb:value.maxRssMb??256,timeoutMs:value.timeoutMs??5000};
|
|
7
|
+
for(const [key,min,max] of [['maxRssMb',16,2048],['timeoutMs',100,30000]])if(!Number.isInteger(options[key])||options[key]<min||options[key]>max)throw new TypeError(`workerPreload.${key} must be between ${min} and ${max}`);
|
|
8
|
+
return options;
|
|
9
|
+
}
|