@rsc-kit/core 0.11.0 → 0.12.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/js/updateStore.d.ts +17 -0
- package/dist/js/updateStore.js +44 -0
- package/dist/js/updateStore.js.map +1 -0
- package/dist/js/useAppUpdate.d.ts +4 -0
- package/dist/js/useAppUpdate.js +29 -0
- package/dist/js/useAppUpdate.js.map +1 -0
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +138 -19
- package/dist/vite.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a newer build is live while this page is still running the old one.
|
|
3
|
+
*
|
|
4
|
+
* Free of React for the same reason onlineStore is: the value arrives from a
|
|
5
|
+
* service worker message, which has nothing to do with rendering, and the hook
|
|
6
|
+
* that reads it lives next door.
|
|
7
|
+
*
|
|
8
|
+
* The situation it reports is real rather than theoretical. This package's
|
|
9
|
+
* worker activates immediately rather than waiting for every tab to close, and
|
|
10
|
+
* activating sweeps the previous build's cache — so a page that has been open
|
|
11
|
+
* across a deploy is running javascript whose remaining chunks are gone. It
|
|
12
|
+
* works until it navigates somewhere that needs one.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isUpdated(): boolean;
|
|
15
|
+
export declare function subscribeToUpdates(callback: () => void): () => void;
|
|
16
|
+
/** The server render's answer. A fresh document is by definition not stale. */
|
|
17
|
+
export declare function updatedOnServer(): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a newer build is live while this page is still running the old one.
|
|
3
|
+
*
|
|
4
|
+
* Free of React for the same reason onlineStore is: the value arrives from a
|
|
5
|
+
* service worker message, which has nothing to do with rendering, and the hook
|
|
6
|
+
* that reads it lives next door.
|
|
7
|
+
*
|
|
8
|
+
* The situation it reports is real rather than theoretical. This package's
|
|
9
|
+
* worker activates immediately rather than waiting for every tab to close, and
|
|
10
|
+
* activating sweeps the previous build's cache — so a page that has been open
|
|
11
|
+
* across a deploy is running javascript whose remaining chunks are gone. It
|
|
12
|
+
* works until it navigates somewhere that needs one.
|
|
13
|
+
*/
|
|
14
|
+
let updated = false;
|
|
15
|
+
const listeners = new Set();
|
|
16
|
+
/** A boolean, so React compares snapshots by value and does not loop. */
|
|
17
|
+
function announce() {
|
|
18
|
+
if (updated)
|
|
19
|
+
return;
|
|
20
|
+
updated = true;
|
|
21
|
+
listeners.forEach((fn) => fn());
|
|
22
|
+
}
|
|
23
|
+
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
|
24
|
+
navigator.serviceWorker.addEventListener('message', (event) => {
|
|
25
|
+
if (event.data?.type === 'rsc-kit:updated')
|
|
26
|
+
announce();
|
|
27
|
+
});
|
|
28
|
+
// A worker that took control after this page loaded is the same news by
|
|
29
|
+
// another route — it fires when the page was open across a deploy and the
|
|
30
|
+
// message was posted before this listener existed.
|
|
31
|
+
navigator.serviceWorker.addEventListener('controllerchange', announce);
|
|
32
|
+
}
|
|
33
|
+
export function isUpdated() {
|
|
34
|
+
return updated;
|
|
35
|
+
}
|
|
36
|
+
export function subscribeToUpdates(callback) {
|
|
37
|
+
listeners.add(callback);
|
|
38
|
+
return () => listeners.delete(callback);
|
|
39
|
+
}
|
|
40
|
+
/** The server render's answer. A fresh document is by definition not stale. */
|
|
41
|
+
export function updatedOnServer() {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=updateStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateStore.js","sourceRoot":"","sources":["../../src/js/updateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,IAAI,OAAO,GAAG,KAAK,CAAA;AACnB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;AAEvC,yEAAyE;AACzE,SAAS,QAAQ;IACf,IAAI,OAAO;QAAE,OAAM;IAEnB,OAAO,GAAG,IAAI,CAAA;IACd,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,eAAe,IAAI,SAAS,EAAE,CAAC;IACrE,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAmB,EAAE,EAAE;QAC1E,IAAK,KAAK,CAAC,IAAiC,EAAE,IAAI,KAAK,iBAAiB;YAAE,QAAQ,EAAE,CAAA;IACtF,CAAC,CAAC,CAAA;IAEF,wEAAwE;IACxE,0EAA0E;IAC1E,mDAAmD;IACnD,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAA;AACxE,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAoB;IACrD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAEvB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["/**\n * Whether a newer build is live while this page is still running the old one.\n *\n * Free of React for the same reason onlineStore is: the value arrives from a\n * service worker message, which has nothing to do with rendering, and the hook\n * that reads it lives next door.\n *\n * The situation it reports is real rather than theoretical. This package's\n * worker activates immediately rather than waiting for every tab to close, and\n * activating sweeps the previous build's cache — so a page that has been open\n * across a deploy is running javascript whose remaining chunks are gone. It\n * works until it navigates somewhere that needs one.\n */\n\nlet updated = false\nconst listeners = new Set<() => void>()\n\n/** A boolean, so React compares snapshots by value and does not loop. */\nfunction announce(): void {\n if (updated) return\n\n updated = true\n listeners.forEach((fn) => fn())\n}\n\nif (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {\n navigator.serviceWorker.addEventListener('message', (event: MessageEvent) => {\n if ((event.data as { type?: string } | null)?.type === 'rsc-kit:updated') announce()\n })\n\n // A worker that took control after this page loaded is the same news by\n // another route — it fires when the page was open across a deploy and the\n // message was posted before this listener existed.\n navigator.serviceWorker.addEventListener('controllerchange', announce)\n}\n\nexport function isUpdated(): boolean {\n return updated\n}\n\nexport function subscribeToUpdates(callback: () => void): () => void {\n listeners.add(callback)\n\n return () => listeners.delete(callback)\n}\n\n/** The server render's answer. A fresh document is by definition not stale. */\nexport function updatedOnServer(): boolean {\n return false\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a newer build is live, and how to get it.
|
|
4
|
+
*
|
|
5
|
+
* const { updated, reload } = useAppUpdate()
|
|
6
|
+
*
|
|
7
|
+
* if (updated) return <button onClick={reload}>A new version is ready</button>
|
|
8
|
+
*
|
|
9
|
+
* Reported rather than acted on. Reloading out from under someone mid-form is
|
|
10
|
+
* worse than the staleness it fixes, so this package will not do it for you —
|
|
11
|
+
* what it will do is tell you, which nothing else can, because only the worker
|
|
12
|
+
* knows a new version activated.
|
|
13
|
+
*/
|
|
14
|
+
import { useSyncExternalStore } from "react";
|
|
15
|
+
import { isUpdated, subscribeToUpdates, updatedOnServer } from "./updateStore";
|
|
16
|
+
export function useAppUpdate() {
|
|
17
|
+
const updated = useSyncExternalStore(subscribeToUpdates, isUpdated, updatedOnServer);
|
|
18
|
+
return {
|
|
19
|
+
updated,
|
|
20
|
+
// location.reload() rather than a router navigation: the point is to
|
|
21
|
+
// re-fetch the document and its javascript, and a client navigation
|
|
22
|
+
// deliberately does neither.
|
|
23
|
+
reload: () => {
|
|
24
|
+
if (typeof window !== "undefined")
|
|
25
|
+
window.location.reload();
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=useAppUpdate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAppUpdate.js","sourceRoot":"","sources":["../../src/js/useAppUpdate.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/E,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAAG,oBAAoB,CAAC,kBAAkB,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAErF,OAAO;QACL,OAAO;QACP,qEAAqE;QACrE,oEAAoE;QACpE,6BAA6B;QAC7B,MAAM,EAAE,GAAG,EAAE;YACX,IAAI,OAAO,MAAM,KAAK,WAAW;gBAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9D,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\n/**\n * Whether a newer build is live, and how to get it.\n *\n * const { updated, reload } = useAppUpdate()\n *\n * if (updated) return <button onClick={reload}>A new version is ready</button>\n *\n * Reported rather than acted on. Reloading out from under someone mid-form is\n * worse than the staleness it fixes, so this package will not do it for you —\n * what it will do is tell you, which nothing else can, because only the worker\n * knows a new version activated.\n */\n\nimport { useSyncExternalStore } from \"react\";\nimport { isUpdated, subscribeToUpdates, updatedOnServer } from \"./updateStore\";\n\nexport function useAppUpdate(): { updated: boolean; reload: () => void } {\n const updated = useSyncExternalStore(subscribeToUpdates, isUpdated, updatedOnServer);\n\n return {\n updated,\n // location.reload() rather than a router navigation: the point is to\n // re-fetch the document and its javascript, and a client navigation\n // deliberately does neither.\n reload: () => {\n if (typeof window !== \"undefined\") window.location.reload();\n },\n };\n}\n"]}
|
package/dist/vite.d.ts
CHANGED
|
@@ -189,7 +189,7 @@ export interface RscKitOptions {
|
|
|
189
189
|
* Nothing else is touched. An action is a POST and must never be answered from
|
|
190
190
|
* a cache; anything cross-origin is somebody else's to cache.
|
|
191
191
|
*/
|
|
192
|
-
export declare const SERVICE_WORKER: (version: string, precache: string[], frozen?: string[]) => string;
|
|
192
|
+
export declare const SERVICE_WORKER: (version: string, precache: string[], frozen?: string[], offlineUrl?: string | null, swExtra?: string | null) => string;
|
|
193
193
|
/**
|
|
194
194
|
* Write the worker beside the assets it caches.
|
|
195
195
|
*
|
package/dist/vite.js
CHANGED
|
@@ -566,7 +566,7 @@ function reportAllDynamic() {
|
|
|
566
566
|
* Nothing else is touched. An action is a POST and must never be answered from
|
|
567
567
|
* a cache; anything cross-origin is somebody else's to cache.
|
|
568
568
|
*/
|
|
569
|
-
export const SERVICE_WORKER = (version, precache, frozen = []) => `// GENERATED by rscKit() — do not edit.
|
|
569
|
+
export const SERVICE_WORKER = (version, precache, frozen = [], offlineUrl = null, swExtra = null) => `// GENERATED by rscKit() — do not edit.
|
|
570
570
|
const VERSION = ${JSON.stringify(version)}
|
|
571
571
|
const CACHE = 'rsc-kit-' + VERSION
|
|
572
572
|
const PRECACHE = ${JSON.stringify(precache, null, 2)}
|
|
@@ -581,12 +581,48 @@ const PRECACHE = ${JSON.stringify(precache, null, 2)}
|
|
|
581
581
|
// cache would be wrong in a way the visitor cannot see.
|
|
582
582
|
const FROZEN = new Set(${JSON.stringify(frozen)})
|
|
583
583
|
|
|
584
|
+
// The page to show when a navigation cannot be answered at all.
|
|
585
|
+
//
|
|
586
|
+
// An ordinary route at /offline, stored at build time like any other — nothing
|
|
587
|
+
// special about the file, only about when it is served. Null when the app has
|
|
588
|
+
// none, and then a navigation with nothing cached fails as it always did.
|
|
589
|
+
//
|
|
590
|
+
// Deliberately NOT the cached root. That was tried: the document IS the page
|
|
591
|
+
// here, so the visitor got the home page's markup under the address they asked
|
|
592
|
+
// for and it did not hydrate — a wrong page pretending to be the right one.
|
|
593
|
+
// This one is about being offline whatever url it appears under, so it is the
|
|
594
|
+
// only page that can honestly stand in for another.
|
|
595
|
+
const OFFLINE_URL = ${JSON.stringify(offlineUrl)}
|
|
596
|
+
${swExtra
|
|
597
|
+
? `
|
|
598
|
+
// The app's own worker code — push, notification clicks, background sync.
|
|
599
|
+
//
|
|
600
|
+
// importScripts rather than a bundled import, because this file is evaluated
|
|
601
|
+
// in a worker scope by the browser rather than built: whatever the app wrote is
|
|
602
|
+
// run as it was written, in the same global, so its listeners sit beside the
|
|
603
|
+
// ones below.
|
|
604
|
+
//
|
|
605
|
+
// First, so an app handler for an event this file does not handle is registered
|
|
606
|
+
// before anything here can call respondWith on it.
|
|
607
|
+
self.importScripts(${JSON.stringify(swExtra)})
|
|
608
|
+
`
|
|
609
|
+
: ''}
|
|
610
|
+
|
|
584
611
|
self.addEventListener('install', (event) => {
|
|
585
612
|
// The new worker takes over rather than waiting for every tab to close.
|
|
586
613
|
// Safe here because assets are content-hashed: a page already open keeps
|
|
587
614
|
// asking for the names it was built with, and those are still cached under
|
|
588
615
|
// their own version until this activates and sweeps.
|
|
589
|
-
event.waitUntil(
|
|
616
|
+
event.waitUntil(
|
|
617
|
+
caches
|
|
618
|
+
.open(CACHE)
|
|
619
|
+
// The offline page is fetched rather than copied from the build output:
|
|
620
|
+
// it is served by the host out of the prerendered directory, which this
|
|
621
|
+
// worker cannot see. Added here so it is in the cache before it is
|
|
622
|
+
// needed, which is the only moment it cannot be fetched.
|
|
623
|
+
.then((cache) => cache.addAll(OFFLINE_URL ? [...PRECACHE, OFFLINE_URL] : PRECACHE))
|
|
624
|
+
.then(() => self.skipWaiting()),
|
|
625
|
+
)
|
|
590
626
|
})
|
|
591
627
|
|
|
592
628
|
self.addEventListener('activate', (event) => {
|
|
@@ -594,10 +630,24 @@ self.addEventListener('activate', (event) => {
|
|
|
594
630
|
caches
|
|
595
631
|
.keys()
|
|
596
632
|
.then((keys) => Promise.all(keys.filter((k) => k.startsWith('rsc-kit-') && k !== CACHE).map((k) => caches.delete(k))))
|
|
597
|
-
.then(() => self.clients.claim())
|
|
633
|
+
.then(() => self.clients.claim())
|
|
634
|
+
.then(() => tellTheOpenPages()),
|
|
598
635
|
)
|
|
599
636
|
})
|
|
600
637
|
|
|
638
|
+
// A page open right now is running the previous build's javascript, and the
|
|
639
|
+
// chunks it has not loaded yet were just swept. It cannot fix that by itself —
|
|
640
|
+
// only a reload gets the new ones — so it is told, and the app decides what to
|
|
641
|
+
// say about it.
|
|
642
|
+
//
|
|
643
|
+
// After the sweep rather than before, so a page acting on this immediately
|
|
644
|
+
// reloads into the new version rather than racing the deletion.
|
|
645
|
+
async function tellTheOpenPages() {
|
|
646
|
+
const open = await self.clients.matchAll({ type: 'window' })
|
|
647
|
+
|
|
648
|
+
for (const page of open) page.postMessage({ type: 'rsc-kit:updated', version: VERSION })
|
|
649
|
+
}
|
|
650
|
+
|
|
601
651
|
const immutable = (url) => url.pathname.startsWith('/assets/') || /-[A-Za-z0-9_-]{8,}\\.[a-z]+$/.test(url.pathname)
|
|
602
652
|
|
|
603
653
|
// Whether a response may be kept at all.
|
|
@@ -741,12 +791,25 @@ self.addEventListener('fetch', (event) => {
|
|
|
741
791
|
|
|
742
792
|
if (hit) return hit
|
|
743
793
|
|
|
744
|
-
// Nothing cached for this url and no network to ask.
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
//
|
|
794
|
+
// Nothing cached for this url and no network to ask.
|
|
795
|
+
//
|
|
796
|
+
// Falling back to the cached ROOT was worse than failing: the document
|
|
797
|
+
// IS the page here, so the visitor got the home page's markup under the
|
|
798
|
+
// address they asked for, and it did not hydrate — a wrong page
|
|
799
|
+
// pretending to be the right one.
|
|
800
|
+
//
|
|
801
|
+
// An offline page is different, and is the one page that can honestly
|
|
802
|
+
// stand in for another: it is about being offline, not about the url it
|
|
803
|
+
// appears under. Navigations only — a payload request answered with a
|
|
804
|
+
// document would be decoded as one and throw.
|
|
805
|
+
if (OFFLINE_URL && request.mode === 'navigate') {
|
|
806
|
+
const page = await caches.match(OFFLINE_URL)
|
|
807
|
+
|
|
808
|
+
if (page) return page
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Letting it fail says what is true, and a page already open is
|
|
812
|
+
// unaffected.
|
|
750
813
|
return Response.error()
|
|
751
814
|
}),
|
|
752
815
|
)
|
|
@@ -852,9 +915,59 @@ function writeWebManifest(clientDir, options) {
|
|
|
852
915
|
else
|
|
853
916
|
log(`manifest: ${options.name} is installable`);
|
|
854
917
|
}
|
|
855
|
-
|
|
918
|
+
/**
|
|
919
|
+
* The url a navigation falls back to when nothing else can answer it.
|
|
920
|
+
*
|
|
921
|
+
* An ordinary route at /offline, and it has to be one the build STORED — a
|
|
922
|
+
* page that renders per request cannot be served when there is no request to
|
|
923
|
+
* be made. So a dynamic /offline is refused as a fallback rather than
|
|
924
|
+
* precached and found wanting at the one moment it matters, and the build says
|
|
925
|
+
* which read did it.
|
|
926
|
+
*/
|
|
927
|
+
function offlineFallback(frozen, results) {
|
|
928
|
+
const found = results.find((r) => r.url === '/offline');
|
|
929
|
+
if (!found)
|
|
930
|
+
return null;
|
|
931
|
+
if (!frozen.includes('/offline')) {
|
|
932
|
+
// The reason already reads "dynamic — called cookies()", and this sentence
|
|
933
|
+
// has said "not stored" by the time it gets there, so the prefix would say
|
|
934
|
+
// it twice with a dash in the middle of both.
|
|
935
|
+
const why = found.reason?.replace(/^dynamic — /, '') ?? null;
|
|
936
|
+
log('offline: /offline cannot be the fallback' +
|
|
937
|
+
(why ? `, because it ${why}` : '') +
|
|
938
|
+
'. A fallback has to be servable with no network at all.');
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
return '/offline';
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* The app's own service worker code, if it wrote any.
|
|
945
|
+
*
|
|
946
|
+
* `src/app/sw.js`, copied next to the generated worker and imported by it. Plain
|
|
947
|
+
* javascript rather than TypeScript, and that is not an oversight: it is
|
|
948
|
+
* evaluated by the browser in a worker scope with no build step in front of it,
|
|
949
|
+
* so what is written is what runs. Calling it .js says so.
|
|
950
|
+
*
|
|
951
|
+
* This is the only way to add an event this package does not handle — push,
|
|
952
|
+
* notificationclick, sync — without giving up everything the generated worker
|
|
953
|
+
* does. There is no option for it because there is nothing to configure: the
|
|
954
|
+
* file is there or it is not.
|
|
955
|
+
*/
|
|
956
|
+
function copyServiceWorkerExtra(clientDir) {
|
|
957
|
+
const source = join(sourceDir, 'app', 'sw.js');
|
|
958
|
+
if (!existsSync(source))
|
|
959
|
+
return null;
|
|
960
|
+
copyFileSync(source, join(clientDir, 'sw-app.js'));
|
|
961
|
+
return '/sw-app.js';
|
|
962
|
+
}
|
|
963
|
+
function writeServiceWorker(clientDir, frozen = [], offlineUrl = null) {
|
|
856
964
|
if (!existsSync(clientDir))
|
|
857
965
|
return;
|
|
966
|
+
// Before the walk, so it lands in the precache with everything else. The
|
|
967
|
+
// worker importScripts it while evaluating, which is the one moment it cannot
|
|
968
|
+
// go to the network for it — a worker whose import fails does not start, and
|
|
969
|
+
// then nothing is cached at all.
|
|
970
|
+
const extra = copyServiceWorkerExtra(clientDir);
|
|
858
971
|
const files = [];
|
|
859
972
|
const walk = (dir, prefix) => {
|
|
860
973
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -872,8 +985,10 @@ function writeServiceWorker(clientDir, frozen = []) {
|
|
|
872
985
|
walk(clientDir, '');
|
|
873
986
|
const precache = ['/', ...files.map((file) => `/${file}`)].sort();
|
|
874
987
|
const version = createHash('sha256').update(precache.join('\n')).digest('hex').slice(0, 12);
|
|
875
|
-
writeFileSync(join(clientDir, 'sw.js'), SERVICE_WORKER(version, precache, frozen));
|
|
876
|
-
log(`offline: ${precache.length} files precached as rsc-kit-${version}`
|
|
988
|
+
writeFileSync(join(clientDir, 'sw.js'), SERVICE_WORKER(version, precache, frozen, offlineUrl, extra));
|
|
989
|
+
log(`offline: ${precache.length} files precached as rsc-kit-${version}` +
|
|
990
|
+
(offlineUrl ? `, falling back to ${offlineUrl}` : '') +
|
|
991
|
+
(extra ? ', with app/sw.js' : ''));
|
|
877
992
|
}
|
|
878
993
|
/**
|
|
879
994
|
* The rsc bundle the build just wrote, whatever it decided to call it.
|
|
@@ -1008,10 +1123,13 @@ ${legend(counted)}
|
|
|
1008
1123
|
// and a 3xx is not `ok`. Saying it here as well means the list means what it
|
|
1009
1124
|
// says, rather than being a wider list that happens to be filtered later.
|
|
1010
1125
|
const guarded = new Set(manifest.routes.filter((r) => r.middleware?.length).map((r) => r.component));
|
|
1011
|
-
return
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1126
|
+
return {
|
|
1127
|
+
frozen: results
|
|
1128
|
+
.filter((r) => r.type === 'frozen' && !guarded.has(r.component))
|
|
1129
|
+
.filter((r) => existsSync(join(staticDir, pathKeyOf(r.url) + '.html')))
|
|
1130
|
+
.map((r) => r.url),
|
|
1131
|
+
results,
|
|
1132
|
+
};
|
|
1015
1133
|
}
|
|
1016
1134
|
/**
|
|
1017
1135
|
* Weighs the javascript one stored page makes the browser download.
|
|
@@ -3781,7 +3899,7 @@ export function rscKit(options = {}) {
|
|
|
3781
3899
|
const staticDir = clientOut
|
|
3782
3900
|
? join(dirname(clientOut), 'server', NITRO_STATIC_DIR)
|
|
3783
3901
|
: join(outDir, NITRO_STATIC_DIR);
|
|
3784
|
-
const frozen = await prerenderAfterBundles(bundle, staticDir, clientOut ?? publicAssetsDir);
|
|
3902
|
+
const { frozen, results } = await prerenderAfterBundles(bundle, staticDir, clientOut ?? publicAssetsDir);
|
|
3785
3903
|
// Manifest first. The service worker precaches whatever it finds in this
|
|
3786
3904
|
// directory, so writing it afterwards leaves it out of the list — and an
|
|
3787
3905
|
// installed app whose manifest is the one file that needs the network is
|
|
@@ -3789,8 +3907,9 @@ export function rscKit(options = {}) {
|
|
|
3789
3907
|
copyAppAssets(clientOut ?? publicAssetsDir);
|
|
3790
3908
|
if (webManifestOptions)
|
|
3791
3909
|
writeWebManifest(clientOut ?? publicAssetsDir, webManifestOptions);
|
|
3792
|
-
if (offline)
|
|
3793
|
-
writeServiceWorker(clientOut ?? publicAssetsDir, frozen);
|
|
3910
|
+
if (offline) {
|
|
3911
|
+
writeServiceWorker(clientOut ?? publicAssetsDir, frozen, offlineFallback(frozen, results));
|
|
3912
|
+
}
|
|
3794
3913
|
},
|
|
3795
3914
|
configResolved(config) {
|
|
3796
3915
|
isWatch = config.build?.watch != null;
|