import { brotliCompressSync, constants } from 'node:zlib'; import { createHash } from 'node:crypto'; import { existsSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs'; import { basename, extname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const buildDir = process.argv[2] ?? join(__dirname, '..', 'build', 'web'); const bootstrapPath = join(buildDir, 'flutter_bootstrap.js'); const hashableEntrypoints = ['main.dart.js', 'main.dart.mjs', 'main.dart.wasm']; const compressibleExtensions = new Set([ '.css', '.html', '.js', '.json', '.mjs', '.svg', '.toml', '.wasm', ]); if (!existsSync(bootstrapPath)) { throw new Error(`Missing Flutter bootstrap file: ${bootstrapPath}`); } let bootstrap = readFileSync(bootstrapPath, 'utf8'); for (const entrypoint of hashableEntrypoints) { const sourcePath = join(buildDir, entrypoint); if (!existsSync(sourcePath)) { continue; } const content = readFileSync(sourcePath); const hash = createHash('sha256').update(content).digest('hex').slice(0, 12); const extension = extname(entrypoint); const stem = entrypoint.slice(0, -extension.length); const hashedName = `${stem}.${hash}${extension}`; const targetPath = join(buildDir, hashedName); renameSync(sourcePath, targetPath); bootstrap = bootstrap.replaceAll(entrypoint, hashedName); } writeFileSync(bootstrapPath, bootstrap); for (const filePath of walk(buildDir)) { if (filePath.endsWith('.br')) { continue; } if (!compressibleExtensions.has(extname(filePath))) { continue; } const content = readFileSync(filePath); writeFileSync( `${filePath}.br`, brotliCompressSync(content, { params: { [constants.BROTLI_PARAM_QUALITY]: 11, }, }), ); } function* walk(directory) { for (const entry of readdirSync(directory, { withFileTypes: true })) { const entryPath = join(directory, entry.name); if (entry.isDirectory()) { yield* walk(entryPath); continue; } if (entry.isFile()) { yield entryPath; } } } console.log(`[userfront] optimized ${basename(buildDir)} with hashed entrypoints and brotli assets`);