@rsc-kit/core 0.21.2 → 0.21.4

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.js CHANGED
@@ -862,12 +862,11 @@ self.addEventListener('install', (event) => {
862
862
  // worker cannot see. Added here so it is in the cache before it is
863
863
  // needed, which is the only moment it cannot be fetched.
864
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
865
+ // Each file on its own rather than cache.addAll, which rejects the whole
866
+ // list for one failure - and a worker whose install rejects is never
867
867
  // installed. The worker before it then stays in charge, serving its own
868
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.
869
+ // a network blip, a quota, a file a deploy replaced mid-install.
871
870
  // A file that did not arrive costs a cache entry; the update still
872
871
  // lands. The browser checks /sw.js for itself, without the page's
873
872
  // javascript, so an install that cannot fail is also what rescues a
@@ -1032,6 +1031,10 @@ const standIn = async (request) => {
1032
1031
  // the ?__rsc=<segments> above is the one header the answer differs on.
1033
1032
  const MATCH = { ignoreVary: true }
1034
1033
 
1034
+ // The documents being fetched to go with a payload, by url - so the payloads
1035
+ // of one page, asked for at several depths, fetch its document once.
1036
+ const documentsInFlight = new Set()
1037
+
1035
1038
  self.addEventListener('fetch', (event) => {
1036
1039
  const request = event.request
1037
1040
  const url = new URL(request.url)
@@ -1109,26 +1112,14 @@ self.addEventListener('fetch', (event) => {
1109
1112
  const copy = response.clone()
1110
1113
  caches.open(CACHE).then((cache) => cache.put(keyFor(request), copy))
1111
1114
 
1112
- // A document is not enough to boot from. The client hydrates from a
1113
- // payload it fetches for itself, and on a first visit that request
1114
- // happens before this worker controls the page — so it is never
1115
- // cached, and a reload with no network has the markup and nothing to
1116
- // hydrate it with.
1117
- //
1118
- // \`X-RSC: 1\` and no segments header is exactly what a fresh boot
1119
- // sends, which is what makes this entry the one it finds: the server
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
- }
1115
+ // No second fetch of the page's payload here. There used to be one,
1116
+ // for offline: a document alone does not boot. But whenever this
1117
+ // worker handles a page load, the page's own boot request - the
1118
+ // same url, X-RSC: 1, no segments - comes through this worker too
1119
+ // and is cached just above. The one time it does not is a first
1120
+ // visit, before the worker controls the page, and then it did not
1121
+ // handle the page load either. So it was a duplicate request on
1122
+ // every page load, at the moment the visitor was waiting.
1132
1123
 
1133
1124
  // And the other way round. Moving between pages fetches payloads and
1134
1125
  // never documents, so a page reached only by a link had nothing to
@@ -1138,16 +1129,20 @@ self.addEventListener('fetch', (event) => {
1138
1129
  // Once per url: the document is fetched only when the cache has none,
1139
1130
  // so this costs one extra request the first time a page is visited
1140
1131
  // rather than one on every navigation to it.
1141
- if (request.headers.get('X-RSC')) {
1132
+ if (request.headers.get('X-RSC') && !documentsInFlight.has(request.url)) {
1142
1133
  const document = new Request(request.url)
1143
1134
 
1144
- caches.open(CACHE).then(async (cache) => {
1145
- if (await cache.match(document)) return
1135
+ documentsInFlight.add(request.url)
1136
+ caches
1137
+ .open(CACHE)
1138
+ .then(async (cache) => {
1139
+ if (await cache.match(document)) return
1146
1140
 
1147
- const fresh = await fetch(document).catch(() => null)
1141
+ const fresh = await fetch(document).catch(() => null)
1148
1142
 
1149
- if (fresh && mayStore(fresh)) await cache.put(document, fresh)
1150
- })
1143
+ if (fresh && mayStore(fresh)) await cache.put(document, fresh)
1144
+ })
1145
+ .finally(() => documentsInFlight.delete(request.url))
1151
1146
  }
1152
1147
  }
1153
1148
 
@@ -1359,7 +1354,45 @@ export function bootsTheApp(file) {
1359
1354
  return true;
1360
1355
  return false;
1361
1356
  }
1362
- function writeServiceWorker(clientDir, frozen = [], offlineUrl = null) {
1357
+ /**
1358
+ * A service worker's version: a hash of everything it serves cache-first.
1359
+ *
1360
+ * It used to be the precache list alone - the hashed file names - on the
1361
+ * reasoning that a deploy changes them. A deploy that changes a stored page
1362
+ * and no client script does not: the list was the same, so sw.js was byte
1363
+ * for byte the same, the browser saw no update, and the worker went on
1364
+ * serving the previous build's page from its cache, cache-first, because "a
1365
+ * deploy changes VERSION and sweeps this cache". Reproduced: deploy build B
1366
+ * over A, reload, and the page is A; only the load after the background
1367
+ * refresh shows B. So the stored pages' bytes are in it too, and a deploy
1368
+ * that changes what the worker holds is a new worker.
1369
+ */
1370
+ export function workerVersion(precache, storedDir) {
1371
+ const version = createHash("sha256");
1372
+ version.update(precache.join("\n"));
1373
+ if (storedDir && existsSync(storedDir)) {
1374
+ const stored = [];
1375
+ const walkStored = (dir) => {
1376
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1377
+ const path = join(dir, entry.name);
1378
+ if (entry.isDirectory())
1379
+ walkStored(path);
1380
+ else
1381
+ stored.push(path);
1382
+ }
1383
+ };
1384
+ walkStored(storedDir);
1385
+ for (const path of stored.sort()) {
1386
+ version.update("\n" + relative(storedDir, path) + "\n");
1387
+ version.update(readFileSync(path));
1388
+ }
1389
+ }
1390
+ return version.digest("hex").slice(0, 12);
1391
+ }
1392
+ function writeServiceWorker(clientDir, frozen = [], offlineUrl = null,
1393
+ // The pages the build stored, which the worker serves cache-first. Their
1394
+ // bytes are part of what a version is - see below.
1395
+ storedDir = null) {
1363
1396
  if (!existsSync(clientDir))
1364
1397
  return;
1365
1398
  // Before the walk, so it lands in the precache with everything else. The
@@ -1383,12 +1416,9 @@ function writeServiceWorker(clientDir, frozen = [], offlineUrl = null) {
1383
1416
  };
1384
1417
  walk(clientDir, "");
1385
1418
  const precache = ["/", ...files.map((file) => `/${file}`)].sort();
1386
- const version = createHash("sha256")
1387
- .update(precache.join("\n"))
1388
- .digest("hex")
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}` +
1419
+ const versionHash = workerVersion(precache, storedDir);
1420
+ writeFileSync(join(clientDir, "sw.js"), SERVICE_WORKER(versionHash, precache, frozen, offlineUrl, extra));
1421
+ log(`offline: ${precache.length} files precached as rsc-kit-${versionHash}` +
1392
1422
  (offlineUrl ? `, falling back to ${offlineUrl}` : "") +
1393
1423
  (extra ? ", with app/sw.js" : ""));
1394
1424
  }
@@ -6526,7 +6556,7 @@ export function rscKit(options = {}) {
6526
6556
  if (webManifestOptions)
6527
6557
  writeWebManifest(clientOut ?? publicAssetsDir, webManifestOptions);
6528
6558
  if (offline) {
6529
- writeServiceWorker(clientOut ?? publicAssetsDir, frozen, offlineFallback(frozen, results));
6559
+ writeServiceWorker(clientOut ?? publicAssetsDir, frozen, offlineFallback(frozen, results), staticDir);
6530
6560
  }
6531
6561
  },
6532
6562
  configResolved(config) {