@timber-js/app 0.2.0-alpha.204 → 0.2.0-alpha.205
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/dist/_chunks/{actions-HUdJADAD.js → actions-jNrXdzgs.js} +4 -3
- package/dist/_chunks/{actions-HUdJADAD.js.map → actions-jNrXdzgs.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
- package/dist/_chunks/{build-manifest-DWppEdLB.js → build-manifest-DTmSGLRz.js} +51 -2
- package/dist/_chunks/build-manifest-DTmSGLRz.js.map +1 -0
- package/dist/_chunks/{cache-api-CAPbZTga.js → cache-api-CUD9Ezaq.js} +3 -2
- package/dist/_chunks/{cache-api-CAPbZTga.js.map → cache-api-CUD9Ezaq.js.map} +1 -1
- package/dist/_chunks/{chains-C9Y7Oyno.js → chains-BjGKq9Je.js} +2 -2
- package/dist/_chunks/{chains-C9Y7Oyno.js.map → chains-BjGKq9Je.js.map} +1 -1
- package/dist/_chunks/{cli-check-BzMGuIH6.js → cli-check-3WE_GyoL.js} +3 -3
- package/dist/_chunks/{cli-check-BzMGuIH6.js.map → cli-check-3WE_GyoL.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-DnXqcIIj.js → cli-schema-sync-D3iIZjku.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-DnXqcIIj.js.map → cli-schema-sync-D3iIZjku.js.map} +1 -1
- package/dist/_chunks/{cloudflare-DxX1SU0g.js → cloudflare-BFb__LYG.js} +3 -3
- package/dist/_chunks/{cloudflare-DxX1SU0g.js.map → cloudflare-BFb__LYG.js.map} +1 -1
- package/dist/_chunks/{convention-lint-DHOFvX5s.js → convention-lint-BnO5TyHD.js} +2 -2
- package/dist/_chunks/{convention-lint-DHOFvX5s.js.map → convention-lint-BnO5TyHD.js.map} +1 -1
- package/dist/_chunks/{dev-server-BV01iyUs.js → dev-server-Dbdgx2dD.js} +8 -195
- package/dist/_chunks/dev-server-Dbdgx2dD.js.map +1 -0
- package/dist/_chunks/{error-boundary-BQKxl6EX.js → error-boundary-BndF-3Td.js} +2 -2
- package/dist/_chunks/{error-boundary-BQKxl6EX.js.map → error-boundary-BndF-3Td.js.map} +1 -1
- package/dist/_chunks/{graph-cache-Cv3njEH8.js → graph-cache-CP4GEmf9.js} +2 -2
- package/dist/_chunks/{graph-cache-Cv3njEH8.js.map → graph-cache-CP4GEmf9.js.map} +1 -1
- package/dist/_chunks/{live-graph-DCLQzV6m.js → live-graph-BM2UbbNx.js} +3 -3
- package/dist/_chunks/{live-graph-DCLQzV6m.js.map → live-graph-BM2UbbNx.js.map} +1 -1
- package/dist/_chunks/logger-k2DQ4EUf.js +506 -0
- package/dist/_chunks/logger-k2DQ4EUf.js.map +1 -0
- package/dist/_chunks/{poison-scan-CX_NaNDi.js → poison-scan-g83aAXhs.js} +2 -2
- package/dist/_chunks/{poison-scan-CX_NaNDi.js.map → poison-scan-g83aAXhs.js.map} +1 -1
- package/dist/_chunks/{logger-DqJ2VoAY.js → primitives-DLAnvsrk.js} +17 -518
- package/dist/_chunks/primitives-DLAnvsrk.js.map +1 -0
- package/dist/_chunks/{scanner-B_tnqFcF.js → scanner-BhNruzv0.js} +3 -3
- package/dist/_chunks/{scanner-B_tnqFcF.js.map → scanner-BhNruzv0.js.map} +1 -1
- package/dist/_chunks/{walkers-Cm3PC5JT.js → walkers-BNsswm-h.js} +2 -2
- package/dist/_chunks/{walkers-Cm3PC5JT.js.map → walkers-BNsswm-h.js.map} +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +3 -3
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +3 -3
- package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/internal.js +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/overlay.d.ts +1 -21
- package/dist/dev-tools/overlay.d.ts.map +1 -1
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/als-registry.d.ts +0 -7
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +2 -6
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +180 -7
- package/dist/server/internal.js.map +1 -1
- package/dist/server/pipeline.d.ts +1 -13
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -38
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +0 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +1 -7
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +0 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/slot-subtree-contain.d.ts +1 -1
- package/dist/server/slot-subtree-contain.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/browser-entry/rsc-stream.ts +1 -82
- package/src/dev-tools/index.ts +0 -2
- package/src/dev-tools/overlay.ts +1 -65
- package/src/plugins/dev-server.ts +4 -45
- package/src/server/als-registry.ts +0 -8
- package/src/server/deny-renderer.ts +0 -7
- package/src/server/pipeline.ts +1 -14
- package/src/server/rsc-entry/deny-fallback.ts +1 -10
- package/src/server/rsc-entry/error-renderer.ts +1 -11
- package/src/server/rsc-entry/helpers.ts +0 -92
- package/src/server/rsc-entry/index.ts +1 -14
- package/src/server/rsc-entry/render-route.ts +2 -23
- package/src/server/rsc-entry/rsc-payload.ts +1 -2
- package/src/server/rsc-entry/rsc-stream.ts +3 -29
- package/src/server/rsc-entry/ssr-renderer.ts +2 -15
- package/src/server/slot-resolver.ts +7 -29
- package/src/server/slot-subtree-contain.ts +22 -14
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
- package/dist/_chunks/csp-nonce-hOGniaG4.js +0 -227
- package/dist/_chunks/csp-nonce-hOGniaG4.js.map +0 -1
- package/dist/_chunks/dev-server-BV01iyUs.js.map +0 -1
- package/dist/_chunks/logger-DqJ2VoAY.js.map +0 -1
- package/dist/dev-tools/debug-channel.d.ts +0 -55
- package/dist/dev-tools/debug-channel.d.ts.map +0 -1
- package/src/dev-tools/debug-channel.ts +0 -151
|
@@ -23,7 +23,6 @@ import { getCspNonce } from '../csp-nonce.ts';
|
|
|
23
23
|
import { renderDenyPage, renderDenyPageAsRsc } from '../deny-renderer.ts';
|
|
24
24
|
import type { DefaultPageRenderer, LayoutEntry } from '../deny-renderer.ts';
|
|
25
25
|
import type { NavContext } from '../ssr-bridge-types.ts';
|
|
26
|
-
import { createDebugChannelSink } from './helpers.ts';
|
|
27
26
|
import { isRscRequest } from '../../shared/rsc-media-type.ts';
|
|
28
27
|
import { withPublishedParams } from '../publish-params.ts';
|
|
29
28
|
import { getCookiesForSsr } from '../cookie-context.ts';
|
|
@@ -291,7 +290,6 @@ async function renderErrorPageViaRsc(
|
|
|
291
290
|
onError(err: unknown) {
|
|
292
291
|
logRenderError({ method: req.method, path: url.pathname, error: err });
|
|
293
292
|
},
|
|
294
|
-
debugChannel: createDebugChannelSink(),
|
|
295
293
|
});
|
|
296
294
|
|
|
297
295
|
const [ssrStream, inlineStream] = teeWithErrorPropagation(rscStream);
|
|
@@ -402,14 +400,7 @@ export async function renderNoMatchPage(
|
|
|
402
400
|
const match: RouteMatch = { segments, segmentParams: {}, middlewareChain: [] };
|
|
403
401
|
|
|
404
402
|
if (isRscRequest(req)) {
|
|
405
|
-
return renderDenyPageAsRsc(
|
|
406
|
-
deny,
|
|
407
|
-
segments,
|
|
408
|
-
layoutComponents,
|
|
409
|
-
req,
|
|
410
|
-
responseHeaders,
|
|
411
|
-
createDebugChannelSink
|
|
412
|
-
);
|
|
403
|
+
return renderDenyPageAsRsc(deny, segments, layoutComponents, req, responseHeaders);
|
|
413
404
|
}
|
|
414
405
|
|
|
415
406
|
return renderDenyPage(
|
|
@@ -420,7 +411,6 @@ export async function renderNoMatchPage(
|
|
|
420
411
|
match,
|
|
421
412
|
responseHeaders,
|
|
422
413
|
clientBootstrap,
|
|
423
|
-
createDebugChannelSink,
|
|
424
414
|
callSsr,
|
|
425
415
|
defaultPage
|
|
426
416
|
);
|
|
@@ -5,104 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ManifestSegmentNode } from '../route-matcher.ts';
|
|
8
|
-
import { swallow } from '../logger.ts';
|
|
9
8
|
import { computeSegmentKeys, computeSegmentTreePaths } from '../../routing/segment-keys.ts';
|
|
10
9
|
import type { SegmentInfo } from '../../shared/segment-info.ts';
|
|
11
10
|
import type { SlotSkipEntry } from '../slot-resolver.ts';
|
|
12
11
|
import { isClientReference } from '../utils/element-type.ts';
|
|
13
12
|
import { isStaticRequestDependent } from '../request-dep.ts';
|
|
14
13
|
|
|
15
|
-
/**
|
|
16
|
-
* No-op — returns undefined so no debugChannel is passed to Flight.
|
|
17
|
-
* Debug rows ride the main stream inline (React's default behavior).
|
|
18
|
-
*
|
|
19
|
-
* Production Flight emits no debug rows. In dev, inline debug rows
|
|
20
|
-
* are handled by the Flight client for owner stack resolution.
|
|
21
|
-
*
|
|
22
|
-
* A writable-only debugChannel triggers a react-server-dom-webpack
|
|
23
|
-
* >=19.3.0 Flight client regression (owner/stack refs parsed after
|
|
24
|
-
* stream close produce "Connection closed" errors).
|
|
25
|
-
*/
|
|
26
|
-
export function createDebugChannelSink(): undefined {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ─── Debug Channel Collector (dev mode only) ────────────────────────────
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parsed component debug info extracted from the Flight debug channel.
|
|
34
|
-
*
|
|
35
|
-
* Contains only component names, environment labels, and stack frames —
|
|
36
|
-
* never source code or props. See design/13-security.md §"Server source
|
|
37
|
-
* never reaches the client".
|
|
38
|
-
*/
|
|
39
|
-
export interface DebugComponentEntry {
|
|
40
|
-
name: string;
|
|
41
|
-
env: string | null;
|
|
42
|
-
key: string | null;
|
|
43
|
-
stack: unknown[] | null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Parse React Flight debug rows into component entries.
|
|
48
|
-
*
|
|
49
|
-
* The Flight debug channel writes rows in `hexId:json\n` format. Each row
|
|
50
|
-
* with a JSON object containing a `name` field is a component debug info
|
|
51
|
-
* entry. Rows without `name` (timing rows, reference rows like `D"$id"`)
|
|
52
|
-
* are skipped.
|
|
53
|
-
*
|
|
54
|
-
* Security: `props` are explicitly stripped from parsed entries — they may
|
|
55
|
-
* contain rendered output or user data. Only `name`, `env`, `key`, and
|
|
56
|
-
* `stack` are retained.
|
|
57
|
-
*/
|
|
58
|
-
export function parseDebugRows(text: string): DebugComponentEntry[] {
|
|
59
|
-
if (!text) return [];
|
|
60
|
-
|
|
61
|
-
const entries: DebugComponentEntry[] = [];
|
|
62
|
-
const lines = text.split('\n');
|
|
63
|
-
|
|
64
|
-
for (const line of lines) {
|
|
65
|
-
if (!line) continue;
|
|
66
|
-
|
|
67
|
-
// Flight row format: hexId:payload
|
|
68
|
-
const colonIdx = line.indexOf(':');
|
|
69
|
-
if (colonIdx === -1) continue;
|
|
70
|
-
|
|
71
|
-
let payload = line.slice(colonIdx + 1);
|
|
72
|
-
|
|
73
|
-
// React Flight debug rows may have a type-tag prefix before the JSON
|
|
74
|
-
// object (e.g., "D{...}" for debug info rows). Strip the single-char
|
|
75
|
-
// tag so the JSON parser sees the object. Non-JSON payloads like
|
|
76
|
-
// D"$a" reference rows are caught by the JSON.parse try/catch below.
|
|
77
|
-
// See TIM-556.
|
|
78
|
-
if (payload.length > 0 && payload[0] !== '{' && payload[0] !== '[' && payload[0] !== '"') {
|
|
79
|
-
payload = payload.slice(1);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Skip payloads that still aren't JSON objects after tag stripping
|
|
83
|
-
if (!payload.startsWith('{')) continue;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const parsed = JSON.parse(payload);
|
|
87
|
-
if (typeof parsed !== 'object' || parsed === null) continue;
|
|
88
|
-
if (typeof parsed.name !== 'string') continue;
|
|
89
|
-
|
|
90
|
-
// Strip props — may contain source-derived data or user data.
|
|
91
|
-
// Only retain: name, env, key, stack.
|
|
92
|
-
entries.push({
|
|
93
|
-
name: parsed.name,
|
|
94
|
-
env: parsed.env ?? null,
|
|
95
|
-
key: parsed.key ?? null,
|
|
96
|
-
stack: Array.isArray(parsed.stack) ? parsed.stack : null,
|
|
97
|
-
});
|
|
98
|
-
} catch (e) {
|
|
99
|
-
swallow(e, 'malformed JSON in RSC client reference row');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return entries;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
14
|
/**
|
|
107
15
|
* Build segment metadata for the X-Timber-Segments response header.
|
|
108
16
|
* Describes the rendered segment chain with request-dependency status,
|
|
@@ -60,7 +60,6 @@ import { renderNoMatchPage } from './error-renderer.ts';
|
|
|
60
60
|
import { renderRoute } from './render-route.ts';
|
|
61
61
|
import { isDebug, isDevMode, setDebugFromConfig } from '../debug.ts';
|
|
62
62
|
import { setSourceMapCallback } from '../dev-source-map.ts';
|
|
63
|
-
import { requestContextAls } from '../als-registry.ts';
|
|
64
63
|
import { capturePrebuiltPayloads } from '../prebuilt-builder.ts';
|
|
65
64
|
import { setPrebuiltPayloadSource } from '../prebuilt/payload-source.ts';
|
|
66
65
|
import { setRscFunctions, setComponentRefreshTimeout } from '../prebuilt-runtime.ts';
|
|
@@ -97,10 +96,6 @@ export function setDevSourceMapHandler(handler: (error: Error) => void): void {
|
|
|
97
96
|
setSourceMapCallback(handler);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
// Dev-only: debug components getter is stored per-request in ALS
|
|
101
|
-
// (RequestContextStore.debugComponentsGetter) to avoid cross-request
|
|
102
|
-
// race conditions. See TIM-557.
|
|
103
|
-
|
|
104
99
|
/**
|
|
105
100
|
* Create the RSC request handler from the route manifest.
|
|
106
101
|
*
|
|
@@ -299,7 +294,6 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
299
294
|
rootSegment: manifest.root,
|
|
300
295
|
buildManifest: typedBuildManifest,
|
|
301
296
|
globalError: manifest.globalError,
|
|
302
|
-
devDebugRegistry: pipelineConfig.devDebugRegistry,
|
|
303
297
|
},
|
|
304
298
|
interception
|
|
305
299
|
);
|
|
@@ -351,14 +345,7 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
351
345
|
onPipelineError: isDev
|
|
352
346
|
? (error: Error, phase: string) => {
|
|
353
347
|
if (pipelineConfig.devPipelineErrorHandler) {
|
|
354
|
-
|
|
355
|
-
// from the Flight debug channel (if available from the current request).
|
|
356
|
-
const store = requestContextAls.getStore();
|
|
357
|
-
const debugComponents =
|
|
358
|
-
phase === 'render' && store?.debugComponentsGetter
|
|
359
|
-
? store.debugComponentsGetter()
|
|
360
|
-
: undefined;
|
|
361
|
-
pipelineConfig.devPipelineErrorHandler(error, phase, debugComponents);
|
|
348
|
+
pipelineConfig.devPipelineErrorHandler(error, phase);
|
|
362
349
|
}
|
|
363
350
|
}
|
|
364
351
|
: undefined,
|
|
@@ -35,13 +35,12 @@ import { buildRouteElement, NoPageComponentError } from '../route-element-builde
|
|
|
35
35
|
import { ParamCoercionError } from '../param-coercion.ts';
|
|
36
36
|
import type { ManifestSegmentNode } from '../route-matcher.ts';
|
|
37
37
|
import { recordTiming } from '../server-timing.ts';
|
|
38
|
-
import { requestContextAls } from '../als-registry.ts';
|
|
39
38
|
import { parseClientStateTree } from '../state-tree-diff.ts';
|
|
40
39
|
|
|
41
40
|
import { getFirstDeniedSegmentIndex } from '../deny-boundary.ts';
|
|
42
41
|
import { handleApiRoute } from './api-handler.ts';
|
|
43
42
|
import { renderErrorPage, renderNoMatchPage } from './error-renderer.ts';
|
|
44
|
-
import { buildRedirectResponse
|
|
43
|
+
import { buildRedirectResponse } from './helpers.ts';
|
|
45
44
|
import { isRscRequest } from '../../shared/rsc-media-type.ts';
|
|
46
45
|
import { buildRscPayloadResponse } from './rsc-payload.ts';
|
|
47
46
|
import { renderRscStream } from './rsc-stream.ts';
|
|
@@ -60,7 +59,6 @@ export interface RenderRouteDeps {
|
|
|
60
59
|
rootSegment: ManifestSegmentNode;
|
|
61
60
|
buildManifest: BuildManifest;
|
|
62
61
|
globalError?: { load: () => Promise<unknown>; filePath: string };
|
|
63
|
-
devDebugRegistry?: import('../../dev-tools/debug-channel.ts').DebugChannelRegistry;
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
/**
|
|
@@ -199,24 +197,8 @@ export async function renderRoute(
|
|
|
199
197
|
|
|
200
198
|
// Render to RSC Flight stream with signal tracking.
|
|
201
199
|
const _rscStart = performance.now();
|
|
202
|
-
|
|
203
|
-
// requests (client navigations) don't serve HTML with __timber_debug_id,
|
|
204
|
-
// so the browser can't subscribe — skip to avoid orphaned sessions.
|
|
205
|
-
const debugRegistry = isRscRequest(req) || clientJsDisabled ? undefined : deps.devDebugRegistry;
|
|
206
|
-
const { rscStream, signals, getDebugComponents, debugRequestId } = renderRscStream(
|
|
207
|
-
payload,
|
|
208
|
-
req,
|
|
209
|
-
leafDenyChain,
|
|
210
|
-
debugRegistry
|
|
211
|
-
);
|
|
200
|
+
const { rscStream, signals } = renderRscStream(payload, req, leafDenyChain);
|
|
212
201
|
|
|
213
|
-
// Store the debug components getter in ALS so onPipelineError can
|
|
214
|
-
// include component tree context for render-phase errors (dev mode only).
|
|
215
|
-
// Per-request via ALS — no cross-request race. See TIM-557.
|
|
216
|
-
const alsStore = requestContextAls.getStore();
|
|
217
|
-
if (alsStore) {
|
|
218
|
-
alsStore.debugComponentsGetter = getDebugComponents;
|
|
219
|
-
}
|
|
220
202
|
recordTiming({
|
|
221
203
|
name: 'rsc-init',
|
|
222
204
|
dur: Math.round(performance.now() - _rscStart),
|
|
@@ -239,7 +221,6 @@ export async function renderRoute(
|
|
|
239
221
|
layoutComponents as LayoutEntry[],
|
|
240
222
|
req,
|
|
241
223
|
responseHeaders,
|
|
242
|
-
createDebugChannelSink,
|
|
243
224
|
getFirstDeniedSegmentIndex()
|
|
244
225
|
);
|
|
245
226
|
}
|
|
@@ -251,7 +232,6 @@ export async function renderRoute(
|
|
|
251
232
|
match,
|
|
252
233
|
responseHeaders,
|
|
253
234
|
clientBootstrap,
|
|
254
|
-
createDebugChannelSink,
|
|
255
235
|
callSsr,
|
|
256
236
|
undefined,
|
|
257
237
|
getFirstDeniedSegmentIndex()
|
|
@@ -310,6 +290,5 @@ export async function renderRoute(
|
|
|
310
290
|
deferSuspenseFor,
|
|
311
291
|
globalError,
|
|
312
292
|
slotSkipInfo,
|
|
313
|
-
debugRequestId,
|
|
314
293
|
});
|
|
315
294
|
}
|
|
@@ -26,7 +26,7 @@ import type { LayoutComponentEntry } from '../route-element-builder.ts';
|
|
|
26
26
|
import type { ManifestSegmentNode } from '../route-matcher.ts';
|
|
27
27
|
import type { SlotSkipEntry } from '../slot-resolver.ts';
|
|
28
28
|
|
|
29
|
-
import { buildRedirectResponse, buildSegmentInfo
|
|
29
|
+
import { buildRedirectResponse, buildSegmentInfo } from './helpers.ts';
|
|
30
30
|
import { RSC_CONTENT_TYPE } from '../../shared/rsc-media-type.ts';
|
|
31
31
|
|
|
32
32
|
import { rscErrorEnvelope } from '../rsc-error-envelope.ts';
|
|
@@ -178,7 +178,6 @@ export async function buildRscPayloadResponse(
|
|
|
178
178
|
layoutComponents as LayoutEntry[],
|
|
179
179
|
req,
|
|
180
180
|
responseHeaders,
|
|
181
|
-
createDebugChannelSink,
|
|
182
181
|
getDeniedIdx()
|
|
183
182
|
);
|
|
184
183
|
}
|
|
@@ -27,8 +27,7 @@ import {
|
|
|
27
27
|
} from '../primitives.ts';
|
|
28
28
|
import { checkAndWarnRscPropError } from '../rsc-prop-warnings.ts';
|
|
29
29
|
|
|
30
|
-
import { isAbortError
|
|
31
|
-
import type { DebugChannelRegistry } from '../../dev-tools/debug-channel.ts';
|
|
30
|
+
import { isAbortError } from './helpers.ts';
|
|
32
31
|
import { isDebug } from '../debug.ts';
|
|
33
32
|
import { isDevMode } from '../debug.ts';
|
|
34
33
|
|
|
@@ -78,10 +77,6 @@ export interface RenderSignals {
|
|
|
78
77
|
export interface RscStreamResult {
|
|
79
78
|
rscStream: ReadableStream<Uint8Array> | undefined;
|
|
80
79
|
signals: RenderSignals;
|
|
81
|
-
/** Dev-only: server component debug info from the Flight debug channel. */
|
|
82
|
-
getDebugComponents?: () => DebugComponentEntry[];
|
|
83
|
-
/** Dev-only: request ID for the debug channel HMR transport. */
|
|
84
|
-
debugRequestId?: string;
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
/**
|
|
@@ -98,8 +93,7 @@ export interface RscStreamResult {
|
|
|
98
93
|
export function renderRscStream(
|
|
99
94
|
payload: RscPayloadRoot,
|
|
100
95
|
req: Request,
|
|
101
|
-
leafDenyChain?: DenyPageEntry[]
|
|
102
|
-
devDebugRegistry?: DebugChannelRegistry
|
|
96
|
+
leafDenyChain?: DenyPageEntry[]
|
|
103
97
|
): RscStreamResult {
|
|
104
98
|
const signals: RenderSignals = {
|
|
105
99
|
denySignal: null,
|
|
@@ -111,14 +105,6 @@ export function renderRscStream(
|
|
|
111
105
|
|
|
112
106
|
let rscStream: ReadableStream<Uint8Array> | undefined;
|
|
113
107
|
|
|
114
|
-
// No debugChannel — debug rows ride the main RSC stream inline.
|
|
115
|
-
// Production Flight emits none. In dev, the browser Flight client
|
|
116
|
-
// handles inline debug rows for owner stack resolution (React's
|
|
117
|
-
// default behavior). A writable-only debugChannel triggers a
|
|
118
|
-
// react-server-dom-webpack >=19.3.0 Flight client regression.
|
|
119
|
-
const debugChannel = undefined;
|
|
120
|
-
const debugRequestId = undefined;
|
|
121
|
-
|
|
122
108
|
try {
|
|
123
109
|
rscStream = renderToReadableStream(
|
|
124
110
|
payload,
|
|
@@ -243,7 +229,6 @@ export function renderRscStream(
|
|
|
243
229
|
errorId,
|
|
244
230
|
});
|
|
245
231
|
},
|
|
246
|
-
debugChannel,
|
|
247
232
|
},
|
|
248
233
|
{
|
|
249
234
|
onClientReference(info: { id: string; name: string; deps: unknown }) {
|
|
@@ -272,16 +257,5 @@ export function renderRscStream(
|
|
|
272
257
|
}
|
|
273
258
|
}
|
|
274
259
|
|
|
275
|
-
return {
|
|
276
|
-
rscStream,
|
|
277
|
-
signals,
|
|
278
|
-
// In production, the debug channel collector's getComponents is available.
|
|
279
|
-
// In dev mode, debug rows ride the main stream (no separate channel), so
|
|
280
|
-
// this is undefined — the error overlay uses stack traces directly.
|
|
281
|
-
getDebugComponents:
|
|
282
|
-
debugChannel && 'getComponents' in debugChannel
|
|
283
|
-
? (debugChannel as { getComponents: () => DebugComponentEntry[] }).getComponents
|
|
284
|
-
: undefined,
|
|
285
|
-
debugRequestId,
|
|
286
|
-
};
|
|
260
|
+
return { rscStream, signals };
|
|
287
261
|
}
|
|
@@ -27,12 +27,7 @@ import type { SlotSkipEntry } from '../slot-resolver.ts';
|
|
|
27
27
|
import type { NavContext } from '../ssr-bridge-types.ts';
|
|
28
28
|
|
|
29
29
|
import { htmlEscapeJsonString } from '../flight-scripts.ts';
|
|
30
|
-
import {
|
|
31
|
-
buildRedirectResponse,
|
|
32
|
-
buildSegmentInfo,
|
|
33
|
-
createDebugChannelSink,
|
|
34
|
-
isAbortError,
|
|
35
|
-
} from './helpers.ts';
|
|
30
|
+
import { buildRedirectResponse, buildSegmentInfo, isAbortError } from './helpers.ts';
|
|
36
31
|
import { getCookiesForSsr } from '../cookie-context.ts';
|
|
37
32
|
import { getDenyStatus, getFirstDeniedSegmentIndex } from '../deny-boundary.ts';
|
|
38
33
|
import { renderErrorPage, type GlobalErrorFile } from './error-renderer.ts';
|
|
@@ -105,7 +100,6 @@ interface SsrRenderOptions {
|
|
|
105
100
|
/** Tier 2 global-error.tsx file, if present in app/. */
|
|
106
101
|
globalError?: GlobalErrorFile;
|
|
107
102
|
slotSkipInfo?: SlotSkipEntry[];
|
|
108
|
-
debugRequestId?: string;
|
|
109
103
|
}
|
|
110
104
|
|
|
111
105
|
/**
|
|
@@ -198,12 +192,7 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
198
192
|
clientBootstrap.preloadLinks +
|
|
199
193
|
// Initialize __timber_f in <head> so it exists before any streaming
|
|
200
194
|
// chunk scripts arrive in <body>. See flight-scripts.ts, LOCAL-415.
|
|
201
|
-
(clientJsDisabled ? '' : flightInitScript(nonce))
|
|
202
|
-
// Embed the debug channel request ID so the browser can subscribe
|
|
203
|
-
// to the correct HMR debug stream for owner stack resolution.
|
|
204
|
-
(opts.debugRequestId && !clientJsDisabled
|
|
205
|
-
? `<script${nonceAttr(nonce)}>self.__timber_debug_id=${htmlEscapeJsonString(JSON.stringify(opts.debugRequestId))}</script>`
|
|
206
|
-
: ''),
|
|
195
|
+
(clientJsDisabled ? '' : flightInitScript(nonce)),
|
|
207
196
|
getHeadSegmentScript: clientJsDisabled
|
|
208
197
|
? undefined
|
|
209
198
|
: () =>
|
|
@@ -250,7 +239,6 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
250
239
|
match,
|
|
251
240
|
responseHeaders,
|
|
252
241
|
clientBootstrap,
|
|
253
|
-
createDebugChannelSink,
|
|
254
242
|
callSsr,
|
|
255
243
|
undefined,
|
|
256
244
|
resolvedDeniedIndex()
|
|
@@ -437,7 +425,6 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
437
425
|
match,
|
|
438
426
|
responseHeaders,
|
|
439
427
|
clientBootstrap,
|
|
440
|
-
createDebugChannelSink,
|
|
441
428
|
callSsr,
|
|
442
429
|
undefined,
|
|
443
430
|
resolvedDeniedIndex()
|
|
@@ -156,12 +156,7 @@ export async function resolveSlotElement(
|
|
|
156
156
|
const rendered = await scope(() =>
|
|
157
157
|
(SlotPage as (props: Record<string, unknown>) => unknown)(props)
|
|
158
158
|
);
|
|
159
|
-
return containSlotSubtree(
|
|
160
|
-
rendered as React.ReactNode,
|
|
161
|
-
denyFallback,
|
|
162
|
-
undefined,
|
|
163
|
-
slotNode.segmentName
|
|
164
|
-
);
|
|
159
|
+
return containSlotSubtree(rendered as React.ReactNode, denyFallback);
|
|
165
160
|
} catch (error) {
|
|
166
161
|
// RedirectSignal must propagate — the pipeline handles redirects
|
|
167
162
|
// at the top level. Swallowing it here would silently return
|
|
@@ -194,20 +189,12 @@ export async function resolveSlotElement(
|
|
|
194
189
|
element,
|
|
195
190
|
h,
|
|
196
191
|
scope,
|
|
197
|
-
denyFallback
|
|
198
|
-
slotNode.segmentName
|
|
192
|
+
denyFallback
|
|
199
193
|
);
|
|
200
194
|
|
|
201
195
|
// Wrap with slot root's layout — INSIDE the access gate, so the layout
|
|
202
196
|
// server component never executes when access.ts denies. See TIM-1074.
|
|
203
|
-
element = await wrapWithLayout(
|
|
204
|
-
slotNode,
|
|
205
|
-
element,
|
|
206
|
-
h,
|
|
207
|
-
scope,
|
|
208
|
-
denyFallback,
|
|
209
|
-
slotNode.segmentName
|
|
210
|
-
);
|
|
197
|
+
element = await wrapWithLayout(slotNode, element, h, scope, denyFallback);
|
|
211
198
|
|
|
212
199
|
// Wrap in SlotAccessGate if slot root has access.ts. The gate is
|
|
213
200
|
// OUTSIDE the layout, matching the segment model in
|
|
@@ -303,8 +290,7 @@ async function wrapWithIntermediateSegments(
|
|
|
303
290
|
element: React.ReactElement,
|
|
304
291
|
h: CreateElementFn,
|
|
305
292
|
scope?: SlotParamScope,
|
|
306
|
-
denyFallback?: React.ReactNode
|
|
307
|
-
slotName?: string
|
|
293
|
+
denyFallback?: React.ReactNode
|
|
308
294
|
): Promise<React.ReactElement> {
|
|
309
295
|
for (let i = chain.length - 1; i > 0; i--) {
|
|
310
296
|
const seg = chain[i];
|
|
@@ -314,7 +300,7 @@ async function wrapWithIntermediateSegments(
|
|
|
314
300
|
errorBoundaryComponent: TimberErrorBoundary,
|
|
315
301
|
isSlotBoundary: true,
|
|
316
302
|
});
|
|
317
|
-
element = await wrapWithLayout(seg, element, h, scope, denyFallback
|
|
303
|
+
element = await wrapWithLayout(seg, element, h, scope, denyFallback);
|
|
318
304
|
element = await wrapWithAccessGate(seg, element, h, scope);
|
|
319
305
|
}
|
|
320
306
|
return element;
|
|
@@ -355,16 +341,10 @@ function ScopedSlotLayout(props: {
|
|
|
355
341
|
layout: (props: Record<string, unknown>) => unknown;
|
|
356
342
|
scope: SlotParamScope;
|
|
357
343
|
denyFallback: React.ReactNode;
|
|
358
|
-
slotName: string | undefined;
|
|
359
344
|
children: React.ReactElement;
|
|
360
345
|
}): unknown {
|
|
361
346
|
const rendered = props.scope(() => props.layout({ children: props.children }));
|
|
362
|
-
return containSlotSubtree(
|
|
363
|
-
rendered as React.ReactNode,
|
|
364
|
-
props.denyFallback,
|
|
365
|
-
props.children,
|
|
366
|
-
props.slotName
|
|
367
|
-
);
|
|
347
|
+
return containSlotSubtree(rendered as React.ReactNode, props.denyFallback, props.children);
|
|
368
348
|
}
|
|
369
349
|
|
|
370
350
|
/**
|
|
@@ -382,8 +362,7 @@ async function wrapWithLayout(
|
|
|
382
362
|
element: React.ReactElement,
|
|
383
363
|
h: CreateElementFn,
|
|
384
364
|
scope?: SlotParamScope,
|
|
385
|
-
denyFallback: React.ReactNode = null
|
|
386
|
-
slotName?: string
|
|
365
|
+
denyFallback: React.ReactNode = null
|
|
387
366
|
): Promise<React.ReactElement> {
|
|
388
367
|
if (!node.layout) return element;
|
|
389
368
|
const Layout = await loadComponent(node.layout);
|
|
@@ -394,7 +373,6 @@ async function wrapWithLayout(
|
|
|
394
373
|
layout: Layout as (props: Record<string, unknown>) => unknown,
|
|
395
374
|
scope,
|
|
396
375
|
denyFallback,
|
|
397
|
-
slotName,
|
|
398
376
|
children: element,
|
|
399
377
|
});
|
|
400
378
|
}
|
|
@@ -65,10 +65,6 @@ interface Containment {
|
|
|
65
65
|
* so the level below is not wrapped (and re-walked) a second time.
|
|
66
66
|
*/
|
|
67
67
|
opaque: unknown;
|
|
68
|
-
/** Slot name (e.g. `@artists`) used as a key prefix for wrapped elements. */
|
|
69
|
-
slotName?: string;
|
|
70
|
-
/** Monotonic counter for disambiguating same-name sibling components. */
|
|
71
|
-
wrapCount: number;
|
|
72
68
|
}
|
|
73
69
|
|
|
74
70
|
function isThenable(value: unknown): value is Thenable<unknown> {
|
|
@@ -160,7 +156,7 @@ function ContainedSlotComponent(props: {
|
|
|
160
156
|
fallback: ReactNode;
|
|
161
157
|
}): ReactNode | Promise<ReactNode> {
|
|
162
158
|
const { component: Component, componentProps, fallback } = props;
|
|
163
|
-
const containment: Containment = { fallback, opaque: undefined
|
|
159
|
+
const containment: Containment = { fallback, opaque: undefined };
|
|
164
160
|
let result: unknown;
|
|
165
161
|
try {
|
|
166
162
|
result = Component(componentProps);
|
|
@@ -208,16 +204,13 @@ function walk(node: unknown, c: Containment, seen: WeakSet<object>): unknown {
|
|
|
208
204
|
const { type, key, props } = node;
|
|
209
205
|
const component = type === ContainedSlotComponent ? undefined : serverComponentOf(type);
|
|
210
206
|
if (component) {
|
|
211
|
-
const effectiveKey = key ?? (c.slotName ? `${c.slotName}:${c.wrapCount++}` : null);
|
|
212
207
|
const wrapped = {
|
|
213
208
|
component,
|
|
214
209
|
componentProps: props as Record<string, unknown>,
|
|
215
210
|
fallback: c.fallback,
|
|
211
|
+
...(key !== null ? { key } : undefined),
|
|
216
212
|
};
|
|
217
|
-
return createElement(
|
|
218
|
-
ContainedSlotComponent,
|
|
219
|
-
effectiveKey === null ? wrapped : { ...wrapped, key: effectiveKey }
|
|
220
|
-
);
|
|
213
|
+
return createElement(ContainedSlotComponent, wrapped);
|
|
221
214
|
}
|
|
222
215
|
// Host elements, fragments, Suspense, client references, providers, and
|
|
223
216
|
// wrappers this walk already made: keep the type, contain whatever server
|
|
@@ -225,7 +218,23 @@ function walk(node: unknown, c: Containment, seen: WeakSet<object>): unknown {
|
|
|
225
218
|
// slots).
|
|
226
219
|
const nextProps = walk(props, c, seen);
|
|
227
220
|
if (nextProps === props) return node;
|
|
228
|
-
|
|
221
|
+
// JSX compiles `<Flex><A /><B /></Flex>` to separate createElement
|
|
222
|
+
// arguments that React does not key-validate, but the walk produces
|
|
223
|
+
// `{ children: [A, B] }` inside props — an array that React treats as
|
|
224
|
+
// dynamic and warns about missing keys. For 2+ children, spread them as
|
|
225
|
+
// separate arguments to restore the JSX calling convention. For 0–1
|
|
226
|
+
// children, pass the array as a single child argument to preserve the
|
|
227
|
+
// array shape — spreading `[x]` would collapse it to a bare element,
|
|
228
|
+
// breaking components that call `children.map()`.
|
|
229
|
+
const { children, ...rest } = nextProps as Record<string, unknown>;
|
|
230
|
+
const propsWithKey = key === null ? rest : { ...rest, key };
|
|
231
|
+
if (Array.isArray(children) && children.length > 1) {
|
|
232
|
+
return createElement(type, propsWithKey, ...(children as ReactNode[]));
|
|
233
|
+
}
|
|
234
|
+
if (children !== undefined) {
|
|
235
|
+
return createElement(type, propsWithKey, children as ReactNode);
|
|
236
|
+
}
|
|
237
|
+
return createElement(type, propsWithKey);
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
if (isPlainObject(node)) {
|
|
@@ -257,8 +266,7 @@ function walk(node: unknown, c: Containment, seen: WeakSet<object>): unknown {
|
|
|
257
266
|
export function containSlotSubtree(
|
|
258
267
|
tree: ReactNode,
|
|
259
268
|
fallback: ReactNode,
|
|
260
|
-
opaque?: ReactNode
|
|
261
|
-
slotName?: string
|
|
269
|
+
opaque?: ReactNode
|
|
262
270
|
): ReactNode {
|
|
263
|
-
return walk(tree, { fallback, opaque
|
|
271
|
+
return walk(tree, { fallback, opaque }, new WeakSet()) as ReactNode;
|
|
264
272
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"build-manifest-DWppEdLB.js","names":[],"sources":["../../src/server/build-manifest.ts"],"sourcesContent":["/**\n * Build manifest types and utilities for CSS and JS asset tracking.\n *\n * The build manifest maps route segment file paths to their output\n * chunks from Vite's client build. This enables:\n * - <link rel=\"stylesheet\"> injection in HTML <head>\n * - <script type=\"module\"> with hashed URLs in production\n * - <link rel=\"modulepreload\"> for client chunk dependencies\n * - Link preload headers for Early Hints (103)\n *\n * In dev mode, Vite's HMR client handles CSS/JS injection, so the build\n * manifest is empty. In production, it's populated from Vite's\n * .vite/manifest.json after the client build.\n *\n * Design docs: 18-build-system.md §\"Build Manifest\", 02-rendering-pipeline.md §\"Early Hints\"\n */\n\n/** A font asset entry in the build manifest. */\nexport interface ManifestFontEntry {\n /** URL path to the font file (e.g. `/_timber/fonts/inter-latin-400-abc123.woff2`). */\n href: string;\n /** Font format (e.g. `woff2`). */\n format: string;\n /** Crossorigin attribute — always `anonymous` for fonts. */\n crossOrigin: string;\n}\n\n/** Build manifest mapping input file paths to output asset URLs. */\nexport interface BuildManifest {\n /** Map from input file path (relative to project root) to output CSS URLs. */\n css: Record<string, string[]>;\n /** Map from input file path to output JS chunk URL (hashed filename). */\n js: Record<string, string>;\n /** Map from input file path to transitive JS dependency URLs for modulepreload. */\n modulepreload: Record<string, string[]>;\n /** Map from input file path to font assets used by that module. */\n fonts: Record<string, ManifestFontEntry[]>;\n /**\n * Cache-busting hashes for metadata route files (opengraph-image, etc.).\n * Map from file path (relative to project root) to short content hash.\n * In dev mode this is empty — a startup nonce is used instead.\n */\n metadataRouteHashes?: Record<string, string>;\n /**\n * Static request-dependency map from the Vite plugin's module-graph analysis.\n * Map from segment entry file path to whether it's request-dependent.\n * Used by buildSegmentInfo() for X-Timber-Segments in production.\n */\n requestDep?: Record<string, boolean>;\n}\n\n/**\n * Empty build manifest used in dev mode, and the single source of truth for\n * what an empty manifest contains.\n *\n * `virtual:timber-build-manifest` serializes this constant into its generated\n * source rather than restating it — one literal for the dev module, one for the\n * production module's `globalThis.__TIMBER_BUILD_MANIFEST__ ?? …` fallback.\n * They were three hand-written objects that had already drifted: this one and\n * the dev literal were missing `requestDep` while the production fallback had\n * it, so a reader who grepped the built bundle for `requestDep` found the one\n * copy that mentioned it and read the fallback as the shipped map (TIM-1300).\n * A field added here now reaches every copy.\n */\nexport const EMPTY_BUILD_MANIFEST: BuildManifest = {\n css: {},\n js: {},\n modulepreload: {},\n fonts: {},\n metadataRouteHashes: {},\n requestDep: {},\n};\n\n/** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */\ninterface SegmentWithFiles {\n layout?: { filePath: string };\n page?: { filePath: string };\n}\n\n/**\n * Collect all CSS files needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting CSS for each layout and page.\n * Deduplicates while preserving order (root layout CSS first).\n */\nexport function collectRouteCss(segments: SegmentWithFiles[], manifest: BuildManifest): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const cssFiles = manifest.css[file.filePath];\n if (!cssFiles) continue;\n for (const url of cssFiles) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"stylesheet\"> tags for CSS URLs.\n *\n * Returns an HTML string to prepend to headHtml for injection\n * via injectHead() before </head>.\n */\nexport function buildCssLinkTags(cssUrls: string[]): string {\n // Emit only <link rel=\"stylesheet\"> — no preload tags. CSS preloading\n // is handled by 103 Early Hints (Link header) which fires before the\n // HTML stream. Float also emits `<link rel=\"stylesheet\" data-precedence>`\n // at the top of <head> during Fizz rendering — the browser discovers\n // CSS from that tag. A <link rel=\"preload\"> here would arrive *after*\n // Float's stylesheet tag and trigger \"Preload was ignored\" warnings.\n return cssUrls.map((url) => `<link rel=\"stylesheet\" href=\"${url}\">`).join('');\n}\n\n// ─── Font utilities ──────────────────────────────────────────────────────\n\n/**\n * Collect all font entries needed for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting fonts for each layout and page.\n * Deduplicates by href while preserving order.\n */\nexport function collectRouteFonts(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): ManifestFontEntry[] {\n const seen = new Set<string>();\n const result: ManifestFontEntry[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const fonts = manifest.fonts[file.filePath];\n if (!fonts) continue;\n for (const entry of fonts) {\n if (!seen.has(entry.href)) {\n seen.add(entry.href);\n result.push(entry);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"preload\"> tags for font assets.\n *\n * Font preloads use `as=font` and always include `crossorigin` (required\n * for font preloads even for same-origin resources per the spec).\n */\nexport function buildFontPreloadTags(fonts: ManifestFontEntry[]): string {\n return fonts\n .map(\n (f) =>\n `<link rel=\"preload\" href=\"${f.href}\" as=\"font\" type=\"font/${f.format}\" crossorigin=\"${f.crossOrigin}\">`\n )\n .join('');\n}\n\n// ─── JS chunk utilities ──────────────────────────────────────────────────\n\n/**\n * Collect modulepreload URLs for a matched route's segment chain.\n *\n * Walks segments root → leaf, collecting transitive JS dependencies\n * for each layout and page. Deduplicates across segments.\n */\nexport function collectRouteModulepreloads(\n segments: SegmentWithFiles[],\n manifest: BuildManifest\n): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n\n for (const segment of segments) {\n for (const file of [segment.layout, segment.page]) {\n if (!file) continue;\n const preloads = manifest.modulepreload[file.filePath];\n if (!preloads) continue;\n for (const url of preloads) {\n if (!seen.has(url)) {\n seen.add(url);\n result.push(url);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generate <link rel=\"modulepreload\"> tags for JS dependency URLs.\n *\n * Modulepreload hints tell the browser to fetch and parse JS modules\n * before they're needed, reducing waterfall latency for dynamic imports.\n */\nexport function buildModulepreloadTags(urls: string[]): string {\n return urls.map((url) => `<link rel=\"modulepreload\" href=\"${url}\">`).join('');\n}\n"],"mappings":";;;;;;;;;;;;;;AAgEA,IAAa,uBAAsC;CACjD,KAAK,CAAC;CACN,IAAI,CAAC;CACL,eAAe,CAAC;CAChB,OAAO,CAAC;CACR,qBAAqB,CAAC;CACtB,YAAY,CAAC;AACf;;;;;;;AAcA,SAAgB,gBAAgB,UAA8B,UAAmC;CAC/F,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAAmB,CAAC;CAE1B,KAAK,MAAM,WAAW,UACpB,KAAK,MAAM,QAAQ,CAAC,QAAQ,QAAQ,QAAQ,IAAI,GAAG;EACjD,IAAI,CAAC,MAAM;EACX,MAAM,WAAW,SAAS,IAAI,KAAK;EACnC,IAAI,CAAC,UAAU;EACf,KAAK,MAAM,OAAO,UAChB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG;GAClB,KAAK,IAAI,GAAG;GACZ,OAAO,KAAK,GAAG;EACjB;CAEJ;CAGF,OAAO;AACT;;;;;;;AA0BA,SAAgB,kBACd,UACA,UACqB;CACrB,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAA8B,CAAC;CAErC,KAAK,MAAM,WAAW,UACpB,KAAK,MAAM,QAAQ,CAAC,QAAQ,QAAQ,QAAQ,IAAI,GAAG;EACjD,IAAI,CAAC,MAAM;EACX,MAAM,QAAQ,SAAS,MAAM,KAAK;EAClC,IAAI,CAAC,OAAO;EACZ,KAAK,MAAM,SAAS,OAClB,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,GAAG;GACzB,KAAK,IAAI,MAAM,IAAI;GACnB,OAAO,KAAK,KAAK;EACnB;CAEJ;CAGF,OAAO;AACT;;;;;;;AAyBA,SAAgB,2BACd,UACA,UACU;CACV,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAAmB,CAAC;CAE1B,KAAK,MAAM,WAAW,UACpB,KAAK,MAAM,QAAQ,CAAC,QAAQ,QAAQ,QAAQ,IAAI,GAAG;EACjD,IAAI,CAAC,MAAM;EACX,MAAM,WAAW,SAAS,cAAc,KAAK;EAC7C,IAAI,CAAC,UAAU;EACf,KAAK,MAAM,OAAO,UAChB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG;GAClB,KAAK,IAAI,GAAG;GACZ,OAAO,KAAK,GAAG;EACjB;CAEJ;CAGF,OAAO;AACT"}
|