vanilla-jet 1.5.3 → 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,23 @@ 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
+
16
+ ## [1.5.4] - 2026-06-28
17
+
18
+ ### Fixed
19
+
20
+ - Added missing static MIME types so self-hosted web fonts and icons serve correctly:
21
+ `woff`, `woff2`, `eot`, `ico`. Previously `.woff2`/`.woff` returned 404 (only `ttf` was mapped),
22
+ forcing font fallbacks and wasted requests.
23
+
7
24
  ## [1.5.3] - 2026-06-28
8
25
 
9
26
  ### Changed
@@ -31,6 +31,10 @@ class Router {
31
31
  'svg': 'image/svg+xml',
32
32
  'ttf': 'application/x-font-ttf',
33
33
  'otf': 'application/x-font-opentype',
34
+ 'woff': 'font/woff',
35
+ 'woff2': 'font/woff2',
36
+ 'eot': 'application/vnd.ms-fontobject',
37
+ 'ico': 'image/x-icon',
34
38
  'pdf': 'application/pdf',
35
39
  'json': 'application/json'
36
40
  };
@@ -135,7 +139,10 @@ class Router {
135
139
  }
136
140
 
137
141
  let metadata = staticFile.metadata;
138
- 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);
139
146
 
140
147
  if (obj.isNotModified(req, metadata)) {
141
148
  let notModifiedHeaders = Object.assign({}, staticHeaders);
@@ -276,7 +283,7 @@ class Router {
276
283
  return `${route}|${normalizedEncodings}`;
277
284
  }
278
285
 
279
- buildStaticHeaders(extHeader, candidates, contentEncoding, metadata) {
286
+ buildStaticHeaders(extHeader, candidates, contentEncoding, metadata, isImmutable) {
280
287
  let staticHeaders = Object.assign({}, extHeader);
281
288
  if (contentEncoding) {
282
289
  staticHeaders['Content-Encoding'] = contentEncoding;
@@ -288,8 +295,15 @@ class Router {
288
295
  staticHeaders['Content-Length'] = metadata.size;
289
296
  staticHeaders['ETag'] = metadata.etag;
290
297
  staticHeaders['Last-Modified'] = metadata.lastModified;
291
- // Force revalidation to keep clients fresh without hard reload.
292
- 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
+ }
293
307
  return staticHeaders;
294
308
  }
295
309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jet",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "VannilaJet framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -48,6 +48,14 @@ test('isProtectedFile: blocks framework/external/node_modules and top-level file
48
48
  assert.equal(router.isProtectedFile('/public/scripts/vanilla.min.js'), false);
49
49
  });
50
50
 
51
+ test('mimes: serves common web fonts and icons', () => {
52
+ const router = makeRouter();
53
+ assert.equal(router.mimes['woff2'], 'font/woff2');
54
+ assert.equal(router.mimes['woff'], 'font/woff');
55
+ assert.ok(router.mimes['eot']);
56
+ assert.ok(router.mimes['ico']);
57
+ });
58
+
51
59
  test('supportsEncoding: honors q-values and array shape', () => {
52
60
  const router = makeRouter();
53
61
  assert.equal(router.supportsEncoding(['gzip'], 'gzip'), true);
@@ -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');