@thomas.f/prnext 0.1.20-alpha → 0.1.21-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/build/app.mjs +4 -4
- package/build/build-timings.mjs +38 -0
- package/build/bundle-runtime.mjs +8 -8
- package/build/compiler.mjs +2 -1
- package/build/flight-summary.mjs +64 -0
- package/build/index.mjs +32 -28
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +7 -2
- package/build/native-discovery.mjs +18 -6
- package/build/npm-packages.mjs +2 -2
- package/build/source-analysis.mjs +54 -1
- package/build/standalone-copy.mjs +48 -0
- package/build/standalone.mjs +2 -22
- package/build/transform-cache.mjs +17 -3
- package/cli.mjs +13 -0
- package/compat/diagnostics.cjs +5 -4
- package/compat/runtime-sample.cjs +56 -0
- package/compat/use-cache.cjs +5 -2
- package/docs/ai/compatibility.md +3 -3
- package/docs/ai/deployment.md +8 -5
- package/docs/ai/troubleshooting.md +22 -0
- package/package.json +20 -20
- package/runtime/app-client.mjs +106 -38
- package/runtime/app-render.mjs +31 -6
- package/runtime/app-stream-channel.mjs +17 -4
- package/runtime/app-stream-html.mjs +30 -11
- package/runtime/cache-components-rsc.mjs +26 -6
- package/runtime/cache-result.mjs +58 -0
- package/runtime/dev-client.mjs +26 -5
- package/runtime/dev-compiler-broker.mjs +20 -23
- package/runtime/dev-gateway.mjs +22 -6
- package/runtime/development-flight.mjs +59 -0
- package/runtime/development-queue.mjs +33 -0
- package/runtime/development.mjs +11 -9
- package/runtime/flight-diagnostics.mjs +48 -0
- package/runtime/flight-tap.mjs +10 -5
- package/runtime/html-envelope.mjs +34 -0
- package/runtime/loopback-rpc.mjs +92 -0
- package/runtime/native-ppr.mjs +57 -4
- package/runtime/persistent/client.mjs +1 -1
- package/runtime/persistent/common.mjs +7 -18
- package/runtime/persistent/daemon.mjs +1 -1
- package/runtime/persistent/native-proxy.mjs +1 -1
- package/runtime/persistent/watchdog.mjs +12 -27
- package/runtime/prewarm.mjs +35 -0
- package/runtime/rsc-worker.mjs +58 -8
- package/runtime/worker.mjs +29 -14
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {lstat, realpath, mkdir, copyFile, stat} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {mapConcurrent} from './concurrent.mjs';
|
|
4
|
+
|
|
5
|
+
const within = (root, file) => file === root || file.startsWith(root + path.sep);
|
|
6
|
+
const version = info => [info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs];
|
|
7
|
+
const unchanged = (left, right) => version(left).every((value, index) => value === version(right)[index]);
|
|
8
|
+
|
|
9
|
+
/** Materialize a finite trace, keeping links deferred until all file writes settle. */
|
|
10
|
+
export async function copyStandaloneTrace(files, {tracingRoot, relocate, maxFiles, maxBytes, concurrency = 8}, io = {lstat, realpath, mkdir, copyFile, stat}) {
|
|
11
|
+
const sources = Array.from(files);
|
|
12
|
+
if (sources.length > maxFiles) throw new Error('Standalone output exceeds 100000 files / 2 GiB. Narrow tracing includes.');
|
|
13
|
+
// Metadata can overlap, but admission and destination ordering remain exactly
|
|
14
|
+
// the trace's order. No bytes are copied before the whole budget is admitted.
|
|
15
|
+
const metadata = await mapConcurrent(sources, async source => {
|
|
16
|
+
const info = await io.lstat(source);
|
|
17
|
+
if (info.isSymbolicLink()) return {source, info, destination: await io.realpath(source)};
|
|
18
|
+
return {source, info};
|
|
19
|
+
}, concurrency);
|
|
20
|
+
let copiedFiles = 0, copiedBytes = 0;
|
|
21
|
+
const pendingLinks = [], groups = new Map();
|
|
22
|
+
for (const {source, info, destination} of metadata) {
|
|
23
|
+
if (info.isSymbolicLink() && !within(tracingRoot, source) && within(destination, tracingRoot)) continue;
|
|
24
|
+
const target = relocate(source);
|
|
25
|
+
if (++copiedFiles > maxFiles || (copiedBytes += info.size) > maxBytes) throw new Error('Standalone output exceeds 100000 files / 2 GiB. Narrow tracing includes.');
|
|
26
|
+
if (info.isSymbolicLink()) { pendingLinks.push([target, relocate(destination)]); continue; }
|
|
27
|
+
if (!info.isFile()) continue;
|
|
28
|
+
const key = process.platform === 'win32' ? target.toLowerCase() : target;
|
|
29
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
30
|
+
groups.get(key).push({source, target, info});
|
|
31
|
+
}
|
|
32
|
+
const directories = new Map();
|
|
33
|
+
await mapConcurrent(groups.values(), async entries => {
|
|
34
|
+
// Alias collisions retain the old serial last-writer order. Unrelated files
|
|
35
|
+
// share a small I/O worker set and one mkdir promise per destination parent.
|
|
36
|
+
for (const {source, target, info} of entries) {
|
|
37
|
+
const before = await io.lstat(source);
|
|
38
|
+
if (!before.isFile() || !unchanged(info, before)) throw new Error('Standalone input changed before copying.');
|
|
39
|
+
const directory = path.dirname(target);
|
|
40
|
+
if (!directories.has(directory)) directories.set(directory, io.mkdir(directory, {recursive: true}));
|
|
41
|
+
await directories.get(directory);
|
|
42
|
+
await io.copyFile(source, target);
|
|
43
|
+
const [after, copied] = await Promise.all([io.lstat(source), io.stat(target)]);
|
|
44
|
+
if (!after.isFile() || !unchanged(before, after) || !copied.isFile() || copied.size !== before.size) throw new Error('Standalone input changed while copying.');
|
|
45
|
+
}
|
|
46
|
+
}, concurrency);
|
|
47
|
+
return {copiedFiles, copiedBytes, pendingLinks};
|
|
48
|
+
}
|
package/build/standalone.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { resolveNativeBinary } from '../native/resolve.mjs';
|
|
8
8
|
import { createHash } from 'node:crypto';
|
|
9
9
|
import { createRequire } from 'node:module';
|
|
10
|
+
import {copyStandaloneTrace} from './standalone-copy.mjs';
|
|
10
11
|
|
|
11
12
|
const slash = value => value.split(path.sep).join('/');
|
|
12
13
|
const within = (root, file) => file === root || file.startsWith(root + path.sep);
|
|
@@ -219,28 +220,7 @@ export async function createStandalone({ projectRoot, stage, manifest, config })
|
|
|
219
220
|
} else traceFiles.add(target);
|
|
220
221
|
}
|
|
221
222
|
await mkdir(output, { recursive: true });
|
|
222
|
-
let copiedBytes =
|
|
223
|
-
let copiedFiles = 0;
|
|
224
|
-
const pendingLinks = [];
|
|
225
|
-
for (const source of traceFiles) {
|
|
226
|
-
const info = await lstat(source);
|
|
227
|
-
// NFT also emits ancestor aliases used by absolute workspace links, such as
|
|
228
|
-
// macOS /var -> /private/var. Their canonical children are already traced;
|
|
229
|
-
// these host directory aliases are not dependencies of the artifact.
|
|
230
|
-
// Real files and links to unrelated external workspaces still go through
|
|
231
|
-
// relocated(), which enforces the configured tracing boundary.
|
|
232
|
-
if (info.isSymbolicLink() && !within(tracingRoot, source) && within(await realpath(source), tracingRoot)) continue;
|
|
233
|
-
const target = relocated(source);
|
|
234
|
-
if (++copiedFiles > maxFiles || (copiedBytes += info.size) > maxBytes) throw new Error('Standalone output exceeds 100000 files / 2 GiB. Narrow tracing includes.');
|
|
235
|
-
if (info.isSymbolicLink()) {
|
|
236
|
-
const destination = await realpath(source);
|
|
237
|
-
pendingLinks.push([target, relocated(destination)]);
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (!info.isFile()) continue;
|
|
241
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
242
|
-
await copyFile(source, target);
|
|
243
|
-
}
|
|
223
|
+
let {copiedBytes, copiedFiles, pendingLinks} = await copyStandaloneTrace(traceFiles, {tracingRoot, relocate: relocated, maxFiles, maxBytes});
|
|
244
224
|
const owners = new Set(bridges.values());
|
|
245
225
|
const includedOwners = new Set();
|
|
246
226
|
for (const file of traceFiles) for (let dir = path.dirname(file); dir !== path.dirname(dir); dir = path.dirname(dir)) if (owners.has(dir)) includedOwners.add(dir);
|
|
@@ -25,7 +25,14 @@ export async function withPersistentTransforms(root,{enabled=true,dev=false}={},
|
|
|
25
25
|
return result;
|
|
26
26
|
}
|
|
27
27
|
function identity(kind,source,settings){return createHash('sha256').update(persistent.getStore()?.key||'memory').update(JSON.stringify([kind,settings,sourceMapsEnabled()])).update('\0').update(source).digest('hex');}
|
|
28
|
-
function
|
|
28
|
+
function discard(key){const value=entries.get(key);if(value){entries.delete(key);bytes-=value.size;}}
|
|
29
|
+
function validValue(value,validate){if(!validate)return true;try{return validate(value)===true;}catch{return false;}}
|
|
30
|
+
function diskValue(key,validate){
|
|
31
|
+
const scope=persistent.getStore();if(!scope?.values.has(key))return;
|
|
32
|
+
const value=scope.values.get(key);scope.values.delete(key);
|
|
33
|
+
if(!validValue(value,validate)){scope.bytes-=Buffer.byteLength(value);scope.dirty=true;return;}
|
|
34
|
+
scope.values.set(key,value);diskHits++;hits++;return value;
|
|
35
|
+
}
|
|
29
36
|
function remember(key,output){
|
|
30
37
|
const scope=persistent.getStore();if(!scope||typeof output!=='string')return;
|
|
31
38
|
const size=Buffer.byteLength(output);if(size>128*1024)return;
|
|
@@ -38,16 +45,23 @@ function remember(key,output){
|
|
|
38
45
|
// closure or mutable build metadata survives in the cache. Include all options
|
|
39
46
|
// affecting output in settings, including build identity and target mode.
|
|
40
47
|
export function cachedTransform(kind, source, settings, transform) {
|
|
48
|
+
return cachedValidatedTransform(kind,source,settings,undefined,transform);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Typed compiler facts may replay only structurally validated string records. */
|
|
52
|
+
export function cachedValidatedTransform(kind, source, settings, validate, transform) {
|
|
41
53
|
const key = identity(kind,source,settings);
|
|
42
54
|
const found = entries.get(key);
|
|
43
|
-
if (found) {
|
|
55
|
+
if (found && validValue(found.output,validate)) {
|
|
44
56
|
hits++; entries.delete(key); entries.set(key,found);
|
|
45
57
|
if(persistent.getStore()&&!persistent.getStore().values.has(key))remember(key,found.output);
|
|
46
58
|
return found.output;
|
|
47
59
|
}
|
|
48
|
-
|
|
60
|
+
if(found)discard(key);
|
|
61
|
+
const saved=diskValue(key,validate);if(saved!==undefined)return saved;
|
|
49
62
|
misses++;
|
|
50
63
|
const output = transform();
|
|
64
|
+
if(!validValue(output,validate))throw new TypeError('Invalid typed compiler cache result');
|
|
51
65
|
remember(key,output);
|
|
52
66
|
// UTF-16 is an upper bound for the retained string payload. Metadata adds
|
|
53
67
|
// engine overhead; the entry count also bounds that overhead.
|
package/cli.mjs
CHANGED
|
@@ -22,6 +22,7 @@ Use prnext for portable scripts; prn is a shortcut on compatible shells.
|
|
|
22
22
|
Detect a completed browser build
|
|
23
23
|
prnext build [directory] --adapter static --from .
|
|
24
24
|
Build a plain HTML/CSS/JS site
|
|
25
|
+
prnext build [directory] --timings Show time spent in React build phases
|
|
25
26
|
prnext output [directory] [--json] Read the versioned build output contract
|
|
26
27
|
prnext start [directory] [options] Start native Rust production server
|
|
27
28
|
prnext pstart [directory] [options] Start production persistently in the background
|
|
@@ -338,11 +339,16 @@ async function main() {
|
|
|
338
339
|
}
|
|
339
340
|
if (command === 'build') {
|
|
340
341
|
const options = {};
|
|
342
|
+
let showTimings = false;
|
|
341
343
|
for (let i = 0; i < args.length; i++) {
|
|
342
344
|
const flag = args[i];
|
|
343
345
|
if (flag === '--dev') options.dev = true;
|
|
344
346
|
else if (flag === '--watch') options.watch = true;
|
|
345
347
|
else if (flag === '--spa') options.spa = true;
|
|
348
|
+
else if (flag === '--timings') {
|
|
349
|
+
if (showTimings) throw new Error('Duplicate build option: --timings');
|
|
350
|
+
showTimings = true;
|
|
351
|
+
}
|
|
346
352
|
else if (flag === '--adapter' || flag === '--from') {
|
|
347
353
|
const value = args[++i];
|
|
348
354
|
if (!value || value.startsWith('-')) throw new Error(`${flag} requires a value`);
|
|
@@ -356,6 +362,7 @@ async function main() {
|
|
|
356
362
|
process.env.NODE_ENV = dev ? 'development' : 'production';
|
|
357
363
|
const started = performance.now();
|
|
358
364
|
if(options.watch){
|
|
365
|
+
if(showTimings)throw new Error('--timings reports one build. Run prnext build --timings without --watch.');
|
|
359
366
|
if(dev)throw new Error('--watch builds production; use prnext dev for development.');
|
|
360
367
|
const {watchProduction}=await import('./build/production-builder.mjs');
|
|
361
368
|
delete options.watch;await watchProduction(root,options);return;
|
|
@@ -367,11 +374,17 @@ async function main() {
|
|
|
367
374
|
else if (options.adapter === 'auto') options.adapter = 'react';
|
|
368
375
|
}
|
|
369
376
|
if (options.adapter === 'static') {
|
|
377
|
+
if(showTimings)throw new Error('--timings requires the React build adapter.');
|
|
370
378
|
const { buildStatic } = await import('./build/static-adapter.mjs');
|
|
371
379
|
await buildStatic(root, options);
|
|
372
380
|
} else {
|
|
373
381
|
const { build } = await import('./build/index.mjs');
|
|
374
382
|
const result=await build(root, { ...options, dev, envMode });
|
|
383
|
+
if(showTimings&&result.buildTimings){
|
|
384
|
+
console.log('Build timings (milliseconds; nested phases are not additive):');
|
|
385
|
+
for(const[name,phase]of Object.entries(result.buildTimings.phases))console.log(` ${name}: ${phase.milliseconds.toFixed(2)} (${phase.calls} ${phase.calls===1?'call':'calls'})`);
|
|
386
|
+
console.log(` total: ${result.buildTimings.totalMilliseconds.toFixed(2)} (excludes CLI module startup)`);
|
|
387
|
+
}
|
|
375
388
|
if(!dev&&result.compilationCache?.hits){
|
|
376
389
|
const {hits,reusedBytes}=result.compilationCache;
|
|
377
390
|
console.log(`Compilation cache: ${hits} ${hits===1?'graph':'graphs'} reused (${Math.ceil(reusedBytes/1024)} KiB).`);
|
package/compat/diagnostics.cjs
CHANGED
|
@@ -4,10 +4,11 @@ const directory = process.env.PRNEXT_INSPECT_DIR;
|
|
|
4
4
|
const { threadId } = require('node:worker_threads');
|
|
5
5
|
let queue = [], scheduled = false, writing = false, size;
|
|
6
6
|
const limit = 1024 * 1024;
|
|
7
|
-
if (directory)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
if (directory) {
|
|
8
|
+
// No GC observer or periodic sampler exists when diagnostics are disabled.
|
|
9
|
+
const sampler = require('./runtime-sample.cjs').createRuntimeSampler();
|
|
10
|
+
setInterval(() => record('process', sampler.sample()), 5000).unref();
|
|
11
|
+
}
|
|
11
12
|
function record(type, fields = {}) {
|
|
12
13
|
if (!directory || queue.length >= 256) return;
|
|
13
14
|
const line = JSON.stringify({ time: Date.now(), pid: process.pid, type, ...fields }) + '\n';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Constructed only for opt-in diagnostics. Samples contain numeric runtime
|
|
4
|
+
// counters, never application values, request context or allocation stacks.
|
|
5
|
+
function createRuntimeSampler() {
|
|
6
|
+
const { PerformanceObserver, performance } = require('node:perf_hooks');
|
|
7
|
+
const { threadId, resourceLimits } = require('node:worker_threads');
|
|
8
|
+
const { getHeapSpaceStatistics } = require('node:v8');
|
|
9
|
+
let count = 0, durationMs = 0, maxPauseMs = 0;
|
|
10
|
+
let previousTime = performance.now();
|
|
11
|
+
let previousLoop = performance.eventLoopUtilization();
|
|
12
|
+
let previousCpu = threadId === 0 ? process.cpuUsage() : undefined;
|
|
13
|
+
const consume = entries => {
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
count++;
|
|
16
|
+
durationMs += entry.duration;
|
|
17
|
+
maxPauseMs = Math.max(maxPauseMs, entry.duration);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const observer = new PerformanceObserver(list => consume(list.getEntries()));
|
|
21
|
+
observer.observe({ entryTypes: ['gc'] });
|
|
22
|
+
return {
|
|
23
|
+
sample() {
|
|
24
|
+
consume(observer.takeRecords());
|
|
25
|
+
const now = performance.now();
|
|
26
|
+
const loop = performance.eventLoopUtilization();
|
|
27
|
+
const eventLoop = performance.eventLoopUtilization(loop, previousLoop);
|
|
28
|
+
const { rss, heapUsed, heapTotal, external, arrayBuffers } = process.memoryUsage();
|
|
29
|
+
const young = getHeapSpaceStatistics().find(space => space.space_name === 'new_space');
|
|
30
|
+
const value = {
|
|
31
|
+
node: process.version, v8: process.versions.v8, threadId,
|
|
32
|
+
intervalMs: now - previousTime,
|
|
33
|
+
rssBytes: rss, heapUsedBytes: heapUsed, heapTotalBytes: heapTotal,
|
|
34
|
+
externalBytes: external, arrayBufferBytes: arrayBuffers,
|
|
35
|
+
youngSpaceBytes: young?.space_size ?? 0,
|
|
36
|
+
workerLimits: { ...resourceLimits },
|
|
37
|
+
gc: { count, durationMs, maxPauseMs },
|
|
38
|
+
eventLoop,
|
|
39
|
+
};
|
|
40
|
+
// CPU usage is process-wide, including all worker threads. Emit it only
|
|
41
|
+
// in the main isolate so aggregators cannot count the RSC CPU twice.
|
|
42
|
+
if (previousCpu) {
|
|
43
|
+
const cpu = process.cpuUsage();
|
|
44
|
+
value.cpu = { scope: 'process', userMicros: cpu.user - previousCpu.user,
|
|
45
|
+
systemMicros: cpu.system - previousCpu.system };
|
|
46
|
+
previousCpu = cpu;
|
|
47
|
+
}
|
|
48
|
+
previousLoop = loop; previousTime = now;
|
|
49
|
+
count = durationMs = maxPauseMs = 0;
|
|
50
|
+
return value;
|
|
51
|
+
},
|
|
52
|
+
close() { observer.disconnect(); },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { createRuntimeSampler };
|
package/compat/use-cache.cjs
CHANGED
|
@@ -93,14 +93,17 @@ async function invokeCache(id, kind, captures, args, callback, segment = '') {
|
|
|
93
93
|
then(_resolve, reject) { reject(new Error("searchParams cannot be read inside 'use cache'; read them outside and pass their values as arguments")); },
|
|
94
94
|
};
|
|
95
95
|
if (encoded.key.length > MAX_BYTES) throw new Error("'use cache' arguments exceed 2 MiB");
|
|
96
|
-
const
|
|
96
|
+
const format=codec===plainCodec?':v8:':codec.cacheKeyVersion==='flight-primitives-v2'?':flight-primitives-v2:':':flight:';
|
|
97
|
+
const key = createHash('sha256').update(id).update(kind).update(format).update(encoded.key).digest('hex');
|
|
97
98
|
const profiles = context.cacheLife || context.manifest?.config?.cacheLife || defaults;
|
|
98
99
|
const scope = { kind, profiles, life: { ...profiles.default }, explicit: new Set(), tags: new Set() };
|
|
99
100
|
let producedMetadata;
|
|
100
101
|
const producer = async () => runCacheScope(async () => {
|
|
101
102
|
const value = await codec.render(() => callback(...encoded.values), encoded, context);
|
|
102
103
|
producedMetadata = metadata(scope, context);
|
|
103
|
-
|
|
104
|
+
// envelope owns its result immediately; an already owned codec Buffer need
|
|
105
|
+
// not be copied into another temporary Buffer before this synchronous copy.
|
|
106
|
+
return envelope(producedMetadata, Buffer.isBuffer(value)?value:Buffer.from(value));
|
|
104
107
|
}, scope);
|
|
105
108
|
let bytes, handlerMetadata;
|
|
106
109
|
const custom = kind !== 'private' && !context.draftMode && Object.hasOwn(configured(context), kind);
|
package/docs/ai/compatibility.md
CHANGED
|
@@ -104,7 +104,7 @@ implemented through PRNext's generated route registry. Do not copy a full
|
|
|
104
104
|
Next.js configuration template into a new application.
|
|
105
105
|
|
|
106
106
|
The repository's reference tests build and run matching applications against
|
|
107
|
-
Next.js 16.4.
|
|
107
|
+
Next.js 16.4.4, including mounted App navigation, form Actions and cookies,
|
|
108
108
|
metadata, Pages locale routing, redirects, APIs and selected webpack loader
|
|
109
109
|
contracts. These executable examples cover specific behavior; they do not
|
|
110
110
|
certify every Next.js application or plugin. `NextRequest.nextUrl` understands
|
|
@@ -112,10 +112,10 @@ data-route build IDs and locales, and request-cookie mutations synchronize the
|
|
|
112
112
|
request header. Request cookies have different mutation results from response
|
|
113
113
|
cookies; do not use them interchangeably.
|
|
114
114
|
|
|
115
|
-
A locked real-application fixture additionally exercises Better Auth 1.7.
|
|
115
|
+
A locked real-application fixture additionally exercises Better Auth 1.7.15
|
|
116
116
|
(email/password, session cookies and logout), Drizzle ORM 0.45.4 over SQLite
|
|
117
117
|
through its proxy driver, next-intl 4.14.9 (server and client translations),
|
|
118
|
-
and the MDX 3.1.1 webpack loader against Next.js 16.4.
|
|
118
|
+
and the MDX 3.1.1 webpack loader against Next.js 16.4.4. It covers authenticated
|
|
119
119
|
Server Actions, user-owned rows, cookie/locale isolation, client navigation and
|
|
120
120
|
Server Component `after()` callbacks. This is evidence for those workflows,
|
|
121
121
|
not a guarantee for every plugin, provider or deployment configuration.
|
package/docs/ai/deployment.md
CHANGED
|
@@ -15,7 +15,7 @@ prnext output --json
|
|
|
15
15
|
prnext start --hostname 127.0.0.1 --port 3000
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
### Upgrading from
|
|
18
|
+
### Upgrading from an earlier alpha
|
|
19
19
|
|
|
20
20
|
Install matching framework/native versions, then rebuild and restart the complete
|
|
21
21
|
application or standalone output. Keep Node and the React protocol peers aligned
|
|
@@ -24,14 +24,17 @@ their identities match; never delete `data.sqlite3` or invalidation history to
|
|
|
24
24
|
perform an upgrade. Read the storage section below before pruning compiler caches.
|
|
25
25
|
|
|
26
26
|
Persistent apps need a supervisor restart too. In a maintenance window for the
|
|
27
|
-
whole `PRNEXT_PM_HOME`, run `prnext pdown` with that same
|
|
28
|
-
|
|
27
|
+
whole `PRNEXT_PM_HOME`, run the old installation's `prnext pdown` with that same
|
|
28
|
+
home before replacing packages. The 0.1.21 local protocol rejects an old
|
|
29
|
+
supervisor rather than silently taking it over. Shutdown stops all managed apps
|
|
30
|
+
and preserves saved settings and enabled
|
|
29
31
|
entries; `prestart` alone keeps the already loaded supervisor code. Update and
|
|
30
|
-
rebuild the installations that must return with
|
|
32
|
+
rebuild the installations that must return with the new version, then launch `pstart` or
|
|
31
33
|
`prestart` through the new CLI. It also restores all enabled entries, with their
|
|
32
34
|
saved binary paths and health routes. Check `pstatus` and real health responses;
|
|
33
35
|
a global CLI update does not replace each app's package or binary. Keep independent
|
|
34
|
-
lifecycles in separate homes.
|
|
36
|
+
lifecycles in separate homes. Do not stop unrelated Node processes or external
|
|
37
|
+
databases, Redis, proxies and OS services as an automatic upgrade fallback.
|
|
35
38
|
|
|
36
39
|
Set `server.publicOrigin` to the external HTTPS origin and `server.trustedProxies`
|
|
37
40
|
to the immediate proxy's IP/CIDR addresses. The configured origin wins; forwarded
|
|
@@ -185,6 +185,28 @@ exclude the full streaming body, and missing/dropped samples are not zero.
|
|
|
185
185
|
`prnext analyze`, after building, rather than interpreting request time as
|
|
186
186
|
compiler performance.
|
|
187
187
|
|
|
188
|
+
The `flight` section of `prnext inspect --json` groups observed App Router
|
|
189
|
+
payload sizes by route pattern and render phase. `flightP95Bytes` is the
|
|
190
|
+
uncompressed Flight size for completed samples; `responseP95Bytes` is the
|
|
191
|
+
uncompressed application output yielded by the HTML renderer. Cancelled, failed
|
|
192
|
+
and timed-out samples remain visible separately and do not count as complete
|
|
193
|
+
payloads. RSC and SSR can observe the same Flight stream: never add their sizes
|
|
194
|
+
or request counts. `modelReadyP95Ms`, `shellReadyP95Ms` and `durationP95Ms`
|
|
195
|
+
include rendering, application work and asynchronous waits, not isolated
|
|
196
|
+
encoder/decoder CPU. Missing data stays `null`. The OTLP/JSON view includes
|
|
197
|
+
`prnext.flight.response.size` in bytes with separate render-phase attributes.
|
|
198
|
+
|
|
199
|
+
Large server-generated presentation trees can produce much more Flight data
|
|
200
|
+
than compact public props. For a pure table, consider a small explicit
|
|
201
|
+
`'use client'` presentation component receiving only the public fields it needs.
|
|
202
|
+
Its initial document can still render on the server, while its implementation
|
|
203
|
+
also joins the browser bundle. Measure the resulting Flight/HTML sizes,
|
|
204
|
+
JavaScript transfer, browser execution and hydration together. Keep database
|
|
205
|
+
access, credentials and private server logic outside that boundary. A count-only
|
|
206
|
+
synthetic table does not predict savings for real row data; converting arbitrary
|
|
207
|
+
Server Components automatically is not a safe optimization. Inspecting a route
|
|
208
|
+
does not automatically rewrite it, enable caching or change its privacy policy.
|
|
209
|
+
|
|
188
210
|
Initial-JS analysis includes required hydration imports and SSR-used dynamic
|
|
189
211
|
chunks. Schema 2 reports per-prerendered-path samples and the maximum size for
|
|
190
212
|
each compression metric; a route's file list can be the union of its samples.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thomas.f/prnext",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21-alpha",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Experimental Rust-powered runtime for Next.js apps, React and npm",
|
|
6
6
|
"homepage": "https://prnext.dev",
|
|
@@ -135,38 +135,38 @@
|
|
|
135
135
|
"@edge-runtime/vm": "5.0.0",
|
|
136
136
|
"@jridgewell/trace-mapping": "0.3.31",
|
|
137
137
|
"@mapbox/node-pre-gyp": "2.0.3",
|
|
138
|
-
"@rollup/pluginutils": "5.
|
|
139
|
-
"acorn": "8.
|
|
138
|
+
"@rollup/pluginutils": "5.5.1",
|
|
139
|
+
"acorn": "8.19.0",
|
|
140
140
|
"acorn-import-attributes": "1.9.5",
|
|
141
141
|
"async-sema": "3.1.1",
|
|
142
142
|
"babel-plugin-react-compiler": "1.0.0",
|
|
143
143
|
"bindings": "1.5.0",
|
|
144
|
-
"browserslist": "4.29.
|
|
145
|
-
"dotenv": "
|
|
144
|
+
"browserslist": "4.29.5",
|
|
145
|
+
"dotenv": "18.0.9",
|
|
146
146
|
"dotenv-expand": "11.0.7",
|
|
147
147
|
"esbuild": "^0.28.2",
|
|
148
148
|
"estree-walker": "2.0.2",
|
|
149
149
|
"get-tsconfig": "4.14.3",
|
|
150
|
-
"glob": "14.0.
|
|
150
|
+
"glob": "14.0.3",
|
|
151
151
|
"graceful-fs": "4.2.11",
|
|
152
152
|
"html-to-image": "1.11.13",
|
|
153
153
|
"jsonc-parser": "3.3.1",
|
|
154
|
-
"lightningcss": "1.
|
|
154
|
+
"lightningcss": "1.33.0",
|
|
155
155
|
"loader-runner": "4.3.2",
|
|
156
|
-
"minimatch": "11.0.
|
|
156
|
+
"minimatch": "11.0.4",
|
|
157
157
|
"node-gyp-build": "4.8.4",
|
|
158
|
-
"oxc-parser": "0.
|
|
158
|
+
"oxc-parser": "0.156.0",
|
|
159
159
|
"path-to-regexp": "6.3.0",
|
|
160
160
|
"picomatch": "4.0.7",
|
|
161
|
-
"postcss": "8.5.
|
|
161
|
+
"postcss": "8.5.32",
|
|
162
162
|
"postcss-flexbugs-fixes": "5.0.2",
|
|
163
|
-
"postcss-preset-env": "11.
|
|
164
|
-
"postcss-selector-parser": "7.1.
|
|
163
|
+
"postcss-preset-env": "11.6.2",
|
|
164
|
+
"postcss-selector-parser": "7.1.7",
|
|
165
165
|
"postcss-value-parser": "4.2.0",
|
|
166
166
|
"react-refresh": "0.19.0",
|
|
167
167
|
"resolve-from": "5.0.0",
|
|
168
|
-
"schema-utils": "4.
|
|
169
|
-
"webpack": "5.
|
|
168
|
+
"schema-utils": "4.8.2",
|
|
169
|
+
"webpack": "5.112.2"
|
|
170
170
|
},
|
|
171
171
|
"peerDependencies": {
|
|
172
172
|
"@redis/client": "^6.3.0",
|
|
@@ -184,12 +184,12 @@
|
|
|
184
184
|
}
|
|
185
185
|
},
|
|
186
186
|
"optionalDependencies": {
|
|
187
|
-
"prnext-darwin-arm64": "0.1.
|
|
188
|
-
"prnext-darwin-x64": "0.1.
|
|
189
|
-
"prnext-linux-arm64-gnu": "0.1.
|
|
190
|
-
"prnext-linux-x64-gnu": "0.1.
|
|
191
|
-
"@thomas.f/prnext-win32-x64-msvc": "0.1.
|
|
192
|
-
"@thomas.f/prnext-win32-arm64-msvc": "0.1.
|
|
187
|
+
"prnext-darwin-arm64": "0.1.21-alpha",
|
|
188
|
+
"prnext-darwin-x64": "0.1.21-alpha",
|
|
189
|
+
"prnext-linux-arm64-gnu": "0.1.21-alpha",
|
|
190
|
+
"prnext-linux-x64-gnu": "0.1.21-alpha",
|
|
191
|
+
"@thomas.f/prnext-win32-x64-msvc": "0.1.21-alpha",
|
|
192
|
+
"@thomas.f/prnext-win32-arm64-msvc": "0.1.21-alpha"
|
|
193
193
|
},
|
|
194
194
|
"engines": {
|
|
195
195
|
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|