@rsc-kit/core 0.20.13 → 0.21.0
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/apiPrerender.js +1 -1
- package/dist/apiPrerender.js.map +1 -1
- package/dist/appAssets.d.ts +6 -0
- package/dist/appAssets.js +34 -0
- package/dist/appAssets.js.map +1 -1
- package/dist/appleSplash.d.ts +10 -0
- package/dist/appleSplash.js +74 -0
- package/dist/appleSplash.js.map +1 -0
- package/dist/earlyHints.js +7 -2
- package/dist/earlyHints.js.map +1 -1
- package/dist/export.js +3 -1
- package/dist/export.js.map +1 -1
- package/dist/files.d.ts +0 -8
- package/dist/files.js +6 -0
- package/dist/files.js.map +1 -1
- package/dist/headers.d.ts +8 -0
- package/dist/headers.js +8 -0
- package/dist/headers.js.map +1 -1
- package/dist/host.d.ts +9 -0
- package/dist/host.js +109 -29
- package/dist/host.js.map +1 -1
- package/dist/js/Form.d.ts +47 -54
- package/dist/js/Form.js +41 -48
- package/dist/js/Form.js.map +1 -1
- package/dist/js/chunkPreload.d.ts +26 -0
- package/dist/js/chunkPreload.js +66 -0
- package/dist/js/chunkPreload.js.map +1 -0
- package/dist/js/installCapture.d.ts +15 -0
- package/dist/js/installCapture.js +17 -0
- package/dist/js/installCapture.js.map +1 -0
- package/dist/js/navigate.js +4 -0
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/useInstall.d.ts +15 -0
- package/dist/js/useInstall.js +102 -0
- package/dist/js/useInstall.js.map +1 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js.map +1 -1
- package/dist/metadata.d.ts +31 -0
- package/dist/metadata.js.map +1 -1
- package/dist/prerender.d.ts +24 -5
- package/dist/prerender.js +107 -19
- package/dist/prerender.js.map +1 -1
- package/dist/request.js +3 -2
- package/dist/request.js.map +1 -1
- package/dist/shellHead.js +4 -2
- package/dist/shellHead.js.map +1 -1
- package/dist/stableNames.d.ts +52 -0
- package/dist/stableNames.js +91 -0
- package/dist/stableNames.js.map +1 -0
- package/dist/vite.d.ts +5 -3
- package/dist/vite.js +164 -10
- package/dist/vite.js.map +1 -1
- package/dist/webManifest.d.ts +37 -0
- package/dist/webManifest.js +51 -6
- package/dist/webManifest.js.map +1 -1
- package/package.json +8 -4
package/dist/vite.js
CHANGED
|
@@ -20,7 +20,8 @@ import { spawnSync } from "node:child_process";
|
|
|
20
20
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
22
|
import rsc, { getPluginApi } from "@vitejs/plugin-rsc";
|
|
23
|
-
import { loadEnv } from "vite";
|
|
23
|
+
import { loadEnv, parseAst } from "vite";
|
|
24
|
+
import { stampDisplayNames } from "./stableNames.js";
|
|
24
25
|
import { REPORT_FILE, buildReport } from "./buildReport.js";
|
|
25
26
|
import { MANIFEST_PATH, manifestWarning, webManifest } from "./webManifest.js";
|
|
26
27
|
import { ASSET_BASE, allAppAssets, appAssets, headTags, typeOf, } from "./appAssets.js";
|
|
@@ -192,6 +193,8 @@ let foundAssets = {
|
|
|
192
193
|
favicon: null,
|
|
193
194
|
icons: [],
|
|
194
195
|
appleIcon: null,
|
|
196
|
+
appleSplash: [],
|
|
197
|
+
screenshots: [],
|
|
195
198
|
openGraph: null,
|
|
196
199
|
twitter: null,
|
|
197
200
|
};
|
|
@@ -390,6 +393,16 @@ function resolvePaths(options) {
|
|
|
390
393
|
icons: foundAssets.icons.map((icon) => icon.href),
|
|
391
394
|
};
|
|
392
395
|
}
|
|
396
|
+
// Screenshots the same way: found by name, their size and form factor read
|
|
397
|
+
// from it when the manifest is written.
|
|
398
|
+
if (webManifestOptions &&
|
|
399
|
+
!webManifestOptions.screenshots?.length &&
|
|
400
|
+
foundAssets.screenshots.length) {
|
|
401
|
+
webManifestOptions = {
|
|
402
|
+
...webManifestOptions,
|
|
403
|
+
screenshots: foundAssets.screenshots.map((shot) => shot.href),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
393
406
|
hostActions = options.hostActions ?? fileHostActions(projectRoot);
|
|
394
407
|
}
|
|
395
408
|
function log(...args) {
|
|
@@ -620,6 +633,10 @@ function routeManifest() {
|
|
|
620
633
|
// A synthesised robots.txt or sitemap.xml runs no guard: it exists to be
|
|
621
634
|
// read by anyone, and a guard on the root would 401 the crawler.
|
|
622
635
|
middleware: generated ? [] : ancestors(name, "middleware"),
|
|
636
|
+
// And the host's, for the same reason: `export const middleware =
|
|
637
|
+
// ['auth']` on a directory guards the route.ts files in it, or adding
|
|
638
|
+
// an endpoint under a guarded path opened a hole in it.
|
|
639
|
+
hostMiddleware: generated ? [] : hostMiddleware(dirOf(name)),
|
|
623
640
|
})),
|
|
624
641
|
};
|
|
625
642
|
}
|
|
@@ -1222,6 +1239,15 @@ function copyAppAssets(clientDir) {
|
|
|
1222
1239
|
copyFileSync(join(sourceDir, "app", asset.file), to);
|
|
1223
1240
|
}
|
|
1224
1241
|
log(`icons: ${all.length} copied from app/`);
|
|
1242
|
+
// A launch screen whose size no device has would be a media query that
|
|
1243
|
+
// matches nothing - served, linked from no page, shown to no one. Named
|
|
1244
|
+
// here, with the sizes that do exist a click away in the guide.
|
|
1245
|
+
const unmatched = foundAssets.appleSplash.filter((splash) => splash.media === null).map((splash) => splash.file);
|
|
1246
|
+
if (unmatched.length) {
|
|
1247
|
+
log(`apple-splash: ${unmatched.join(", ")} ${unmatched.length === 1 ? "matches" : "match"} no iPhone or iPad screen, ` +
|
|
1248
|
+
"so no page links to it. Name each file by the device's size in pixels, portrait or landscape - " +
|
|
1249
|
+
"apple-splash-1179x2556.png is an iPhone 15. The sizes are in the PWA guide.");
|
|
1250
|
+
}
|
|
1225
1251
|
}
|
|
1226
1252
|
function writeWebManifest(clientDir, options) {
|
|
1227
1253
|
if (!existsSync(clientDir))
|
|
@@ -1230,6 +1256,9 @@ function writeWebManifest(clientDir, options) {
|
|
|
1230
1256
|
const warning = manifestWarning(options);
|
|
1231
1257
|
if (warning)
|
|
1232
1258
|
log(warning);
|
|
1259
|
+
else if (!options.screenshots?.length)
|
|
1260
|
+
log(`manifest: ${options.name} is installable. Chrome shows its basic install sheet; ` +
|
|
1261
|
+
"a screenshot-wide-*.png and screenshot-narrow-*.png in app/ get the richer one.");
|
|
1233
1262
|
else
|
|
1234
1263
|
log(`manifest: ${options.name} is installable`);
|
|
1235
1264
|
}
|
|
@@ -2265,6 +2294,18 @@ const FALLBACK_BODY = ` const answer = await devHandler(request)
|
|
|
2265
2294
|
headers.set('x-forwarded-host', here.host)
|
|
2266
2295
|
headers.set('x-forwarded-proto', here.protocol.replace(':', ''))
|
|
2267
2296
|
|
|
2297
|
+
// And never the visitor's own. The backend trusts this proxy, so an
|
|
2298
|
+
// X-Forwarded-For the browser wrote would be read as the client address -
|
|
2299
|
+
// a visitor choosing their own IP for a throttle on /login. Nothing here
|
|
2300
|
+
// knows the peer's address to put in its place, so the chain is dropped
|
|
2301
|
+
// unless this server sits behind a proxy of its own that wrote it, which
|
|
2302
|
+
// RSC_TRUST_FORWARDED=1 says.
|
|
2303
|
+
if (!(typeof process !== 'undefined' && process.env.RSC_TRUST_FORWARDED === '1')) {
|
|
2304
|
+
for (const name of ['x-forwarded-for', 'forwarded', 'x-real-ip', 'x-client-ip', 'x-forwarded-port', 'x-forwarded-prefix']) {
|
|
2305
|
+
headers.delete(name)
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2268
2309
|
const hasBody = request.method !== 'GET' && request.method !== 'HEAD'
|
|
2269
2310
|
|
|
2270
2311
|
try {
|
|
@@ -2333,10 +2374,7 @@ const NITRO_PRERENDERED = ` prerendered: import.meta.env.PROD
|
|
|
2333
2374
|
? prerenderedBeside(import.meta.url, ${JSON.stringify(NITRO_STATIC_DIR)})
|
|
2334
2375
|
: undefined,
|
|
2335
2376
|
`;
|
|
2336
|
-
const NITRO_HANDLER_OPTIONS = ` props:
|
|
2337
|
-
...match.params,
|
|
2338
|
-
...Object.fromEntries(new URL(request.url).searchParams),
|
|
2339
|
-
}),
|
|
2377
|
+
const NITRO_HANDLER_OPTIONS = ` props: queryAndParams,
|
|
2340
2378
|
// A built server gzips what it answers, where the runtime can; the dev
|
|
2341
2379
|
// server answers raw, which is what a person reading a response wants.
|
|
2342
2380
|
compress: import.meta.env.PROD,
|
|
@@ -2475,7 +2513,7 @@ import { parseParams, parseSearchParams, parseBody, isSearchParamsError, isBodyE
|
|
|
2475
2513
|
import { notFoundDigest, isNotFoundSignal } from ${JSON.stringify(join(packageDir, "notFound"))}
|
|
2476
2514
|
import { noteRequestRead, urlOf } from ${JSON.stringify(join(packageDir, "request"))}
|
|
2477
2515
|
import { redirectDigest } from ${JSON.stringify(join(packageDir, "redirectDigest"))}
|
|
2478
|
-
import { createRscHandler } from ${JSON.stringify(join(packageDir, "host"))}
|
|
2516
|
+
import { createRscHandler, queryAndParams } from ${JSON.stringify(join(packageDir, "host"))}
|
|
2479
2517
|
import { httpHostCalls } from ${JSON.stringify(join(packageDir, "hostCalls"))}
|
|
2480
2518
|
import { prerenderedBeside } from ${JSON.stringify(join(packageDir, "files"))}
|
|
2481
2519
|
import { renderToReadableStream, decodeReply, decodeAction, decodeFormState, loadServerAction } from '@vitejs/plugin-rsc/rsc'
|
|
@@ -3512,11 +3550,47 @@ async function renderTree(
|
|
|
3512
3550
|
}
|
|
3513
3551
|
}
|
|
3514
3552
|
|
|
3553
|
+
// How the app behaves added to an iPhone's home screen - Next's shape.
|
|
3554
|
+
// Both capable names: Safari reads the apple- one, Chrome warns about it
|
|
3555
|
+
// and reads the plain one, and Next switched between them in a minor.
|
|
3556
|
+
if (md.appleWebApp != null && md.appleWebApp !== false) {
|
|
3557
|
+
const web = (md.appleWebApp === true ? { capable: true } : md.appleWebApp) as Record<string, unknown>
|
|
3558
|
+
|
|
3559
|
+
if (web.capable) {
|
|
3560
|
+
head.push(tag('mobile-web-app-capable', 'yes'))
|
|
3561
|
+
head.push(tag('apple-mobile-web-app-capable', 'yes'))
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
if (web.title != null) head.push(tag('apple-mobile-web-app-title', web.title))
|
|
3565
|
+
if (web.statusBarStyle != null) head.push(tag('apple-mobile-web-app-status-bar-style', web.statusBarStyle))
|
|
3566
|
+
|
|
3567
|
+
if (web.startupImage != null) {
|
|
3568
|
+
const startup = Array.isArray(web.startupImage) ? web.startupImage : [web.startupImage]
|
|
3569
|
+
|
|
3570
|
+
for (const item of startup) {
|
|
3571
|
+
if (item == null) continue
|
|
3572
|
+
|
|
3573
|
+
const image = typeof item === 'object' && !(item instanceof URL)
|
|
3574
|
+
? (item as Record<string, unknown>)
|
|
3575
|
+
: { url: item }
|
|
3576
|
+
|
|
3577
|
+
head.push(
|
|
3578
|
+
createElement('link', {
|
|
3579
|
+
key: '__s_' + String(image.url).slice(0, 60) + '_' + String(image.media ?? ''),
|
|
3580
|
+
rel: 'apple-touch-startup-image',
|
|
3581
|
+
href: absolute(image.url),
|
|
3582
|
+
...(image.media ? { media: image.media } : {}),
|
|
3583
|
+
}),
|
|
3584
|
+
)
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3515
3589
|
// other is flattened in beside the named keys, because it is a place to put
|
|
3516
3590
|
// meta tags rather than a meta tag by that name. A key at the top level
|
|
3517
3591
|
// still renders - the type no longer invites one, but an app written
|
|
3518
3592
|
// against the old shape must not silently lose its tags.
|
|
3519
|
-
const structured = new Set(['title', 'description', 'robots', 'metadataBase', 'openGraph', 'twitter', 'icons', 'other'])
|
|
3593
|
+
const structured = new Set(['title', 'description', 'robots', 'metadataBase', 'openGraph', 'twitter', 'icons', 'appleWebApp', 'other'])
|
|
3520
3594
|
const named = Object.entries(md).filter(([k]) => !structured.has(k))
|
|
3521
3595
|
const extra = Object.entries((md.other ?? {}) as Record<string, unknown>)
|
|
3522
3596
|
|
|
@@ -3606,6 +3680,11 @@ async function runHostMiddleware(component) {
|
|
|
3606
3680
|
for (const route of manifest().routes) {
|
|
3607
3681
|
if (route.hostMiddleware?.length) hostChains[route.component] = route.hostMiddleware
|
|
3608
3682
|
}
|
|
3683
|
+
|
|
3684
|
+
// Api routes, keyed by module name - the key runRouteMiddleware is asked with.
|
|
3685
|
+
for (const api of manifest().apis ?? []) {
|
|
3686
|
+
if (api.hostMiddleware?.length) hostChains[api.name] = api.hostMiddleware
|
|
3687
|
+
}
|
|
3609
3688
|
}
|
|
3610
3689
|
|
|
3611
3690
|
const names = hostChains[component] ?? []
|
|
@@ -4471,6 +4550,8 @@ export async function handleRscPayload(
|
|
|
4471
4550
|
// data, so only the shell is safe to cache
|
|
4472
4551
|
// A page that sets neither is genuinely static and can be prerendered fully.
|
|
4473
4552
|
const PPR_SHELL_TIMEOUT_MS = Number(process.env.RSC_PPR_TIMEOUT_MS || 2000)
|
|
4553
|
+
// How long a render goes without a Flight row before it counts as waiting.
|
|
4554
|
+
const PPR_QUIET_MS = 50
|
|
4474
4555
|
|
|
4475
4556
|
export async function handleRscPprShell(
|
|
4476
4557
|
component: string,
|
|
@@ -4507,6 +4588,11 @@ export async function handleRscPprShell(
|
|
|
4507
4588
|
// installed, because those are different statements: a test that installed
|
|
4508
4589
|
// one is not a build that can reach the host.
|
|
4509
4590
|
canReachHost = false,
|
|
4591
|
+
// Told once, when the render has stopped producing rows and is only
|
|
4592
|
+
// waiting. The budget still runs - a slow query can still land and be
|
|
4593
|
+
// stored - but the caller no longer has to hold a slot for a render that
|
|
4594
|
+
// is doing nothing but watching a clock.
|
|
4595
|
+
onQuiet?: () => void,
|
|
4510
4596
|
): Promise<{ shellHtml: string; clientChunks: unknown; timedOut: boolean; usedDynamicApis: boolean; error?: string }> {
|
|
4511
4597
|
// Deliberately no middleware here. The probe is asking whether the content is
|
|
4512
4598
|
// the same for everyone, which is a question about the page. Whether a
|
|
@@ -4535,6 +4621,35 @@ export async function handleRscPprShell(
|
|
|
4535
4621
|
const controller = new AbortController()
|
|
4536
4622
|
const budget = setTimeout(() => controller.abort(), budgetMs)
|
|
4537
4623
|
|
|
4624
|
+
// Quiet is a stretch with no Flight row: what is left is awaiting something,
|
|
4625
|
+
// and whether that something ever answers is what the budget decides. A
|
|
4626
|
+
// guess in one direction only - a render mistaken for quiet just overlaps
|
|
4627
|
+
// with the next, and is still given its whole budget.
|
|
4628
|
+
let quietTimer: ReturnType<typeof setTimeout> | undefined
|
|
4629
|
+
const stir = () => {
|
|
4630
|
+
if (!onQuiet) return
|
|
4631
|
+
clearTimeout(quietTimer)
|
|
4632
|
+
quietTimer = setTimeout(() => {
|
|
4633
|
+
const told = onQuiet
|
|
4634
|
+
onQuiet = undefined
|
|
4635
|
+
told?.()
|
|
4636
|
+
}, PPR_QUIET_MS)
|
|
4637
|
+
}
|
|
4638
|
+
const watchForQuiet = (stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> => {
|
|
4639
|
+
if (!onQuiet) return stream
|
|
4640
|
+
|
|
4641
|
+
stir()
|
|
4642
|
+
|
|
4643
|
+
return stream.pipeThrough(
|
|
4644
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
4645
|
+
transform(chunk, out) {
|
|
4646
|
+
stir()
|
|
4647
|
+
out.enqueue(chunk)
|
|
4648
|
+
},
|
|
4649
|
+
}),
|
|
4650
|
+
)
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4538
4653
|
// Anything that failed while producing this shell.
|
|
4539
4654
|
//
|
|
4540
4655
|
// A rejection inside a Suspense boundary does NOT reach the caller: React
|
|
@@ -4616,7 +4731,7 @@ export async function handleRscPprShell(
|
|
|
4616
4731
|
)
|
|
4617
4732
|
// Quiet about a redirect: during the probe it is a classification, not
|
|
4618
4733
|
// a failure, and React would otherwise print a stack for every one.
|
|
4619
|
-
const flight = renderToReadableStream(tree, { onError: noteFailure })
|
|
4734
|
+
const flight = watchForQuiet(renderToReadableStream(tree, { onError: noteFailure }))
|
|
4620
4735
|
const ssr = await (import.meta as any).viteRsc.loadModule('ssr', 'index')
|
|
4621
4736
|
// The flight stream stays open across this. A Flight stream that has
|
|
4622
4737
|
// closed tells the decoder the connection ended, so the boundary waiting
|
|
@@ -4647,6 +4762,7 @@ export async function handleRscPprShell(
|
|
|
4647
4762
|
|
|
4648
4763
|
await produce
|
|
4649
4764
|
clearTimeout(budget)
|
|
4765
|
+
clearTimeout(quietTimer)
|
|
4650
4766
|
|
|
4651
4767
|
// timedOut used to mean the stopwatch ran out. It now means React has
|
|
4652
4768
|
// boundaries it could not finish — the thing the caller was always asking
|
|
@@ -4815,6 +4931,7 @@ function generateEntrySsr() {
|
|
|
4815
4931
|
const redirectDigestModule = join(packageDir, "redirectDigest");
|
|
4816
4932
|
const notFoundModule = join(packageDir, "notFound");
|
|
4817
4933
|
const earlyClicks = join(packageDir, "js/earlyClicks");
|
|
4934
|
+
const installCapture = join(packageDir, "js/installCapture");
|
|
4818
4935
|
return `// GENERATED by rscKit() — do not edit.
|
|
4819
4936
|
import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr'
|
|
4820
4937
|
import { renderToReadableStream, resume } from 'react-dom/server.edge'
|
|
@@ -4825,6 +4942,11 @@ import { cancelledByConsumer, caughtByLoading } from ${JSON.stringify(fallbackRe
|
|
|
4825
4942
|
import { parseRedirectDigest } from ${JSON.stringify(redirectDigestModule)}
|
|
4826
4943
|
import { isNotFoundDigest } from ${JSON.stringify(notFoundModule)}
|
|
4827
4944
|
import { EARLY_CLICKS } from ${JSON.stringify(earlyClicks)}
|
|
4945
|
+
${webManifestOptions ? `import { INSTALL_CAPTURE } from ${JSON.stringify(installCapture)}` : ""}
|
|
4946
|
+
|
|
4947
|
+
// What runs ahead of the runtime: held taps, and - in an app that can be
|
|
4948
|
+
// installed at all - the browser's offer to install it. See installCapture.ts.
|
|
4949
|
+
const EARLY = EARLY_CLICKS${webManifestOptions ? " + INSTALL_CAPTURE" : ""}
|
|
4828
4950
|
|
|
4829
4951
|
// Set only by the dev server. @vitejs/plugin-rsc emits its bootstrap and CSS
|
|
4830
4952
|
// links root-relative in dev, which would send the browser to the host for
|
|
@@ -4847,7 +4969,7 @@ export async function handleSsr(
|
|
|
4847
4969
|
// seconds before the runtime hydrates is held for it rather than given
|
|
4848
4970
|
// to the browser as a document load. See earlyClicks.ts.
|
|
4849
4971
|
const bootstrapScriptContent = bootstrap
|
|
4850
|
-
?
|
|
4972
|
+
? EARLY + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
|
|
4851
4973
|
: undefined
|
|
4852
4974
|
|
|
4853
4975
|
// Without an onError handler React rejects each abortable task on its own,
|
|
@@ -4889,7 +5011,7 @@ export async function handleSsrPrerender(
|
|
|
4889
5011
|
const bootstrapScriptContent =
|
|
4890
5012
|
options.bootstrap === false
|
|
4891
5013
|
? undefined
|
|
4892
|
-
:
|
|
5014
|
+
: EARLY + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
|
|
4893
5015
|
|
|
4894
5016
|
const { prelude, postponed } = await prerender(root as any, {
|
|
4895
5017
|
bootstrapScriptContent,
|
|
@@ -5052,9 +5174,21 @@ function generateEntryBrowser() {
|
|
|
5052
5174
|
}))
|
|
5053
5175
|
: null;
|
|
5054
5176
|
const refreshModule = join(packageDir, "js/navigate");
|
|
5177
|
+
const chunkPreload = join(packageDir, "js/chunkPreload");
|
|
5055
5178
|
return `// GENERATED by rscKit() — do not edit.
|
|
5056
5179
|
import { createViteRscApp } from ${JSON.stringify(clientBootstrap)}
|
|
5057
5180
|
import { refresh } from ${JSON.stringify(refreshModule)}
|
|
5181
|
+
import { setChunkLoader } from ${JSON.stringify(chunkPreload)}
|
|
5182
|
+
import * as clientReferences from 'virtual:vite-rsc/client-references'
|
|
5183
|
+
|
|
5184
|
+
// How a client component's id becomes its chunk, for loading the ones a
|
|
5185
|
+
// prefetched page uses before the tap - see chunkPreload.ts. The same map the
|
|
5186
|
+
// runtime loads them through, so a chunk loaded here is the one it finds.
|
|
5187
|
+
// A production build only: in development the runtime imports by path.
|
|
5188
|
+
if (import.meta.env.PROD) {
|
|
5189
|
+
const references = (clientReferences as { default: Record<string, () => Promise<unknown>> }).default
|
|
5190
|
+
setChunkLoader((id) => references[id]?.())
|
|
5191
|
+
}
|
|
5058
5192
|
|
|
5059
5193
|
createViteRscApp(document, ${JSON.stringify(interceptManifest())}, ${JSON.stringify({
|
|
5060
5194
|
staticPayloads: staticPayloads || null,
|
|
@@ -5268,6 +5402,25 @@ const JSX_PLUGIN_PATTERN = /react|babel|oxc/i;
|
|
|
5268
5402
|
* class is ever constructed on the server.
|
|
5269
5403
|
*/
|
|
5270
5404
|
const CLIENT_STUB = /registerClientReference\(\s*\(\) => \{ throw new Error\("Unexpectedly client reference export '"/g;
|
|
5405
|
+
/**
|
|
5406
|
+
* The server's render of client components, with names a second bundler
|
|
5407
|
+
* cannot change. See stableNames.ts: a replay matches slots by component
|
|
5408
|
+
* name, and `bun build --compile` renames whatever collides when it merges
|
|
5409
|
+
* module scopes. Stamped on the ssr output as written, so the names are
|
|
5410
|
+
* the ones the prerender recorded - it renders from this same output - and
|
|
5411
|
+
* the ones anything serving it reads, compiled or not.
|
|
5412
|
+
*/
|
|
5413
|
+
function stableComponentNames() {
|
|
5414
|
+
return {
|
|
5415
|
+
name: "rsc-kit:stable-component-names",
|
|
5416
|
+
apply: "build",
|
|
5417
|
+
applyToEnvironment: (environment) => environment.name === "ssr",
|
|
5418
|
+
renderChunk(code) {
|
|
5419
|
+
const stamped = stampDisplayNames(code, (source) => parseAst(source));
|
|
5420
|
+
return stamped === code ? null : { code: stamped, map: null };
|
|
5421
|
+
},
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5271
5424
|
function extendableClientReferences() {
|
|
5272
5425
|
return {
|
|
5273
5426
|
name: "rsc-kit:extendable-client-references",
|
|
@@ -6417,6 +6570,7 @@ export function rscKit(options = {}) {
|
|
|
6417
6570
|
metadataRoutesPlugin(),
|
|
6418
6571
|
openApiPlugin(),
|
|
6419
6572
|
extendableClientReferences(),
|
|
6573
|
+
stableComponentNames(),
|
|
6420
6574
|
typecheckPlugin(),
|
|
6421
6575
|
clientImportsAudit(),
|
|
6422
6576
|
routesPlugin,
|