@rsc-kit/core 0.11.0 → 0.13.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/README.md +15 -10
- package/dist/js/Form.d.ts +152 -3
- package/dist/js/Form.js +293 -29
- package/dist/js/Form.js.map +1 -1
- package/dist/js/formEncoding.d.ts +8 -0
- package/dist/js/formEncoding.js +37 -0
- package/dist/js/formEncoding.js.map +1 -0
- package/dist/js/formStore.d.ts +9 -0
- package/dist/js/formStore.js +43 -0
- package/dist/js/formStore.js.map +1 -0
- 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/routes.d.ts +28 -0
- package/dist/routes.js +15 -1
- package/dist/routes.js.map +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +147 -19
- package/dist/vite.js.map +1 -1
- package/package.json +14 -6
- package/dist/js/useForm.d.ts +0 -42
- package/dist/js/useForm.js +0 -164
- package/dist/js/useForm.js.map +0 -1
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.
|
|
@@ -1122,6 +1240,7 @@ function patternOf(segments) {
|
|
|
1122
1240
|
*/
|
|
1123
1241
|
function renderRouteTypes(manifest) {
|
|
1124
1242
|
const patterns = [...new Set(manifest.routes.map((route) => patternOf(route.segments)))].sort();
|
|
1243
|
+
const apis = [...new Set((manifest.apis ?? []).map((route) => patternOf(route.segments)))].sort();
|
|
1125
1244
|
return [
|
|
1126
1245
|
'// @generated — do not edit. Written by the RSC build from the route tree.',
|
|
1127
1246
|
'//',
|
|
@@ -1140,6 +1259,14 @@ function renderRouteTypes(manifest) {
|
|
|
1140
1259
|
? ' routes:\n' + patterns.map((p) => ' | ' + JSON.stringify(p)).join('\n')
|
|
1141
1260
|
: ' // No routes found under the source directory.\n routes: never',
|
|
1142
1261
|
' }',
|
|
1262
|
+
// Api routes are a separate union, so Link refuses an api url and apiUrl()
|
|
1263
|
+
// refuses a page. Linking to an api route navigates the browser away to a
|
|
1264
|
+
// json document, which is the mistake worth catching.
|
|
1265
|
+
' interface RegisterApi {',
|
|
1266
|
+
apis.length > 0
|
|
1267
|
+
? ' apis:\n' + apis.map((p) => ' | ' + JSON.stringify(p)).join('\n')
|
|
1268
|
+
: ' // No route.ts files found under the source directory.\n apis: never',
|
|
1269
|
+
' }',
|
|
1143
1270
|
'}',
|
|
1144
1271
|
'',
|
|
1145
1272
|
].join('\n');
|
|
@@ -3781,7 +3908,7 @@ export function rscKit(options = {}) {
|
|
|
3781
3908
|
const staticDir = clientOut
|
|
3782
3909
|
? join(dirname(clientOut), 'server', NITRO_STATIC_DIR)
|
|
3783
3910
|
: join(outDir, NITRO_STATIC_DIR);
|
|
3784
|
-
const frozen = await prerenderAfterBundles(bundle, staticDir, clientOut ?? publicAssetsDir);
|
|
3911
|
+
const { frozen, results } = await prerenderAfterBundles(bundle, staticDir, clientOut ?? publicAssetsDir);
|
|
3785
3912
|
// Manifest first. The service worker precaches whatever it finds in this
|
|
3786
3913
|
// directory, so writing it afterwards leaves it out of the list — and an
|
|
3787
3914
|
// installed app whose manifest is the one file that needs the network is
|
|
@@ -3789,8 +3916,9 @@ export function rscKit(options = {}) {
|
|
|
3789
3916
|
copyAppAssets(clientOut ?? publicAssetsDir);
|
|
3790
3917
|
if (webManifestOptions)
|
|
3791
3918
|
writeWebManifest(clientOut ?? publicAssetsDir, webManifestOptions);
|
|
3792
|
-
if (offline)
|
|
3793
|
-
writeServiceWorker(clientOut ?? publicAssetsDir, frozen);
|
|
3919
|
+
if (offline) {
|
|
3920
|
+
writeServiceWorker(clientOut ?? publicAssetsDir, frozen, offlineFallback(frozen, results));
|
|
3921
|
+
}
|
|
3794
3922
|
},
|
|
3795
3923
|
configResolved(config) {
|
|
3796
3924
|
isWatch = config.build?.watch != null;
|