vanilla-jet 1.6.1 → 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,33 @@ 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
+
19
+ ## [1.6.2] - 2026-06-30
20
+
21
+ ### Fixed
22
+
23
+ - **Template externalization now self-cleans:** disabling `externalize_templates` (or never enabling it)
24
+ removes any previously generated `public/scripts/templates.js` (+ `.gz`/`.br`), so toggling the flag
25
+ never leaves a stale 749 KB file behind (which the service worker would otherwise precache).
26
+
27
+ ### Notes
28
+
29
+ - **`externalize_templates` caveat (WebView):** it shrinks the initial HTML for web browsers, but it injects
30
+ all templates via one synchronous `innerHTML`. On slower JS engines (native WebViews — WKWebView /
31
+ Android WebView) that block can stall the boot. **Prefer it OFF for apps loaded primarily inside a
32
+ WebView**; keep it ON only for pure-web apps that benefit from a smaller first byte.
33
+
7
34
  ## [1.6.1] - 2026-06-29
8
35
 
9
36
  ### Security
@@ -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.1",
3
+ "version": "1.6.3",
4
4
  "description": "VannilaJet framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -201,6 +201,10 @@ async function createHTMLFile(content, filePath) {
201
201
  // bundle (defer + document order), so views still find their templates in the DOM at boot.
202
202
  if (opts.externalize_templates) {
203
203
  minified = externalizeTemplates(minified);
204
+ } else {
205
+ // Feature off: remove any previously generated templates.js so a stale 749KB file
206
+ // isn't left behind (and isn't precached by the service worker).
207
+ removeStaleTemplatesFile();
204
208
  }
205
209
 
206
210
  const publicPath = path.join(processCwd(), '/public/pages');
@@ -218,6 +222,22 @@ async function createHTMLFile(content, filePath) {
218
222
  console.log(chalk.green(`Created gzipped version at: ${absolutePath}.gz`));
219
223
  }
220
224
 
225
+ // -- Remove a previously generated public/scripts/templates.js (+ compressed siblings)
226
+ // when externalization is disabled, so toggling the flag never leaves a stale file behind.
227
+ function removeStaleTemplatesFile() {
228
+ const base = path.join(processCwd(), 'public', 'scripts', 'templates.js');
229
+ ['', '.gz', '.br'].forEach((ext) => {
230
+ try {
231
+ if (fs.existsSync(base + ext)) {
232
+ fs.unlinkSync(base + ext);
233
+ console.log(chalk.green(`VanillaJet - removed stale public/scripts/templates.js${ext}`));
234
+ }
235
+ } catch (err) {
236
+ // Non-fatal: never fail the build over cleanup.
237
+ }
238
+ });
239
+ }
240
+
221
241
  // -- Move all <script type="text/template"> blocks out of the page into
222
242
  // public/scripts/templates.js (a tiny injector that adds them to the DOM). Returns the
223
243
  // page HTML with those blocks replaced by a single deferred <script> tag (placed where the
@@ -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);