Files
weichenandCursor 3dbd9770b5 feat(web): precompress dist assets to .br and .gz at build time
Use Node zlib in a post-vite step so the Go server can serve negotiated
variants without per-request compression cost.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 08:18:02 +00:00

71 lines
2.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Build-time precompress for web/dist: write sibling .br / .gz variants.
* Zero npm deps — uses Node zlib (available in node:22-bookworm web-builder).
*/
import fs from 'node:fs';
import path from 'node:path';
import { brotliCompressSync, constants as zlibConstants, gzipSync } from 'node:zlib';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const distDir = path.resolve(__dirname, '..', 'dist');
const MIN_BYTES = 1024;
const COMPRESS_EXT = new Set(['.js', '.css', '.html', '.svg', '.json', '.txt', '.xml', '.map']);
const SKIP_EXT = new Set(['.woff', '.woff2', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.br', '.gz']);
function walk(dir, out = []) {
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, ent.name);
if (ent.isDirectory()) walk(full, out);
else out.push(full);
}
return out;
}
function shouldCompress(filePath) {
const ext = path.extname(filePath).toLowerCase();
if (SKIP_EXT.has(ext)) return false;
if (!COMPRESS_EXT.has(ext)) return false;
const st = fs.statSync(filePath);
return st.size >= MIN_BYTES;
}
function writeIfSmaller(sourcePath, variantPath, buf, label, originalSize) {
if (buf.length >= originalSize) {
if (fs.existsSync(variantPath)) fs.unlinkSync(variantPath);
return null;
}
fs.writeFileSync(variantPath, buf);
const pct = ((1 - buf.length / originalSize) * 100).toFixed(1);
console.log(
` ${label.padEnd(4)} ${path.relative(distDir, sourcePath)} ${originalSize} → ${buf.length} (−${pct}%)`,
);
return buf.length;
}
if (!fs.existsSync(distDir)) {
console.error(`precompress: missing ${distDir}; run vite build first`);
process.exit(1);
}
const files = walk(distDir).filter(shouldCompress);
console.log(`precompress: ${files.length} candidate(s) under ${distDir}`);
let brCount = 0;
let gzCount = 0;
for (const file of files) {
const raw = fs.readFileSync(file);
const br = brotliCompressSync(raw, {
params: {
[zlibConstants.BROTLI_PARAM_QUALITY]: 11,
[zlibConstants.BROTLI_PARAM_SIZE_HINT]: raw.length,
},
});
const gz = gzipSync(raw, { level: 9 });
if (writeIfSmaller(file, `${file}.br`, br, 'br', raw.length) != null) brCount += 1;
if (writeIfSmaller(file, `${file}.gz`, gz, 'gzip', raw.length) != null) gzCount += 1;
}
console.log(`precompress: wrote ${brCount} .br and ${gzCount} .gz`);