vanilla-jet 1.6.2 → 1.6.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/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable project changes are documented in this file.
4
4
 
5
5
  The format follows a structure inspired by Keep a Changelog and semantic versioning.
6
6
 
7
+ ## [1.6.3] - 2026-07-16
8
+
9
+ ### Fixed
10
+
11
+ - **SW precache no longer reads stale bundles from the browser's HTTP cache:** install now fetches
12
+ the fingerprinted urls (`?v=size-mtime`, guaranteed HTTP-cache miss) with `cache: 'no-cache'` as a
13
+ second guard. Previously it fetched the bare asset paths through the HTTP cache; with
14
+ `static_cache_max_age` set, a new SW could pin an outdated bundle into a brand-new cache and —
15
+ because serving is cache-first with `ignoreSearch` — every deploy became invisible to returning
16
+ users for up to `static_cache_max_age` seconds unless they hard-reloaded (which bypasses the SW
17
+ per spec but never rewrites its Cache Storage). Diagnosed in production on a consumer app.
18
+
7
19
  ## [1.6.2] - 2026-06-30
8
20
 
9
21
  ### Fixed
@@ -11,11 +11,18 @@
11
11
  * produces a new cache and activate() purges the stale ones. Because VanillaJet
12
12
  * fingerprints asset URLs (`?v=size-mtime`), matches use { ignoreSearch: true }
13
13
  * so a cache entry keeps serving across version query changes within the cache.
14
+ *
15
+ * Precaching fetches the FINGERPRINTED urls (PRECACHE_URLS) with cache: 'no-cache'.
16
+ * Both guards exist so install never reads a stale copy from the browser's HTTP
17
+ * cache: bare asset paths may sit there for `static_cache_max_age` seconds, and a
18
+ * precache that goes through it would pin an outdated bundle into a brand-new
19
+ * cache — making every deploy invisible until a hard reload or header expiry.
14
20
  */
15
21
 
16
22
  const CACHE_NAME = '__CACHE_NAME__';
17
23
  const CACHE_PREFIX = '__CACHE_PREFIX__';
18
24
  const PRECACHE_ASSETS = __PRECACHE_ASSETS__;
25
+ const PRECACHE_URLS = __PRECACHE_URLS__;
19
26
  const ON_DEMAND_PREFIXES = __ON_DEMAND_PREFIXES__;
20
27
 
21
28
  const MATCH_OPTIONS = { ignoreSearch: true };
@@ -30,10 +37,12 @@ function isCacheable(pathname) {
30
37
  globalThis.addEventListener('install', (event) => {
31
38
  event.waitUntil(
32
39
  caches.open(CACHE_NAME).then((cache) =>
33
- Promise.allSettled(PRECACHE_ASSETS.map((asset) => cache.add(asset))).then((results) => {
40
+ Promise.allSettled(
41
+ PRECACHE_URLS.map((asset) => cache.add(new Request(asset, { cache: 'no-cache' })))
42
+ ).then((results) => {
34
43
  results.forEach((result, i) => {
35
44
  if (result.status === 'rejected') {
36
- console.error('SW precache failed: ' + PRECACHE_ASSETS[i], result.reason);
45
+ console.error('SW precache failed: ' + PRECACHE_URLS[i], result.reason);
37
46
  }
38
47
  });
39
48
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jet",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "VannilaJet framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -123,6 +123,23 @@ function buildPrecacheList(root, opts, shared) {
123
123
  return precache;
124
124
  }
125
125
 
126
+ // Fingerprint each precache path (`?v=size-mtime`, same scheme the Dipper uses for
127
+ // asset tags). Install fetches THESE urls: a content change produces a new URL, which
128
+ // guarantees an HTTP-cache miss. Precaching the bare path instead can read a stale
129
+ // copy the HTTP cache is allowed to keep for `static_cache_max_age` seconds, pinning
130
+ // an outdated bundle into the new SW cache (deploys invisible until a hard reload).
131
+ function versionAssets(root, precache) {
132
+ return precache.map((assetPath) => {
133
+ const absolute = path.join(root, assetPath.replace(/^\//, ''));
134
+ try {
135
+ const stats = fs.statSync(absolute);
136
+ return `${assetPath}?v=${stats.size}-${Math.floor(stats.mtimeMs)}`;
137
+ } catch (err) {
138
+ return assetPath;
139
+ }
140
+ });
141
+ }
142
+
126
143
  function computeCacheHash(root, precache) {
127
144
  const hash = crypto.createHash('md5');
128
145
  precache.forEach((assetPath) => {
@@ -161,6 +178,7 @@ function main() {
161
178
  : DEFAULT_ON_DEMAND_PREFIXES;
162
179
 
163
180
  const precache = buildPrecacheList(root, opts, shared);
181
+ const precacheUrls = versionAssets(root, precache);
164
182
  const cacheName = `${baseSlug}-sw-${computeCacheHash(root, precache)}`;
165
183
 
166
184
  let template = fs.readFileSync(TEMPLATE_PATH, 'utf8');
@@ -168,6 +186,7 @@ function main() {
168
186
  .replace(/__CACHE_NAME__/g, cacheName)
169
187
  .replace(/__CACHE_PREFIX__/g, cachePrefix)
170
188
  .replace('__PRECACHE_ASSETS__', JSON.stringify(precache, null, '\t'))
189
+ .replace('__PRECACHE_URLS__', JSON.stringify(precacheUrls, null, '\t'))
171
190
  .replace('__ON_DEMAND_PREFIXES__', JSON.stringify(onDemandPrefixes, null, '\t'));
172
191
 
173
192
  const publicDir = path.join(root, 'public');
@@ -38,7 +38,12 @@ describe('service worker generation', () => {
38
38
  assert.match(sw, /testapp-sw-[a-f0-9]{12}/, 'cache name should be content-pinned');
39
39
  assert.match(sw, /\/public\/scripts\/vanilla\.min\.js/);
40
40
  assert.match(sw, /\/public\/styles\/app\.min\.css/);
41
+ // Precache must fetch fingerprinted urls, bypassing any HTTP-cached bare path
42
+ // (a stale HTTP-cache hit would pin an outdated bundle into the new SW cache).
43
+ assert.match(sw, /\/public\/scripts\/vanilla\.min\.js\?v=\d+-\d+/, 'precache urls should be fingerprinted');
44
+ assert.match(sw, /cache: 'no-cache'/, 'precache requests should revalidate against the server');
41
45
  assert.ok(!sw.includes('__PRECACHE_ASSETS__'), 'no placeholders should remain');
46
+ assert.ok(!sw.includes('__PRECACHE_URLS__'), 'no placeholders should remain');
42
47
  assert.ok(!sw.includes('__CACHE_NAME__'), 'no placeholders should remain');
43
48
  } finally {
44
49
  removeWorkspace(ws);