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 +17 -0
- package/framework/router.js +18 -4
- package/package.json +1 -1
- package/test/router.test.js +8 -0
- package/test/server.test.js +11 -0
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
|
package/framework/router.js
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
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
package/test/router.test.js
CHANGED
|
@@ -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);
|
package/test/server.test.js
CHANGED
|
@@ -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');
|