@rsc-kit/core 0.21.2 → 0.21.3
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/vite.d.ts +18 -2
- package/dist/vite.js +117 -39
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/vite.d.ts
CHANGED
|
@@ -29,8 +29,10 @@ export interface RscKitOptions {
|
|
|
29
29
|
* tags, responses }`; the document says its own with `info`, `servers`,
|
|
30
30
|
* `security`. An api route the build stores, without middleware.
|
|
31
31
|
*
|
|
32
|
-
* The page that reads it is Scalar's: `
|
|
33
|
-
*
|
|
32
|
+
* The page that reads it is Scalar's: `renderApiReference({ config: { url:
|
|
33
|
+
* '/openapi.json' } })` from @scalar/client-side-rendering, returned as an
|
|
34
|
+
* HTML Response from a route.ts. Not @scalar/nextjs-api-reference, whose
|
|
35
|
+
* peer dependency installs all of Next.js.
|
|
34
36
|
*/
|
|
35
37
|
openapi?: boolean | {
|
|
36
38
|
path?: string;
|
|
@@ -347,6 +349,20 @@ export declare function offlineFallback(frozen: string[], results: PrerenderResu
|
|
|
347
349
|
* browser ever renders.
|
|
348
350
|
*/
|
|
349
351
|
export declare function bootsTheApp(file: string): boolean;
|
|
352
|
+
/**
|
|
353
|
+
* A service worker's version: a hash of everything it serves cache-first.
|
|
354
|
+
*
|
|
355
|
+
* It used to be the precache list alone - the hashed file names - on the
|
|
356
|
+
* reasoning that a deploy changes them. A deploy that changes a stored page
|
|
357
|
+
* and no client script does not: the list was the same, so sw.js was byte
|
|
358
|
+
* for byte the same, the browser saw no update, and the worker went on
|
|
359
|
+
* serving the previous build's page from its cache, cache-first, because "a
|
|
360
|
+
* deploy changes VERSION and sweeps this cache". Reproduced: deploy build B
|
|
361
|
+
* over A, reload, and the page is A; only the load after the background
|
|
362
|
+
* refresh shows B. So the stored pages' bytes are in it too, and a deploy
|
|
363
|
+
* that changes what the worker holds is a new worker.
|
|
364
|
+
*/
|
|
365
|
+
export declare function workerVersion(precache: string[], storedDir: string | null): string;
|
|
350
366
|
export declare function smallStylesheetReader(assetsDir: string, setting: "auto" | true | number): (href: string) => string | null;
|
|
351
367
|
/** A server action the bundle registered: where it is and what it is called. */
|
|
352
368
|
export interface KnownAction {
|
package/dist/vite.js
CHANGED
|
@@ -849,6 +849,43 @@ const INSTALL_FETCH_MS = 30000
|
|
|
849
849
|
const within = (ms, promise) =>
|
|
850
850
|
Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error('no answer in ' + ms + ' ms')), ms))])
|
|
851
851
|
|
|
852
|
+
// The same limit, and the download stopped when it is reached. A race only
|
|
853
|
+
// stops the waiting: the fetch it gave up on went on downloading beside
|
|
854
|
+
// everything after it.
|
|
855
|
+
const abortable = (ms, start) => {
|
|
856
|
+
const controller = new AbortController()
|
|
857
|
+
const timer = setTimeout(() => controller.abort(), ms)
|
|
858
|
+
|
|
859
|
+
return within(ms, start(controller.signal)).finally(() => clearTimeout(timer))
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// How many precache downloads run together. The page a visitor is looking at
|
|
863
|
+
// comes first; the precache is for the next visit.
|
|
864
|
+
const PRECACHE_AT_ONCE = 4
|
|
865
|
+
|
|
866
|
+
// Run a task over every item, at most limit of them at a time. Resolves once
|
|
867
|
+
// all have settled; a task that fails is its own business.
|
|
868
|
+
const pool = (items, limit, task) =>
|
|
869
|
+
new Promise((resolve) => {
|
|
870
|
+
let next = 0
|
|
871
|
+
let running = 0
|
|
872
|
+
const launch = () => {
|
|
873
|
+
if (next >= items.length && running === 0) return resolve()
|
|
874
|
+
|
|
875
|
+
while (running < limit && next < items.length) {
|
|
876
|
+
running++
|
|
877
|
+
Promise.resolve(task(items[next++]))
|
|
878
|
+
.catch(() => {})
|
|
879
|
+
.finally(() => {
|
|
880
|
+
running--
|
|
881
|
+
launch()
|
|
882
|
+
})
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
launch()
|
|
887
|
+
})
|
|
888
|
+
|
|
852
889
|
self.addEventListener('install', (event) => {
|
|
853
890
|
// The new worker takes over rather than waiting for every tab to close.
|
|
854
891
|
// Safe here because assets are content-hashed: a page already open keeps
|
|
@@ -872,13 +909,23 @@ self.addEventListener('install', (event) => {
|
|
|
872
909
|
// lands. The browser checks /sw.js for itself, without the page's
|
|
873
910
|
// javascript, so an install that cannot fail is also what rescues a
|
|
874
911
|
// visitor stuck on a worker far older than this one.
|
|
912
|
+
//
|
|
913
|
+
// A few at a time and at low priority, never all at once. An install
|
|
914
|
+
// runs beside the page that triggered it - the first visit after a
|
|
915
|
+
// deploy - and a hundred and seventy files requested together queued
|
|
916
|
+
// that page's own requests behind them: on a phone, the page sat
|
|
917
|
+
// unhydrated for about forty seconds, until the precache had finished.
|
|
875
918
|
.then((cache) =>
|
|
876
|
-
|
|
877
|
-
(
|
|
878
|
-
|
|
879
|
-
|
|
919
|
+
pool(OFFLINE_URL ? [...PRECACHE, OFFLINE_URL] : PRECACHE, PRECACHE_AT_ONCE, (url) =>
|
|
920
|
+
abortable(INSTALL_FETCH_MS, (signal) =>
|
|
921
|
+
fetch(url, { priority: 'low', signal }).then((response) => {
|
|
922
|
+
if (!response.ok) throw new Error('status ' + response.status)
|
|
923
|
+
|
|
924
|
+
return cache.put(url, response)
|
|
880
925
|
}),
|
|
881
|
-
)
|
|
926
|
+
).catch((error) => {
|
|
927
|
+
console.warn('[rsc-kit] offline: ' + url + ' was not precached (' + (error && error.message || error) + '); it will be cached the first time it is fetched')
|
|
928
|
+
}),
|
|
882
929
|
).then(() => cache),
|
|
883
930
|
)
|
|
884
931
|
// And the payload each precached page boots from. A document alone is
|
|
@@ -1032,6 +1079,10 @@ const standIn = async (request) => {
|
|
|
1032
1079
|
// the ?__rsc=<segments> above is the one header the answer differs on.
|
|
1033
1080
|
const MATCH = { ignoreVary: true }
|
|
1034
1081
|
|
|
1082
|
+
// The documents being fetched to go with a payload, by url - so the payloads
|
|
1083
|
+
// of one page, asked for at several depths, fetch its document once.
|
|
1084
|
+
const documentsInFlight = new Set()
|
|
1085
|
+
|
|
1035
1086
|
self.addEventListener('fetch', (event) => {
|
|
1036
1087
|
const request = event.request
|
|
1037
1088
|
const url = new URL(request.url)
|
|
@@ -1109,26 +1160,14 @@ self.addEventListener('fetch', (event) => {
|
|
|
1109
1160
|
const copy = response.clone()
|
|
1110
1161
|
caches.open(CACHE).then((cache) => cache.put(keyFor(request), copy))
|
|
1111
1162
|
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1114
|
-
//
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
//
|
|
1119
|
-
//
|
|
1120
|
-
// varies on those, and the Cache API matches on the same.
|
|
1121
|
-
if (request.mode === 'navigate') {
|
|
1122
|
-
const warm = new Request(request.url, { headers: { 'X-RSC': '1' } })
|
|
1123
|
-
|
|
1124
|
-
fetch(warm)
|
|
1125
|
-
.then((payload) => {
|
|
1126
|
-
if (mayStore(payload)) {
|
|
1127
|
-
caches.open(CACHE).then((cache) => cache.put(keyFor(warm), payload))
|
|
1128
|
-
}
|
|
1129
|
-
})
|
|
1130
|
-
.catch(() => {})
|
|
1131
|
-
}
|
|
1163
|
+
// No second fetch of the page's payload here. There used to be one,
|
|
1164
|
+
// for offline: a document alone does not boot. But whenever this
|
|
1165
|
+
// worker handles a page load, the page's own boot request - the
|
|
1166
|
+
// same url, X-RSC: 1, no segments - comes through this worker too
|
|
1167
|
+
// and is cached just above. The one time it does not is a first
|
|
1168
|
+
// visit, before the worker controls the page, and then it did not
|
|
1169
|
+
// handle the page load either. So it was a duplicate request on
|
|
1170
|
+
// every page load, at the moment the visitor was waiting.
|
|
1132
1171
|
|
|
1133
1172
|
// And the other way round. Moving between pages fetches payloads and
|
|
1134
1173
|
// never documents, so a page reached only by a link had nothing to
|
|
@@ -1138,16 +1177,20 @@ self.addEventListener('fetch', (event) => {
|
|
|
1138
1177
|
// Once per url: the document is fetched only when the cache has none,
|
|
1139
1178
|
// so this costs one extra request the first time a page is visited
|
|
1140
1179
|
// rather than one on every navigation to it.
|
|
1141
|
-
if (request.headers.get('X-RSC')) {
|
|
1180
|
+
if (request.headers.get('X-RSC') && !documentsInFlight.has(request.url)) {
|
|
1142
1181
|
const document = new Request(request.url)
|
|
1143
1182
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1183
|
+
documentsInFlight.add(request.url)
|
|
1184
|
+
caches
|
|
1185
|
+
.open(CACHE)
|
|
1186
|
+
.then(async (cache) => {
|
|
1187
|
+
if (await cache.match(document)) return
|
|
1146
1188
|
|
|
1147
|
-
|
|
1189
|
+
const fresh = await fetch(document).catch(() => null)
|
|
1148
1190
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1191
|
+
if (fresh && mayStore(fresh)) await cache.put(document, fresh)
|
|
1192
|
+
})
|
|
1193
|
+
.finally(() => documentsInFlight.delete(request.url))
|
|
1151
1194
|
}
|
|
1152
1195
|
}
|
|
1153
1196
|
|
|
@@ -1359,7 +1402,45 @@ export function bootsTheApp(file) {
|
|
|
1359
1402
|
return true;
|
|
1360
1403
|
return false;
|
|
1361
1404
|
}
|
|
1362
|
-
|
|
1405
|
+
/**
|
|
1406
|
+
* A service worker's version: a hash of everything it serves cache-first.
|
|
1407
|
+
*
|
|
1408
|
+
* It used to be the precache list alone - the hashed file names - on the
|
|
1409
|
+
* reasoning that a deploy changes them. A deploy that changes a stored page
|
|
1410
|
+
* and no client script does not: the list was the same, so sw.js was byte
|
|
1411
|
+
* for byte the same, the browser saw no update, and the worker went on
|
|
1412
|
+
* serving the previous build's page from its cache, cache-first, because "a
|
|
1413
|
+
* deploy changes VERSION and sweeps this cache". Reproduced: deploy build B
|
|
1414
|
+
* over A, reload, and the page is A; only the load after the background
|
|
1415
|
+
* refresh shows B. So the stored pages' bytes are in it too, and a deploy
|
|
1416
|
+
* that changes what the worker holds is a new worker.
|
|
1417
|
+
*/
|
|
1418
|
+
export function workerVersion(precache, storedDir) {
|
|
1419
|
+
const version = createHash("sha256");
|
|
1420
|
+
version.update(precache.join("\n"));
|
|
1421
|
+
if (storedDir && existsSync(storedDir)) {
|
|
1422
|
+
const stored = [];
|
|
1423
|
+
const walkStored = (dir) => {
|
|
1424
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1425
|
+
const path = join(dir, entry.name);
|
|
1426
|
+
if (entry.isDirectory())
|
|
1427
|
+
walkStored(path);
|
|
1428
|
+
else
|
|
1429
|
+
stored.push(path);
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
walkStored(storedDir);
|
|
1433
|
+
for (const path of stored.sort()) {
|
|
1434
|
+
version.update("\n" + relative(storedDir, path) + "\n");
|
|
1435
|
+
version.update(readFileSync(path));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
return version.digest("hex").slice(0, 12);
|
|
1439
|
+
}
|
|
1440
|
+
function writeServiceWorker(clientDir, frozen = [], offlineUrl = null,
|
|
1441
|
+
// The pages the build stored, which the worker serves cache-first. Their
|
|
1442
|
+
// bytes are part of what a version is - see below.
|
|
1443
|
+
storedDir = null) {
|
|
1363
1444
|
if (!existsSync(clientDir))
|
|
1364
1445
|
return;
|
|
1365
1446
|
// Before the walk, so it lands in the precache with everything else. The
|
|
@@ -1383,12 +1464,9 @@ function writeServiceWorker(clientDir, frozen = [], offlineUrl = null) {
|
|
|
1383
1464
|
};
|
|
1384
1465
|
walk(clientDir, "");
|
|
1385
1466
|
const precache = ["/", ...files.map((file) => `/${file}`)].sort();
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
.slice(0, 12);
|
|
1390
|
-
writeFileSync(join(clientDir, "sw.js"), SERVICE_WORKER(version, precache, frozen, offlineUrl, extra));
|
|
1391
|
-
log(`offline: ${precache.length} files precached as rsc-kit-${version}` +
|
|
1467
|
+
const versionHash = workerVersion(precache, storedDir);
|
|
1468
|
+
writeFileSync(join(clientDir, "sw.js"), SERVICE_WORKER(versionHash, precache, frozen, offlineUrl, extra));
|
|
1469
|
+
log(`offline: ${precache.length} files precached as rsc-kit-${versionHash}` +
|
|
1392
1470
|
(offlineUrl ? `, falling back to ${offlineUrl}` : "") +
|
|
1393
1471
|
(extra ? ", with app/sw.js" : ""));
|
|
1394
1472
|
}
|
|
@@ -6526,7 +6604,7 @@ export function rscKit(options = {}) {
|
|
|
6526
6604
|
if (webManifestOptions)
|
|
6527
6605
|
writeWebManifest(clientOut ?? publicAssetsDir, webManifestOptions);
|
|
6528
6606
|
if (offline) {
|
|
6529
|
-
writeServiceWorker(clientOut ?? publicAssetsDir, frozen, offlineFallback(frozen, results));
|
|
6607
|
+
writeServiceWorker(clientOut ?? publicAssetsDir, frozen, offlineFallback(frozen, results), staticDir);
|
|
6530
6608
|
}
|
|
6531
6609
|
},
|
|
6532
6610
|
configResolved(config) {
|