vanilla-jet 1.5.4 → 1.5.5

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,15 @@ 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.5.5] - 2026-06-28
8
+
9
+ ### Changed
10
+
11
+ - **Fingerprinted assets are now cached immutably.** Static requests carrying a `?v=` (the
12
+ `?v=size-mtime` fingerprint from `dipper.versionedUrl`) are served `Cache-Control: public,
13
+ max-age=31536000, immutable`; non-versioned assets keep `no-cache, must-revalidate`. This eliminates
14
+ per-asset revalidation round-trips for clients without the service worker (notably native WebViews).
15
+
7
16
  ## [1.5.4] - 2026-06-28
8
17
 
9
18
  ### Fixed
@@ -139,7 +139,10 @@ class Router {
139
139
  }
140
140
 
141
141
  let metadata = staticFile.metadata;
142
- let staticHeaders = obj.buildStaticHeaders(extHeader, staticCandidates, staticFile.contentEncoding, metadata);
142
+ // Fingerprinted assets (requested with ?v=size-mtime) are safe to cache forever:
143
+ // any content change produces a new URL. Everything else keeps revalidation.
144
+ let isImmutable = Boolean(request.get('v'));
145
+ let staticHeaders = obj.buildStaticHeaders(extHeader, staticCandidates, staticFile.contentEncoding, metadata, isImmutable);
143
146
 
144
147
  if (obj.isNotModified(req, metadata)) {
145
148
  let notModifiedHeaders = Object.assign({}, staticHeaders);
@@ -280,7 +283,7 @@ class Router {
280
283
  return `${route}|${normalizedEncodings}`;
281
284
  }
282
285
 
283
- buildStaticHeaders(extHeader, candidates, contentEncoding, metadata) {
286
+ buildStaticHeaders(extHeader, candidates, contentEncoding, metadata, isImmutable) {
284
287
  let staticHeaders = Object.assign({}, extHeader);
285
288
  if (contentEncoding) {
286
289
  staticHeaders['Content-Encoding'] = contentEncoding;
@@ -292,8 +295,15 @@ class Router {
292
295
  staticHeaders['Content-Length'] = metadata.size;
293
296
  staticHeaders['ETag'] = metadata.etag;
294
297
  staticHeaders['Last-Modified'] = metadata.lastModified;
295
- // Force revalidation to keep clients fresh without hard reload.
296
- staticHeaders['Cache-Control'] = 'no-cache, must-revalidate';
298
+ if (isImmutable) {
299
+ // Fingerprinted URL (?v=): cache for a year and skip revalidation entirely.
300
+ // This is the big win for clients without the service worker (e.g. native WebViews),
301
+ // which otherwise revalidate every asset on every load.
302
+ staticHeaders['Cache-Control'] = 'public, max-age=31536000, immutable';
303
+ } else {
304
+ // Non-versioned assets: force revalidation to keep clients fresh without a hard reload.
305
+ staticHeaders['Cache-Control'] = 'no-cache, must-revalidate';
306
+ }
297
307
  return staticHeaders;
298
308
  }
299
309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jet",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "description": "VannilaJet framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -78,6 +78,17 @@ test('static asset is served with caching validators', async () => {
78
78
  assert.ok(res.headers.get('last-modified'), 'Last-Modified should be present');
79
79
  });
80
80
 
81
+ test('fingerprinted (?v=) asset is cached immutable; plain asset revalidates', async () => {
82
+ const versioned = await fetch(`${baseUrl}/public/scripts/test.min.js?v=123-456`);
83
+ await versioned.arrayBuffer();
84
+ assert.equal(versioned.status, 200);
85
+ assert.match(versioned.headers.get('cache-control') || '', /immutable/);
86
+
87
+ const plain = await fetch(`${baseUrl}/public/scripts/test.min.js`);
88
+ await plain.arrayBuffer();
89
+ assert.match(plain.headers.get('cache-control') || '', /no-cache/);
90
+ });
91
+
81
92
  test('conditional request returns 304 when ETag matches', async () => {
82
93
  const first = await fetch(`${baseUrl}/public/scripts/test.min.js`);
83
94
  const etag = first.headers.get('etag');