@rsc-kit/core 0.20.3 → 0.20.5
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/earlyHints.d.ts +17 -0
- package/dist/earlyHints.js +65 -0
- package/dist/earlyHints.js.map +1 -0
- package/dist/host.d.ts +3 -0
- package/dist/host.js +31 -1
- package/dist/host.js.map +1 -1
- package/dist/js/Link.js +1 -1
- package/dist/js/Link.js.map +1 -1
- package/dist/js/PathnameProvider.js +14 -1
- package/dist/js/PathnameProvider.js.map +1 -1
- package/dist/js/createViteRscApp.js +6 -0
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/earlyClicks.d.ts +35 -0
- package/dist/js/earlyClicks.js +67 -0
- package/dist/js/earlyClicks.js.map +1 -0
- package/dist/js/navigate.js +95 -11
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/updateStore.d.ts +6 -0
- package/dist/js/updateStore.js +48 -2
- package/dist/js/updateStore.js.map +1 -1
- package/dist/vite.js +113 -11
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/vite.js
CHANGED
|
@@ -756,6 +756,14 @@ function reportAllDynamic() {
|
|
|
756
756
|
export const SERVICE_WORKER = (version, precache, frozen = [], offlineUrl = null, swExtra = null) => `// GENERATED by rscKit() — do not edit.
|
|
757
757
|
const VERSION = ${JSON.stringify(version)}
|
|
758
758
|
const CACHE = 'rsc-kit-' + VERSION
|
|
759
|
+
|
|
760
|
+
// This worker's own cache, and only that. \`caches.match()\` searches every
|
|
761
|
+
// cache on the origin - the previous worker's included, until this one's
|
|
762
|
+
// activation sweeps it - and answered a payload request for the page with
|
|
763
|
+
// the previous build's payload: client references this build does not
|
|
764
|
+
// have, and the page reloaded to recover. A document, a payload, a page
|
|
765
|
+
// this worker did not cache is not served from a cache it did not fill.
|
|
766
|
+
const own = (key, options) => caches.open(CACHE).then((cache) => cache.match(key, options))
|
|
759
767
|
const PRECACHE = ${JSON.stringify(precache, null, 2)}
|
|
760
768
|
|
|
761
769
|
// The urls the build stored whole. Their answer cannot change until the next
|
|
@@ -869,6 +877,39 @@ async function tellTheOpenPages() {
|
|
|
869
877
|
|
|
870
878
|
const immutable = (url) => url.pathname.startsWith('/assets/') || /-[A-Za-z0-9_-]{8,}\\.[a-z]+$/.test(url.pathname)
|
|
871
879
|
|
|
880
|
+
// The build the server is on, as a page reported it: a page told 409 - its
|
|
881
|
+
// build is not the server's - says so here before it loads the document,
|
|
882
|
+
// and answers with the same to say it has been heard. Until this worker is
|
|
883
|
+
// replaced, a stored document from any other build is served from the
|
|
884
|
+
// network rather than the cache. Without this a phone lived in a loop for
|
|
885
|
+
// as long as the new worker took to install: the old worker served the old
|
|
886
|
+
// document from cache, the old client was told 409 on its first tap, loaded
|
|
887
|
+
// the document - the old one again - and so on, once per url, a document
|
|
888
|
+
// load per tap, for the tens of seconds a phone spends precaching.
|
|
889
|
+
let serverBuild = null
|
|
890
|
+
|
|
891
|
+
self.addEventListener('message', (event) => {
|
|
892
|
+
const data = event.data
|
|
893
|
+
|
|
894
|
+
if (data && data.type === 'rsc-kit:server-build' && typeof data.version === 'string') {
|
|
895
|
+
serverBuild = data.version
|
|
896
|
+
|
|
897
|
+
if (event.ports && event.ports[0]) event.ports[0].postMessage({ type: 'rsc-kit:server-build', version: serverBuild })
|
|
898
|
+
}
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
// Whether a cached document may be the previous build's: the server has
|
|
902
|
+
// been reported on another build than the copy carries, or a newer worker
|
|
903
|
+
// is installing or waiting behind this one.
|
|
904
|
+
const maybeStale = (hit) => {
|
|
905
|
+
if (!hit) return false
|
|
906
|
+
if (self.registration && (self.registration.installing || self.registration.waiting)) return true
|
|
907
|
+
|
|
908
|
+
const built = hit.headers.get('X-RSC-Version')
|
|
909
|
+
|
|
910
|
+
return serverBuild !== null && built !== null && built !== serverBuild
|
|
911
|
+
}
|
|
912
|
+
|
|
872
913
|
// Whether a response may be kept at all.
|
|
873
914
|
//
|
|
874
915
|
// \`no-store\` is not advice here, it is the answer. A query defaults to
|
|
@@ -905,11 +946,11 @@ const keyFor = (request) => {
|
|
|
905
946
|
const standIn = async (request) => {
|
|
906
947
|
if (!OFFLINE_URL) return null
|
|
907
948
|
|
|
908
|
-
if (request.mode === 'navigate') return (await
|
|
949
|
+
if (request.mode === 'navigate') return (await own(OFFLINE_URL)) ?? null
|
|
909
950
|
|
|
910
951
|
if (request.headers.get('X-RSC') && !request.headers.get('X-RSC-Segments')) {
|
|
911
952
|
return (
|
|
912
|
-
(await
|
|
953
|
+
(await own(
|
|
913
954
|
keyFor(new Request(new URL(OFFLINE_URL, self.location.origin), { headers: { 'X-RSC': '1' } })),
|
|
914
955
|
MATCH,
|
|
915
956
|
)) ?? null
|
|
@@ -960,7 +1001,7 @@ self.addEventListener('fetch', (event) => {
|
|
|
960
1001
|
// differently for every value of it.
|
|
961
1002
|
if (FROZEN.has(url.pathname.replace(/\\/+$/, '') || '/') && !url.search) {
|
|
962
1003
|
event.respondWith(
|
|
963
|
-
|
|
1004
|
+
own(keyFor(request), MATCH).then((hit) => {
|
|
964
1005
|
const fresh = fetch(request)
|
|
965
1006
|
.then((response) => {
|
|
966
1007
|
if (mayStore(response)) {
|
|
@@ -984,7 +1025,11 @@ self.addEventListener('fetch', (event) => {
|
|
|
984
1025
|
})
|
|
985
1026
|
|
|
986
1027
|
// Not awaited when there is a hit: the point is that the visitor does
|
|
987
|
-
// not wait for the network for a page that cannot have changed
|
|
1028
|
+
// not wait for the network for a page that cannot have changed - and
|
|
1029
|
+
// a deploy is how it changes, so a copy that may be the previous
|
|
1030
|
+
// build's waits for the network, with the copy as the fallback.
|
|
1031
|
+
if (maybeStale(hit)) return fresh.catch(() => hit)
|
|
1032
|
+
|
|
988
1033
|
return hit ?? fresh
|
|
989
1034
|
}),
|
|
990
1035
|
)
|
|
@@ -1044,7 +1089,7 @@ self.addEventListener('fetch', (event) => {
|
|
|
1044
1089
|
return response
|
|
1045
1090
|
})
|
|
1046
1091
|
.catch(async () => {
|
|
1047
|
-
const hit = await
|
|
1092
|
+
const hit = await own(keyFor(request), MATCH)
|
|
1048
1093
|
|
|
1049
1094
|
if (hit) return hit
|
|
1050
1095
|
|
|
@@ -2393,6 +2438,7 @@ import { LoadingBoundary } from ${JSON.stringify(join(packageDir, "js/LoadingBou
|
|
|
2393
2438
|
import { DocumentTitle } from ${JSON.stringify(join(packageDir, "js/DocumentTitle"))}
|
|
2394
2439
|
import { SlotBoundary } from ${JSON.stringify(join(packageDir, "js/SlotBoundary"))}
|
|
2395
2440
|
import { RouteErrorBoundary } from ${JSON.stringify(join(packageDir, "js/RouteErrorBoundary"))}
|
|
2441
|
+
import __rsc_assets from 'virtual:vite-rsc/assets-manifest'
|
|
2396
2442
|
import { sectionComponent } from ${JSON.stringify(join(packageDir, "js/section"))}
|
|
2397
2443
|
import { PathnameProvider } from ${JSON.stringify(join(packageDir, "js/PathnameProvider"))}
|
|
2398
2444
|
import { DefaultRouteError } from ${JSON.stringify(join(packageDir, "js/DefaultRouteError"))}
|
|
@@ -2659,14 +2705,48 @@ export function manifest(): any {
|
|
|
2659
2705
|
return ${JSON.stringify(routeManifest())}
|
|
2660
2706
|
}
|
|
2661
2707
|
|
|
2708
|
+
/**
|
|
2709
|
+
* The stylesheet and the client entry every document of this build links,
|
|
2710
|
+
* from the build's own manifest, for the host's Link header - a CDN sends
|
|
2711
|
+
* it ahead as 103 Early Hints. The root layout's stylesheet is the one the
|
|
2712
|
+
* document carries; the entry's own is usually empty, and either way both
|
|
2713
|
+
* are named. See earlyHints.ts.
|
|
2714
|
+
*/
|
|
2715
|
+
export function criticalAssets(): { styles: string[]; modules: string[]; fonts: string[] } {
|
|
2716
|
+
// Nothing to send ahead in development: the entry is Vite's own url, and
|
|
2717
|
+
// there is no CDN in front to hint.
|
|
2718
|
+
if (import.meta.env.DEV) return { styles: [], modules: [], fonts: [] }
|
|
2719
|
+
|
|
2720
|
+
const manifest = __rsc_assets as {
|
|
2721
|
+
clientEntryUrl?: string
|
|
2722
|
+
clientEntryDeps?: { css?: string[] }
|
|
2723
|
+
serverResources?: Record<string, { css?: string[] }>
|
|
2724
|
+
}
|
|
2725
|
+
const styles = new Set<string>(manifest.clientEntryDeps?.css ?? [])
|
|
2726
|
+
|
|
2727
|
+
const rootLayouts = ['app/layout.tsx', 'app/layout.jsx', 'app/layout.ts', 'app/layout.js']
|
|
2728
|
+
|
|
2729
|
+
for (const [file, resources] of Object.entries(manifest.serverResources ?? {})) {
|
|
2730
|
+
if (rootLayouts.some((name) => file === name || file.endsWith('/' + name))) {
|
|
2731
|
+
for (const href of resources.css ?? []) styles.add(href)
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
return {
|
|
2736
|
+
styles: [...styles],
|
|
2737
|
+
modules: manifest.clientEntryUrl ? [manifest.clientEntryUrl] : [],
|
|
2738
|
+
fonts: [],
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2662
2742
|
let cachedBuildId: string | null = null
|
|
2663
2743
|
|
|
2664
2744
|
/**
|
|
2665
2745
|
* What this build's client is, as a short id.
|
|
2666
2746
|
*
|
|
2667
|
-
* Derived from the bootstrap script the document ships
|
|
2668
|
-
*
|
|
2669
|
-
*
|
|
2747
|
+
* Derived from the bootstrap script the document ships and the assets
|
|
2748
|
+
* manifest behind it - the entry, every client reference and the chunks
|
|
2749
|
+
* each loads - so two builds whose browsers would disagree never share one.
|
|
2670
2750
|
* The host puts it on every answer and compares it against what a client
|
|
2671
2751
|
* says it is running; a mismatch on a payload request is a 409, and the
|
|
2672
2752
|
* client loads the document instead. Read once, on demand, because the
|
|
@@ -2675,7 +2755,13 @@ let cachedBuildId: string | null = null
|
|
|
2675
2755
|
export async function buildId(): Promise<string> {
|
|
2676
2756
|
if (cachedBuildId !== null) return cachedBuildId
|
|
2677
2757
|
|
|
2678
|
-
|
|
2758
|
+
// The bootstrap script names the entry; the assets manifest names every
|
|
2759
|
+
// client reference and the chunks each loads. A client component added
|
|
2760
|
+
// to a layout changes the manifest and not always the entry - the
|
|
2761
|
+
// reference map is its own chunk - and that is exactly the change an old
|
|
2762
|
+
// client cannot survive, so the id covers both.
|
|
2763
|
+
const content: string =
|
|
2764
|
+
(await (import.meta as any).viteRsc.loadBootstrapScriptContent('index')) + JSON.stringify(__rsc_assets)
|
|
2679
2765
|
let hash = 2166136261
|
|
2680
2766
|
|
|
2681
2767
|
for (let i = 0; i < content.length; i++) {
|
|
@@ -4475,6 +4561,7 @@ async function serve(request: Request): Promise<Response> {
|
|
|
4475
4561
|
resolveMetadata,
|
|
4476
4562
|
runRouteMiddleware,
|
|
4477
4563
|
buildId,
|
|
4564
|
+
criticalAssets,
|
|
4478
4565
|
} as never,
|
|
4479
4566
|
${NITRO_HANDLER_OPTIONS}${NITRO_PRERENDERED} maxActionBody: ${maxActionBody === undefined ? "undefined" : String(maxActionBody)},
|
|
4480
4567
|
})
|
|
@@ -4558,6 +4645,7 @@ function generateEntrySsr() {
|
|
|
4558
4645
|
const fallbackReport = join(packageDir, "js/fallbackReport");
|
|
4559
4646
|
const redirectDigestModule = join(packageDir, "redirectDigest");
|
|
4560
4647
|
const notFoundModule = join(packageDir, "notFound");
|
|
4648
|
+
const earlyClicks = join(packageDir, "js/earlyClicks");
|
|
4561
4649
|
return `// GENERATED by rscKit() — do not edit.
|
|
4562
4650
|
import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr'
|
|
4563
4651
|
import { renderToReadableStream, resume } from 'react-dom/server.edge'
|
|
@@ -4567,6 +4655,7 @@ import { noteFallback } from ${JSON.stringify(request)}
|
|
|
4567
4655
|
import { cancelledByConsumer, caughtByLoading } from ${JSON.stringify(fallbackReport)}
|
|
4568
4656
|
import { parseRedirectDigest } from ${JSON.stringify(redirectDigestModule)}
|
|
4569
4657
|
import { isNotFoundDigest } from ${JSON.stringify(notFoundModule)}
|
|
4658
|
+
import { EARLY_CLICKS } from ${JSON.stringify(earlyClicks)}
|
|
4570
4659
|
|
|
4571
4660
|
// Set only by the dev server. @vitejs/plugin-rsc emits its bootstrap and CSS
|
|
4572
4661
|
// links root-relative in dev, which would send the browser to the host for
|
|
@@ -4585,8 +4674,11 @@ export async function handleSsr(
|
|
|
4585
4674
|
// Without the bootstrap the page ships no client runtime at all: no React,
|
|
4586
4675
|
// no Flight client, no router. HTML only. A page with nothing interactive on
|
|
4587
4676
|
// it has no use for 70kB of hydration.
|
|
4677
|
+
// The click listener first, then the runtime's import: a tap in the
|
|
4678
|
+
// seconds before the runtime hydrates is held for it rather than given
|
|
4679
|
+
// to the browser as a document load. See earlyClicks.ts.
|
|
4588
4680
|
const bootstrapScriptContent = bootstrap
|
|
4589
|
-
? await (import.meta as any).viteRsc.loadBootstrapScriptContent('index')
|
|
4681
|
+
? EARLY_CLICKS + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
|
|
4590
4682
|
: undefined
|
|
4591
4683
|
|
|
4592
4684
|
// Without an onError handler React rejects each abortable task on its own,
|
|
@@ -4628,7 +4720,7 @@ export async function handleSsrPrerender(
|
|
|
4628
4720
|
const bootstrapScriptContent =
|
|
4629
4721
|
options.bootstrap === false
|
|
4630
4722
|
? undefined
|
|
4631
|
-
: await (import.meta as any).viteRsc.loadBootstrapScriptContent('index')
|
|
4723
|
+
: EARLY_CLICKS + (await (import.meta as any).viteRsc.loadBootstrapScriptContent('index'))
|
|
4632
4724
|
|
|
4633
4725
|
const { prelude, postponed } = await prerender(root as any, {
|
|
4634
4726
|
bootstrapScriptContent,
|
|
@@ -5458,6 +5550,16 @@ export function rscKit(options = {}) {
|
|
|
5458
5550
|
if (typeof nitro.options.compressPublicAssets !== "object")
|
|
5459
5551
|
nitro.options.compressPublicAssets = true;
|
|
5460
5552
|
nitro.options.virtual ??= {};
|
|
5553
|
+
// The worker script is never cached by heuristics. It was served
|
|
5554
|
+
// with no Cache-Control at all, and a browser keeps such a file for a
|
|
5555
|
+
// tenth of its age: a deploy's new worker then installs the previous
|
|
5556
|
+
// deploy's list against the new server, every file 404s, and the
|
|
5557
|
+
// install fails - leaving the old worker in charge, serving the old
|
|
5558
|
+
// documents, for as long as the browser holds the script.
|
|
5559
|
+
nitro.options.routeRules = {
|
|
5560
|
+
...(nitro.options.routeRules ?? {}),
|
|
5561
|
+
"/sw.js": { ...(nitro.options.routeRules?.["/sw.js"] ?? {}), headers: { "cache-control": "no-cache", ...(nitro.options.routeRules?.["/sw.js"]?.headers ?? {}) } },
|
|
5562
|
+
};
|
|
5461
5563
|
// The names of functions survive Nitro's bundle. The pages were
|
|
5462
5564
|
// prerendered against Vite's own server bundles, where a dependency
|
|
5463
5565
|
// is loaded from node_modules under the names it was written with;
|