Files
Oxicloud/frontend/scripts/precompress.mjs
T

61 lines
2.0 KiB
JavaScript
Raw Normal View History

// Precompress built SPA assets so the Rust web layer can serve them with
// `ServeDir::precompressed_br()/precompressed_gzip()` instead of re-running
// Brotli over the same immutable bundle on every request (the tower-http
// CompressionLayer stays as the on-the-fly fallback for anything without a
// sibling). Runs as the `build` script's final step; uses only node:zlib —
// no dependencies. See benches/STATIC-PRECOMPRESSED.md for the measured win.
import { promises as fs } from 'node:fs';
import path from 'node:path';
import zlib from 'node:zlib';
const OUT_DIR = process.argv[2] ?? '../static-dist';
// Compressible text assets; media formats are already compressed.
const EXTENSIONS = new Set([
'.js',
'.mjs',
'.css',
'.html',
'.svg',
'.json',
'.txt',
'.xml',
'.map',
'.webmanifest'
]);
// Below this size the encoding overhead outweighs the transfer win
// (mirrors the server's SizeAbove(256) predicate).
const MIN_BYTES = 256;
async function* walk(dir) {
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
const p = path.join(dir, entry.name);
if (entry.isDirectory()) yield* walk(p);
else yield p;
}
}
let files = 0;
let inBytes = 0;
let brBytes = 0;
for await (const file of walk(OUT_DIR)) {
if (!EXTENSIONS.has(path.extname(file))) continue;
const data = await fs.readFile(file);
if (data.length < MIN_BYTES) continue;
const br = zlib.brotliCompressSync(data, {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 11,
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: data.length
}
});
const gz = zlib.gzipSync(data, { level: 9 });
// Only keep siblings that actually shrink the asset.
if (br.length < data.length) await fs.writeFile(`${file}.br`, br);
if (gz.length < data.length) await fs.writeFile(`${file}.gz`, gz);
files += 1;
inBytes += data.length;
brBytes += Math.min(br.length, data.length);
}
console.log(
`precompress: ${files} assets, ${(inBytes / 1024).toFixed(0)} KiB → ${(brBytes / 1024).toFixed(0)} KiB brotli (${inBytes ? ((1 - brBytes / inBytes) * 100).toFixed(0) : 0}% smaller)`
);