@rsc-kit/core 0.20.13 → 0.21.1
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 +43 -5
- package/dist/prerender.js +138 -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 +200 -13
- 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
|
}
|
|
@@ -824,6 +841,14 @@ self.importScripts(${JSON.stringify(swExtra)})
|
|
|
824
841
|
`
|
|
825
842
|
: ""}
|
|
826
843
|
|
|
844
|
+
// How long one install-time fetch may take. An install is pending until every
|
|
845
|
+
// fetch in it settles, and a request that never answers kept the previous
|
|
846
|
+
// worker in charge for as long as it hung - so one that is late is treated
|
|
847
|
+
// like one that failed: skipped, and fetched again the first time it is used.
|
|
848
|
+
const INSTALL_FETCH_MS = 30000
|
|
849
|
+
const within = (ms, promise) =>
|
|
850
|
+
Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error('no answer in ' + ms + ' ms')), ms))])
|
|
851
|
+
|
|
827
852
|
self.addEventListener('install', (event) => {
|
|
828
853
|
// The new worker takes over rather than waiting for every tab to close.
|
|
829
854
|
// Safe here because assets are content-hashed: a page already open keeps
|
|
@@ -836,7 +861,26 @@ self.addEventListener('install', (event) => {
|
|
|
836
861
|
// it is served by the host out of the prerendered directory, which this
|
|
837
862
|
// worker cannot see. Added here so it is in the cache before it is
|
|
838
863
|
// needed, which is the only moment it cannot be fetched.
|
|
839
|
-
|
|
864
|
+
//
|
|
865
|
+
// One at a time rather than cache.addAll, which rejects the whole list
|
|
866
|
+
// for one failure - and a worker whose install rejects is never
|
|
867
|
+
// installed. The worker before it then stays in charge, serving its own
|
|
868
|
+
// build's pages, for every visit until one install happens to succeed:
|
|
869
|
+
// a network blip, a quota, a file a deploy replaced mid-install, and a
|
|
870
|
+
// phone kept a worker from builds ago whose bugs had long been fixed.
|
|
871
|
+
// A file that did not arrive costs a cache entry; the update still
|
|
872
|
+
// lands. The browser checks /sw.js for itself, without the page's
|
|
873
|
+
// javascript, so an install that cannot fail is also what rescues a
|
|
874
|
+
// visitor stuck on a worker far older than this one.
|
|
875
|
+
.then((cache) =>
|
|
876
|
+
Promise.all(
|
|
877
|
+
(OFFLINE_URL ? [...PRECACHE, OFFLINE_URL] : PRECACHE).map((url) =>
|
|
878
|
+
within(INSTALL_FETCH_MS, cache.add(url)).catch((error) => {
|
|
879
|
+
console.warn('[rsc-kit] offline: ' + url + ' was not precached (' + (error && error.message || error) + '); it will be cached the first time it is fetched')
|
|
880
|
+
}),
|
|
881
|
+
),
|
|
882
|
+
).then(() => cache),
|
|
883
|
+
)
|
|
840
884
|
// And the payload each precached page boots from. A document alone is
|
|
841
885
|
// markup that never hydrates: the client fetches its payload on boot,
|
|
842
886
|
// and a precached / rendered offline and stayed inert because that
|
|
@@ -848,7 +892,7 @@ self.addEventListener('install', (event) => {
|
|
|
848
892
|
.map((url) => {
|
|
849
893
|
const warm = new Request(url, { headers: { 'X-RSC': '1' } })
|
|
850
894
|
|
|
851
|
-
return fetch(warm)
|
|
895
|
+
return within(INSTALL_FETCH_MS, fetch(warm))
|
|
852
896
|
.then((payload) => {
|
|
853
897
|
if (mayStore(payload)) return cache.put(keyFor(warm), payload)
|
|
854
898
|
|
|
@@ -1222,6 +1266,15 @@ function copyAppAssets(clientDir) {
|
|
|
1222
1266
|
copyFileSync(join(sourceDir, "app", asset.file), to);
|
|
1223
1267
|
}
|
|
1224
1268
|
log(`icons: ${all.length} copied from app/`);
|
|
1269
|
+
// A launch screen whose size no device has would be a media query that
|
|
1270
|
+
// matches nothing - served, linked from no page, shown to no one. Named
|
|
1271
|
+
// here, with the sizes that do exist a click away in the guide.
|
|
1272
|
+
const unmatched = foundAssets.appleSplash.filter((splash) => splash.media === null).map((splash) => splash.file);
|
|
1273
|
+
if (unmatched.length) {
|
|
1274
|
+
log(`apple-splash: ${unmatched.join(", ")} ${unmatched.length === 1 ? "matches" : "match"} no iPhone or iPad screen, ` +
|
|
1275
|
+
"so no page links to it. Name each file by the device's size in pixels, portrait or landscape - " +
|
|
1276
|
+
"apple-splash-1179x2556.png is an iPhone 15. The sizes are in the PWA guide.");
|
|
1277
|
+
}
|
|
1225
1278
|
}
|
|
1226
1279
|
function writeWebManifest(clientDir, options) {
|
|
1227
1280
|
if (!existsSync(clientDir))
|
|
@@ -1230,6 +1283,9 @@ function writeWebManifest(clientDir, options) {
|
|
|
1230
1283
|
const warning = manifestWarning(options);
|
|
1231
1284
|
if (warning)
|
|
1232
1285
|
log(warning);
|
|
1286
|
+
else if (!options.screenshots?.length)
|
|
1287
|
+
log(`manifest: ${options.name} is installable. Chrome shows its basic install sheet; ` +
|
|
1288
|
+
"a screenshot-wide-*.png and screenshot-narrow-*.png in app/ get the richer one.");
|
|
1233
1289
|
else
|
|
1234
1290
|
log(`manifest: ${options.name} is installable`);
|
|
1235
1291
|
}
|
|
@@ -2265,6 +2321,18 @@ const FALLBACK_BODY = ` const answer = await devHandler(request)
|
|
|
2265
2321
|
headers.set('x-forwarded-host', here.host)
|
|
2266
2322
|
headers.set('x-forwarded-proto', here.protocol.replace(':', ''))
|
|
2267
2323
|
|
|
2324
|
+
// And never the visitor's own. The backend trusts this proxy, so an
|
|
2325
|
+
// X-Forwarded-For the browser wrote would be read as the client address -
|
|
2326
|
+
// a visitor choosing their own IP for a throttle on /login. Nothing here
|
|
2327
|
+
// knows the peer's address to put in its place, so the chain is dropped
|
|
2328
|
+
// unless this server sits behind a proxy of its own that wrote it, which
|
|
2329
|
+
// RSC_TRUST_FORWARDED=1 says.
|
|
2330
|
+
if (!(typeof process !== 'undefined' && process.env.RSC_TRUST_FORWARDED === '1')) {
|
|
2331
|
+
for (const name of ['x-forwarded-for', 'forwarded', 'x-real-ip', 'x-client-ip', 'x-forwarded-port', 'x-forwarded-prefix']) {
|
|
2332
|
+
headers.delete(name)
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2268
2336
|
const hasBody = request.method !== 'GET' && request.method !== 'HEAD'
|
|
2269
2337
|
|
|
2270
2338
|
try {
|
|
@@ -2333,10 +2401,7 @@ const NITRO_PRERENDERED = ` prerendered: import.meta.env.PROD
|
|
|
2333
2401
|
? prerenderedBeside(import.meta.url, ${JSON.stringify(NITRO_STATIC_DIR)})
|
|
2334
2402
|
: undefined,
|
|
2335
2403
|
`;
|
|
2336
|
-
const NITRO_HANDLER_OPTIONS = ` props:
|
|
2337
|
-
...match.params,
|
|
2338
|
-
...Object.fromEntries(new URL(request.url).searchParams),
|
|
2339
|
-
}),
|
|
2404
|
+
const NITRO_HANDLER_OPTIONS = ` props: queryAndParams,
|
|
2340
2405
|
// A built server gzips what it answers, where the runtime can; the dev
|
|
2341
2406
|
// server answers raw, which is what a person reading a response wants.
|
|
2342
2407
|
compress: import.meta.env.PROD,
|
|
@@ -2475,7 +2540,7 @@ import { parseParams, parseSearchParams, parseBody, isSearchParamsError, isBodyE
|
|
|
2475
2540
|
import { notFoundDigest, isNotFoundSignal } from ${JSON.stringify(join(packageDir, "notFound"))}
|
|
2476
2541
|
import { noteRequestRead, urlOf } from ${JSON.stringify(join(packageDir, "request"))}
|
|
2477
2542
|
import { redirectDigest } from ${JSON.stringify(join(packageDir, "redirectDigest"))}
|
|
2478
|
-
import { createRscHandler } from ${JSON.stringify(join(packageDir, "host"))}
|
|
2543
|
+
import { createRscHandler, queryAndParams } from ${JSON.stringify(join(packageDir, "host"))}
|
|
2479
2544
|
import { httpHostCalls } from ${JSON.stringify(join(packageDir, "hostCalls"))}
|
|
2480
2545
|
import { prerenderedBeside } from ${JSON.stringify(join(packageDir, "files"))}
|
|
2481
2546
|
import { renderToReadableStream, decodeReply, decodeAction, decodeFormState, loadServerAction } from '@vitejs/plugin-rsc/rsc'
|
|
@@ -3512,11 +3577,47 @@ async function renderTree(
|
|
|
3512
3577
|
}
|
|
3513
3578
|
}
|
|
3514
3579
|
|
|
3580
|
+
// How the app behaves added to an iPhone's home screen - Next's shape.
|
|
3581
|
+
// Both capable names: Safari reads the apple- one, Chrome warns about it
|
|
3582
|
+
// and reads the plain one, and Next switched between them in a minor.
|
|
3583
|
+
if (md.appleWebApp != null && md.appleWebApp !== false) {
|
|
3584
|
+
const web = (md.appleWebApp === true ? { capable: true } : md.appleWebApp) as Record<string, unknown>
|
|
3585
|
+
|
|
3586
|
+
if (web.capable) {
|
|
3587
|
+
head.push(tag('mobile-web-app-capable', 'yes'))
|
|
3588
|
+
head.push(tag('apple-mobile-web-app-capable', 'yes'))
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
if (web.title != null) head.push(tag('apple-mobile-web-app-title', web.title))
|
|
3592
|
+
if (web.statusBarStyle != null) head.push(tag('apple-mobile-web-app-status-bar-style', web.statusBarStyle))
|
|
3593
|
+
|
|
3594
|
+
if (web.startupImage != null) {
|
|
3595
|
+
const startup = Array.isArray(web.startupImage) ? web.startupImage : [web.startupImage]
|
|
3596
|
+
|
|
3597
|
+
for (const item of startup) {
|
|
3598
|
+
if (item == null) continue
|
|
3599
|
+
|
|
3600
|
+
const image = typeof item === 'object' && !(item instanceof URL)
|
|
3601
|
+
? (item as Record<string, unknown>)
|
|
3602
|
+
: { url: item }
|
|
3603
|
+
|
|
3604
|
+
head.push(
|
|
3605
|
+
createElement('link', {
|
|
3606
|
+
key: '__s_' + String(image.url).slice(0, 60) + '_' + String(image.media ?? ''),
|
|
3607
|
+
rel: 'apple-touch-startup-image',
|
|
3608
|
+
href: absolute(image.url),
|
|
3609
|
+
...(image.media ? { media: image.media } : {}),
|
|
3610
|
+
}),
|
|
3611
|
+
)
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3515
3616
|
// other is flattened in beside the named keys, because it is a place to put
|
|
3516
3617
|
// meta tags rather than a meta tag by that name. A key at the top level
|
|
3517
3618
|
// still renders - the type no longer invites one, but an app written
|
|
3518
3619
|
// against the old shape must not silently lose its tags.
|
|
3519
|
-
const structured = new Set(['title', 'description', 'robots', 'metadataBase', 'openGraph', 'twitter', 'icons', 'other'])
|
|
3620
|
+
const structured = new Set(['title', 'description', 'robots', 'metadataBase', 'openGraph', 'twitter', 'icons', 'appleWebApp', 'other'])
|
|
3520
3621
|
const named = Object.entries(md).filter(([k]) => !structured.has(k))
|
|
3521
3622
|
const extra = Object.entries((md.other ?? {}) as Record<string, unknown>)
|
|
3522
3623
|
|
|
@@ -3606,6 +3707,11 @@ async function runHostMiddleware(component) {
|
|
|
3606
3707
|
for (const route of manifest().routes) {
|
|
3607
3708
|
if (route.hostMiddleware?.length) hostChains[route.component] = route.hostMiddleware
|
|
3608
3709
|
}
|
|
3710
|
+
|
|
3711
|
+
// Api routes, keyed by module name - the key runRouteMiddleware is asked with.
|
|
3712
|
+
for (const api of manifest().apis ?? []) {
|
|
3713
|
+
if (api.hostMiddleware?.length) hostChains[api.name] = api.hostMiddleware
|
|
3714
|
+
}
|
|
3609
3715
|
}
|
|
3610
3716
|
|
|
3611
3717
|
const names = hostChains[component] ?? []
|
|
@@ -4471,6 +4577,8 @@ export async function handleRscPayload(
|
|
|
4471
4577
|
// data, so only the shell is safe to cache
|
|
4472
4578
|
// A page that sets neither is genuinely static and can be prerendered fully.
|
|
4473
4579
|
const PPR_SHELL_TIMEOUT_MS = Number(process.env.RSC_PPR_TIMEOUT_MS || 2000)
|
|
4580
|
+
// How long a render goes without a Flight row before it counts as waiting.
|
|
4581
|
+
const PPR_QUIET_MS = 50
|
|
4474
4582
|
|
|
4475
4583
|
export async function handleRscPprShell(
|
|
4476
4584
|
component: string,
|
|
@@ -4507,6 +4615,11 @@ export async function handleRscPprShell(
|
|
|
4507
4615
|
// installed, because those are different statements: a test that installed
|
|
4508
4616
|
// one is not a build that can reach the host.
|
|
4509
4617
|
canReachHost = false,
|
|
4618
|
+
// Told once, when the render has stopped producing rows and is only
|
|
4619
|
+
// waiting. The budget still runs - a slow query can still land and be
|
|
4620
|
+
// stored - but the caller no longer has to hold a slot for a render that
|
|
4621
|
+
// is doing nothing but watching a clock.
|
|
4622
|
+
onQuiet?: () => void,
|
|
4510
4623
|
): Promise<{ shellHtml: string; clientChunks: unknown; timedOut: boolean; usedDynamicApis: boolean; error?: string }> {
|
|
4511
4624
|
// Deliberately no middleware here. The probe is asking whether the content is
|
|
4512
4625
|
// the same for everyone, which is a question about the page. Whether a
|
|
@@ -4535,6 +4648,35 @@ export async function handleRscPprShell(
|
|
|
4535
4648
|
const controller = new AbortController()
|
|
4536
4649
|
const budget = setTimeout(() => controller.abort(), budgetMs)
|
|
4537
4650
|
|
|
4651
|
+
// Quiet is a stretch with no Flight row: what is left is awaiting something,
|
|
4652
|
+
// and whether that something ever answers is what the budget decides. A
|
|
4653
|
+
// guess in one direction only - a render mistaken for quiet just overlaps
|
|
4654
|
+
// with the next, and is still given its whole budget.
|
|
4655
|
+
let quietTimer: ReturnType<typeof setTimeout> | undefined
|
|
4656
|
+
const stir = () => {
|
|
4657
|
+
if (!onQuiet) return
|
|
4658
|
+
clearTimeout(quietTimer)
|
|
4659
|
+
quietTimer = setTimeout(() => {
|
|
4660
|
+
const told = onQuiet
|
|
4661
|
+
onQuiet = undefined
|
|
4662
|
+
told?.()
|
|
4663
|
+
}, PPR_QUIET_MS)
|
|
4664
|
+
}
|
|
4665
|
+
const watchForQuiet = (stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> => {
|
|
4666
|
+
if (!onQuiet) return stream
|
|
4667
|
+
|
|
4668
|
+
stir()
|
|
4669
|
+
|
|
4670
|
+
return stream.pipeThrough(
|
|
4671
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
4672
|
+
transform(chunk, out) {
|
|
4673
|
+
stir()
|
|
4674
|
+
out.enqueue(chunk)
|
|
4675
|
+
},
|
|
4676
|
+
}),
|
|
4677
|
+
)
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4538
4680
|
// Anything that failed while producing this shell.
|
|
4539
4681
|
//
|
|
4540
4682
|
// A rejection inside a Suspense boundary does NOT reach the caller: React
|
|
@@ -4616,7 +4758,7 @@ export async function handleRscPprShell(
|
|
|
4616
4758
|
)
|
|
4617
4759
|
// Quiet about a redirect: during the probe it is a classification, not
|
|
4618
4760
|
// a failure, and React would otherwise print a stack for every one.
|
|
4619
|
-
const flight = renderToReadableStream(tree, { onError: noteFailure })
|
|
4761
|
+
const flight = watchForQuiet(renderToReadableStream(tree, { onError: noteFailure }))
|
|
4620
4762
|
const ssr = await (import.meta as any).viteRsc.loadModule('ssr', 'index')
|
|
4621
4763
|
// The flight stream stays open across this. A Flight stream that has
|
|
4622
4764
|
// closed tells the decoder the connection ended, so the boundary waiting
|
|
@@ -4647,6 +4789,7 @@ export async function handleRscPprShell(
|
|
|
4647
4789
|
|
|
4648
4790
|
await produce
|
|
4649
4791
|
clearTimeout(budget)
|
|
4792
|
+
clearTimeout(quietTimer)
|
|
4650
4793
|
|
|
4651
4794
|
// timedOut used to mean the stopwatch ran out. It now means React has
|
|
4652
4795
|
// boundaries it could not finish — the thing the caller was always asking
|
|
@@ -4815,6 +4958,7 @@ function generateEntrySsr() {
|
|
|
4815
4958
|
const redirectDigestModule = join(packageDir, "redirectDigest");
|
|
4816
4959
|
const notFoundModule = join(packageDir, "notFound");
|
|
4817
4960
|
const earlyClicks = join(packageDir, "js/earlyClicks");
|
|
4961
|
+
const installCapture = join(packageDir, "js/installCapture");
|
|
4818
4962
|
return `// GENERATED by rscKit() — do not edit.
|
|
4819
4963
|
import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr'
|
|
4820
4964
|
import { renderToReadableStream, resume } from 'react-dom/server.edge'
|
|
@@ -4825,6 +4969,11 @@ import { cancelledByConsumer, caughtByLoading } from ${JSON.stringify(fallbackRe
|
|
|
4825
4969
|
import { parseRedirectDigest } from ${JSON.stringify(redirectDigestModule)}
|
|
4826
4970
|
import { isNotFoundDigest } from ${JSON.stringify(notFoundModule)}
|
|
4827
4971
|
import { EARLY_CLICKS } from ${JSON.stringify(earlyClicks)}
|
|
4972
|
+
${webManifestOptions ? `import { INSTALL_CAPTURE } from ${JSON.stringify(installCapture)}` : ""}
|
|
4973
|
+
|
|
4974
|
+
// What runs ahead of the runtime: held taps, and - in an app that can be
|
|
4975
|
+
// installed at all - the browser's offer to install it. See installCapture.ts.
|
|
4976
|
+
const EARLY = EARLY_CLICKS${webManifestOptions ? " + INSTALL_CAPTURE" : ""}
|
|
4828
4977
|
|
|
4829
4978
|
// Set only by the dev server. @vitejs/plugin-rsc emits its bootstrap and CSS
|
|
4830
4979
|
// links root-relative in dev, which would send the browser to the host for
|
|
@@ -4847,7 +4996,7 @@ export async function handleSsr(
|
|
|
4847
4996
|
// seconds before the runtime hydrates is held for it rather than given
|
|
4848
4997
|
// to the browser as a document load. See earlyClicks.ts.
|
|
4849
4998
|
const bootstrapScriptContent = bootstrap
|
|
4850
|
-
?
|
|
4999
|
+
? EARLY + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
|
|
4851
5000
|
: undefined
|
|
4852
5001
|
|
|
4853
5002
|
// Without an onError handler React rejects each abortable task on its own,
|
|
@@ -4889,7 +5038,7 @@ export async function handleSsrPrerender(
|
|
|
4889
5038
|
const bootstrapScriptContent =
|
|
4890
5039
|
options.bootstrap === false
|
|
4891
5040
|
? undefined
|
|
4892
|
-
:
|
|
5041
|
+
: EARLY + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
|
|
4893
5042
|
|
|
4894
5043
|
const { prelude, postponed } = await prerender(root as any, {
|
|
4895
5044
|
bootstrapScriptContent,
|
|
@@ -5052,9 +5201,21 @@ function generateEntryBrowser() {
|
|
|
5052
5201
|
}))
|
|
5053
5202
|
: null;
|
|
5054
5203
|
const refreshModule = join(packageDir, "js/navigate");
|
|
5204
|
+
const chunkPreload = join(packageDir, "js/chunkPreload");
|
|
5055
5205
|
return `// GENERATED by rscKit() — do not edit.
|
|
5056
5206
|
import { createViteRscApp } from ${JSON.stringify(clientBootstrap)}
|
|
5057
5207
|
import { refresh } from ${JSON.stringify(refreshModule)}
|
|
5208
|
+
import { setChunkLoader } from ${JSON.stringify(chunkPreload)}
|
|
5209
|
+
import * as clientReferences from 'virtual:vite-rsc/client-references'
|
|
5210
|
+
|
|
5211
|
+
// How a client component's id becomes its chunk, for loading the ones a
|
|
5212
|
+
// prefetched page uses before the tap - see chunkPreload.ts. The same map the
|
|
5213
|
+
// runtime loads them through, so a chunk loaded here is the one it finds.
|
|
5214
|
+
// A production build only: in development the runtime imports by path.
|
|
5215
|
+
if (import.meta.env.PROD) {
|
|
5216
|
+
const references = (clientReferences as { default: Record<string, () => Promise<unknown>> }).default
|
|
5217
|
+
setChunkLoader((id) => references[id]?.())
|
|
5218
|
+
}
|
|
5058
5219
|
|
|
5059
5220
|
createViteRscApp(document, ${JSON.stringify(interceptManifest())}, ${JSON.stringify({
|
|
5060
5221
|
staticPayloads: staticPayloads || null,
|
|
@@ -5268,6 +5429,25 @@ const JSX_PLUGIN_PATTERN = /react|babel|oxc/i;
|
|
|
5268
5429
|
* class is ever constructed on the server.
|
|
5269
5430
|
*/
|
|
5270
5431
|
const CLIENT_STUB = /registerClientReference\(\s*\(\) => \{ throw new Error\("Unexpectedly client reference export '"/g;
|
|
5432
|
+
/**
|
|
5433
|
+
* The server's render of client components, with names a second bundler
|
|
5434
|
+
* cannot change. See stableNames.ts: a replay matches slots by component
|
|
5435
|
+
* name, and `bun build --compile` renames whatever collides when it merges
|
|
5436
|
+
* module scopes. Stamped on the ssr output as written, so the names are
|
|
5437
|
+
* the ones the prerender recorded - it renders from this same output - and
|
|
5438
|
+
* the ones anything serving it reads, compiled or not.
|
|
5439
|
+
*/
|
|
5440
|
+
function stableComponentNames() {
|
|
5441
|
+
return {
|
|
5442
|
+
name: "rsc-kit:stable-component-names",
|
|
5443
|
+
apply: "build",
|
|
5444
|
+
applyToEnvironment: (environment) => environment.name === "ssr",
|
|
5445
|
+
renderChunk(code) {
|
|
5446
|
+
const stamped = stampDisplayNames(code, (source) => parseAst(source));
|
|
5447
|
+
return stamped === code ? null : { code: stamped, map: null };
|
|
5448
|
+
},
|
|
5449
|
+
};
|
|
5450
|
+
}
|
|
5271
5451
|
function extendableClientReferences() {
|
|
5272
5452
|
return {
|
|
5273
5453
|
name: "rsc-kit:extendable-client-references",
|
|
@@ -6075,7 +6255,13 @@ export function rscKit(options = {}) {
|
|
|
6075
6255
|
// dependency exists once, so there is nothing to collide.
|
|
6076
6256
|
// The server does not pay for the bigger chunk the way a
|
|
6077
6257
|
// browser would: it reads from disk, once, at startup.
|
|
6078
|
-
|
|
6258
|
+
//
|
|
6259
|
+
// `codeSplitting`, rolldown's name for what was
|
|
6260
|
+
// `advancedChunks`: the old name still works and prints a
|
|
6261
|
+
// deprecation warning in every build, twice - plugin-rsc
|
|
6262
|
+
// builds this environment once to scan and once for real.
|
|
6263
|
+
// Every rolldown a Vite 8 ships with knows the new name.
|
|
6264
|
+
codeSplitting: {
|
|
6079
6265
|
groups: [{ name: "vendor", test: /[\\/]node_modules[\\/]/ }],
|
|
6080
6266
|
},
|
|
6081
6267
|
},
|
|
@@ -6417,6 +6603,7 @@ export function rscKit(options = {}) {
|
|
|
6417
6603
|
metadataRoutesPlugin(),
|
|
6418
6604
|
openApiPlugin(),
|
|
6419
6605
|
extendableClientReferences(),
|
|
6606
|
+
stableComponentNames(),
|
|
6420
6607
|
typecheckPlugin(),
|
|
6421
6608
|
clientImportsAudit(),
|
|
6422
6609
|
routesPlugin,
|