vanilla-jet 1.5.2 → 1.5.4
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 +24 -0
- package/framework/router.js +9 -1
- package/package.json +1 -1
- package/scripts/compress_br.js +39 -26
- package/test/router.test.js +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ 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.4] - 2026-06-28
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Added missing static MIME types so self-hosted web fonts and icons serve correctly:
|
|
12
|
+
`woff`, `woff2`, `eot`, `ico`. Previously `.woff2`/`.woff` returned 404 (only `ttf` was mapped),
|
|
13
|
+
forcing font fallbacks and wasted requests.
|
|
14
|
+
|
|
15
|
+
## [1.5.3] - 2026-06-28
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Static compression now applies to ALL compressible assets, not just the app bundle.**
|
|
20
|
+
`framework/router.js` negotiates `.br`/`.gz` for any `css`/`js` request that has a precompressed
|
|
21
|
+
sibling (falling back to the original otherwise) — previously only `vanilla.min.js`/`app.min.css`.
|
|
22
|
+
This makes self-hosted vendor libraries serve gzip/brotli instead of uncompressed.
|
|
23
|
+
- `scripts/compress_br.js` now walks `public/scripts`, `public/styles`, `public/pages` and emits
|
|
24
|
+
`.gz` + `.br` for every `.js`/`.css`/`.html` (was: only three named files).
|
|
25
|
+
|
|
26
|
+
### Why
|
|
27
|
+
|
|
28
|
+
- Self-hosting third-party libs (to cut CDN origins) only helps if they're served compressed;
|
|
29
|
+
otherwise a large lib (e.g. a 369 KB bundle) would ship uncompressed and regress vs the CDN.
|
|
30
|
+
|
|
7
31
|
## [1.5.2] - 2026-06-28
|
|
8
32
|
|
|
9
33
|
### Added
|
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
|
};
|
|
@@ -199,7 +203,11 @@ class Router {
|
|
|
199
203
|
getStaticCandidates(request, ext, filename) {
|
|
200
204
|
let obj = this;
|
|
201
205
|
let candidates = [{ filename: filename, contentEncoding: '' }];
|
|
202
|
-
|
|
206
|
+
// Any compressible type can be served precompressed; resolveFirstAvailableStaticFile
|
|
207
|
+
// falls back to the original when a .br/.gz sibling doesn't exist. This lets ALL
|
|
208
|
+
// self-hosted assets (vendor libs, plugins, …) be served gzip/brotli, not just the
|
|
209
|
+
// app bundle — otherwise self-hosting a large lib would ship it uncompressed.
|
|
210
|
+
let isCompressible = obj.compressionMimes.includes(ext);
|
|
203
211
|
if (!isCompressible) {
|
|
204
212
|
return candidates;
|
|
205
213
|
}
|
package/package.json
CHANGED
package/scripts/compress_br.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
// (
|
|
3
|
-
//
|
|
1
|
+
// Precompresses build outputs to .gz + .br so the server can serve any self-hosted
|
|
2
|
+
// asset (vendor libs, plugins, bundles, styles, pages) compressed via Accept-Encoding
|
|
3
|
+
// negotiation. Without this, self-hosting a large library would ship it uncompressed.
|
|
4
|
+
// Safe + additive: clients that don't accept br get gzip; those that accept neither
|
|
5
|
+
// get the original (resolveFirstAvailableStaticFile falls back).
|
|
4
6
|
|
|
5
7
|
const fs = require('fs');
|
|
6
8
|
const path = require('path');
|
|
@@ -14,33 +16,44 @@ function processCwd() {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const root = processCwd();
|
|
19
|
+
const DIRS = ['public/scripts', 'public/styles', 'public/pages'];
|
|
20
|
+
const COMPRESSIBLE = new Set(['.js', '.css', '.html']);
|
|
21
|
+
const BROTLI_OPTIONS = { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 } };
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
function walk(dir, out) {
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
} catch (err) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const full = path.join(dir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
walk(full, out);
|
|
34
|
+
} else if (
|
|
35
|
+
COMPRESSIBLE.has(path.extname(entry.name)) &&
|
|
36
|
+
!entry.name.endsWith('.gz') &&
|
|
37
|
+
!entry.name.endsWith('.br')
|
|
38
|
+
) {
|
|
39
|
+
out.push(full);
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
|
-
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const files = [];
|
|
45
|
+
DIRS.forEach((dir) => walk(path.join(root, dir), files));
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
let count = 0;
|
|
48
|
+
files.forEach((file) => {
|
|
34
49
|
try {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const input = fs.readFileSync(filePath);
|
|
40
|
-
const compressed = zlib.brotliCompressSync(input, BROTLI_OPTIONS);
|
|
41
|
-
fs.writeFileSync(filePath + '.br', compressed);
|
|
42
|
-
console.log(`VanillaJet - brotli: ${relativePath}.br (${input.length} -> ${compressed.length} B)`);
|
|
50
|
+
const input = fs.readFileSync(file);
|
|
51
|
+
fs.writeFileSync(file + '.gz', zlib.gzipSync(input, { level: 9 }));
|
|
52
|
+
fs.writeFileSync(file + '.br', zlib.brotliCompressSync(input, BROTLI_OPTIONS));
|
|
53
|
+
count = count + 1;
|
|
43
54
|
} catch (err) {
|
|
44
|
-
//
|
|
55
|
+
// Skip unreadable files; never fail the build over compression.
|
|
45
56
|
}
|
|
46
57
|
});
|
|
58
|
+
|
|
59
|
+
console.log(`VanillaJet - precompressed ${count} assets (.gz + .br)`);
|
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);
|