@thomas.f/prnext 0.1.12-alpha → 0.1.13-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 +262 -33
- package/build/actions.mjs +4 -0
- package/build/app-config.mjs +2 -0
- package/build/app.mjs +20 -5
- package/build/bundle-runtime.mjs +82 -0
- package/build/compiler.mjs +3 -1
- package/build/config.mjs +9 -2
- package/build/font-cache.mjs +50 -0
- package/build/font-files.mjs +5 -3
- package/build/fonts.mjs +1 -1
- package/build/index.mjs +5 -0
- package/build/metadata-conflicts.mjs +13 -0
- package/build/metadata-images.mjs +3 -3
- package/build/metadata-routes.mjs +3 -3
- package/build/native-packages.mjs +5 -1
- package/build/npm-packages.mjs +2 -1
- package/build/optimize-imports.mjs +112 -0
- package/build/proxy-body-config.mjs +10 -0
- package/build/scan.mjs +9 -7
- package/cli.mjs +16 -0
- package/compat/after.cjs +51 -0
- package/compat/index.d.ts +3 -1
- package/compat/server.cjs +2 -1
- package/compat/server.d.cts +2 -0
- package/native/launch.mjs +19 -0
- package/package.json +7 -7
- package/runtime/api.mjs +15 -5
- package/runtime/app-render.mjs +78 -33
- package/runtime/app-route-plan.mjs +45 -0
- package/runtime/app-routing-state.mjs +101 -21
- package/runtime/app-routing.mjs +37 -18
- package/runtime/app-segments.mjs +25 -11
- package/runtime/app-static.mjs +1 -1
- package/runtime/app-stream-html.mjs +2 -1
- package/runtime/cache-request.mjs +10 -2
- package/runtime/cloud/archive.mjs +316 -0
- package/runtime/cloud/client.mjs +658 -0
- package/runtime/cloud/environment.mjs +60 -0
- package/runtime/cloud/state.mjs +141 -0
- package/runtime/cloud/upload.mjs +48 -0
- package/runtime/html-envelope.mjs +70 -7
- package/runtime/http.mjs +7 -4
- package/runtime/middleware.mjs +3 -3
- package/runtime/request-transport.mjs +1 -1
- package/runtime/route-static.mjs +1 -1
- package/runtime/rsc-worker.mjs +27 -4
- package/runtime/stream-utils.mjs +22 -3
- package/runtime/transport.mjs +16 -0
- package/runtime/worker-metadata.mjs +23 -0
- package/runtime/worker.mjs +36 -9
package/runtime/worker.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// prnext-concurrency:512
|
|
3
3
|
import { requestFrames } from './request-transport.mjs';
|
|
4
4
|
import { requestWork } from './request-work.mjs';
|
|
5
|
+
import { freezeClientModules } from './worker-metadata.mjs';
|
|
5
6
|
import { AsyncResource } from 'node:async_hooks';
|
|
6
7
|
const workScope = new AsyncResource('PRNextWorkObserver', { triggerAsyncId: 0 });
|
|
7
8
|
import { readFile } from 'node:fs/promises';
|
|
@@ -35,6 +36,7 @@ loadEnvConfig(projectRoot, { dev: !!manifest.dev });
|
|
|
35
36
|
// browser graph and the RSC worker's mode, including dev launched from CI.
|
|
36
37
|
process.env.NODE_ENV = manifest.dev ? 'development' : 'production';
|
|
37
38
|
const production = !manifest.dev;
|
|
39
|
+
if (production) freezeClientModules(manifest.app?.clientModules);
|
|
38
40
|
// Load each renderer beside the application only when that request needs it.
|
|
39
41
|
// Middleware and API workers do not pay for React or the Pages/App renderers.
|
|
40
42
|
const runtimeModule = async name => {
|
|
@@ -58,6 +60,7 @@ let incrementalRuntime;
|
|
|
58
60
|
|
|
59
61
|
async function handleRequest(input, output, signalController, responseComplete) {
|
|
60
62
|
let responseStarted = false;
|
|
63
|
+
const finalizers = new Set();
|
|
61
64
|
try {
|
|
62
65
|
if (input.renderMode === 'incremental-cache' && input.routeId === '__prnext_incremental_cache') {
|
|
63
66
|
incrementalRuntime ??= await runtimeModule('incremental-cache');
|
|
@@ -76,7 +79,7 @@ async function handleRequest(input, output, signalController, responseComplete)
|
|
|
76
79
|
if (route.router !== 'app' && !['isr', 'error404', 'error500'].includes(input.renderMode) && route.fallback === false && !prerendered.has(requestedPath) && !prerendered.has(requestedPath.replace(/\/$/, '') || '/')) {
|
|
77
80
|
await output(input.id, { status: 404, body: 'Not Found' }, input); return;
|
|
78
81
|
}
|
|
79
|
-
const options = { ...input, modulePath: path.resolve(distDir, route.module), route, production, manifest, distDir, signal: signalController.signal };
|
|
82
|
+
const options = { ...input, modulePath: path.resolve(distDir, route.module), route, production, manifest, distDir, signal: signalController.signal, deferFinalizer: finalize => finalizers.add(finalize) };
|
|
80
83
|
let result;
|
|
81
84
|
if (isMiddleware) {
|
|
82
85
|
middlewareRuntime ??= await import(pathToFileURL(path.join(distDir, 'runtime/middleware.mjs')).href);
|
|
@@ -105,11 +108,9 @@ async function handleRequest(input, output, signalController, responseComplete)
|
|
|
105
108
|
input.body = undefined;
|
|
106
109
|
options.body = undefined;
|
|
107
110
|
responseStarted = true;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
finally { await result.finalizeCache?.(); }
|
|
111
|
+
if (result.finalizeCache) finalizers.add(result.finalizeCache);
|
|
112
|
+
await output(input.id, result, { ...input, production, signalController });
|
|
113
|
+
responseComplete?.();
|
|
113
114
|
} catch (error) {
|
|
114
115
|
const cancelled = signalController.signal.aborted || error instanceof ProtocolWriteError;
|
|
115
116
|
signalController.abort(error);
|
|
@@ -122,6 +123,21 @@ async function handleRequest(input, output, signalController, responseComplete)
|
|
|
122
123
|
// for its one error/redirect response. Do not mistake that cleanup signal
|
|
123
124
|
// for a client disconnect while writing the replacement headers.
|
|
124
125
|
await output(input?.id ?? null, navigationResponse(error) || errorResponse(error, production), { ...input, production });
|
|
126
|
+
responseComplete?.();
|
|
127
|
+
} finally {
|
|
128
|
+
if (finalizers.size) {
|
|
129
|
+
const pending = (async () => {
|
|
130
|
+
for (const finalize of finalizers) {
|
|
131
|
+
try { await finalize(); }
|
|
132
|
+
catch (error) { console.error('[prnext] Post-response work failed:', error?.stack || error); }
|
|
133
|
+
}
|
|
134
|
+
})();
|
|
135
|
+
// Release this socket's response lane while retaining worker activity.
|
|
136
|
+
// Otherwise an immediate follow-up request waits behind after() work.
|
|
137
|
+
const track = signalController.signal[requestWork];
|
|
138
|
+
if (track) track(pending);
|
|
139
|
+
else await pending;
|
|
140
|
+
}
|
|
125
141
|
}
|
|
126
142
|
}
|
|
127
143
|
|
|
@@ -173,7 +189,7 @@ if (token && laneLimit > 1) {
|
|
|
173
189
|
if (invocations.size >= laneLimit) throw new Error('Worker invocation capacity exceeded');
|
|
174
190
|
controller = new AbortController();
|
|
175
191
|
const active = controller;
|
|
176
|
-
let pendingWork = 0, unsupervisedWork = 0, finished = false;
|
|
192
|
+
let pendingWork = 0, unsupervisedWork = 0, finished = false, backgroundDeadline = 0;
|
|
177
193
|
invocations.add(active);
|
|
178
194
|
announceActivity();
|
|
179
195
|
active.signal[requestWork] = (promise, options) => {
|
|
@@ -183,6 +199,10 @@ if (token && laneLimit > 1) {
|
|
|
183
199
|
// legitimate cache work merely because its HTTP body has ended.
|
|
184
200
|
const supervised = options?.supervised === true;
|
|
185
201
|
if (!supervised) unsupervisedWork++;
|
|
202
|
+
if (!supervised && Number.isFinite(options?.timeoutMs) && options.timeoutMs > 0 && options.timeoutMs <= 300000) {
|
|
203
|
+
backgroundDeadline = Math.max(backgroundDeadline, performance.now() + options.timeoutMs);
|
|
204
|
+
if (active.signal.aborted) { clearTimeout(workTimer); retireIfAbandoned(); }
|
|
205
|
+
}
|
|
186
206
|
const settled = () => {
|
|
187
207
|
pendingWork--;
|
|
188
208
|
if (!supervised) unsupervisedWork--;
|
|
@@ -199,7 +219,7 @@ if (token && laneLimit > 1) {
|
|
|
199
219
|
if (pendingWork && !unsupervisedWork) return;
|
|
200
220
|
workTimer = setTimeout(() => {
|
|
201
221
|
if (invocations.has(active) && (!pendingWork || unsupervisedWork)) process.exit(70);
|
|
202
|
-
}, 5000);
|
|
222
|
+
}, Math.max(5000, backgroundDeadline - performance.now()));
|
|
203
223
|
workTimer.unref();
|
|
204
224
|
};
|
|
205
225
|
active.signal.addEventListener('abort', retireIfAbandoned, { once: true });
|
|
@@ -209,7 +229,7 @@ if (token && laneLimit > 1) {
|
|
|
209
229
|
if (!pendingWork) { invocations.delete(active); clearTimeout(workTimer); }
|
|
210
230
|
else {
|
|
211
231
|
if (unsupervisedWork && !active.signal.aborted) {
|
|
212
|
-
workTimer = setTimeout(() => { if (invocations.has(active) && unsupervisedWork) process.exit(70); }, 30000);
|
|
232
|
+
workTimer = setTimeout(() => { if (invocations.has(active) && unsupervisedWork) process.exit(70); }, Math.max(30000, backgroundDeadline - performance.now()));
|
|
213
233
|
workTimer.unref();
|
|
214
234
|
}
|
|
215
235
|
}
|
|
@@ -259,4 +279,11 @@ if (token && laneLimit > 1) {
|
|
|
259
279
|
try { await handleRequest(input, output, new AbortController()); }
|
|
260
280
|
catch (error) { console.error('[prnext] Worker transport failed:', error?.stack || error); process.exit(1); }
|
|
261
281
|
}
|
|
282
|
+
// The maintenance/ISR transport is serial and awaits its own finalizers.
|
|
283
|
+
// RSC settlement can still outlive its terminal frame; drain it explicitly
|
|
284
|
+
// before closing the isolate's MessagePorts after parent EOF.
|
|
285
|
+
if (appRuntime || appStaticRuntime) {
|
|
286
|
+
const runtime = appRuntime || await runtimeModule('app-render');
|
|
287
|
+
await runtime.drainAppRuntime();
|
|
288
|
+
}
|
|
262
289
|
}
|