Files
xamxam/scripts/build-css.mjs
Pontoporeia 20fe4b6c8c Add biome + rolldown + lightningcss build pipeline for JS/CSS bundling & minification
- package.json with biome, rolldown, lightningcss devDependencies  
- biome.json: add CSS formatter support
- scripts/build-css.mjs: lightningcss resolves @import chain, bundles/minifies CSS  
- scripts/build-js.mjs: rolldown per-entry JS bundling (no code splitting)
- scripts/build.mjs: orchestrator for both CSS + JS
- scripts/check-build.mjs: staleness checker for CI/deploy guard
- justfile: add build, build-css, build-js, build-install, build-check recipes
- justfile: deploy recipe now runs build before deploy-code
- head.php + form-page.php: use dist/base.min.css instead of style.css
- All controllers + FormBootstrap: reference dist/*.min.{css,js}
- admin footer: load admin.min.js for all admin pages
- repertoire: use public.min.js instead of individual app JS files
- Fix stray '}' syntax error in admin.css line 305
- .gitignore: add app/public/assets/dist/
2026-06-24 13:09:50 +02:00

153 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Build CSS bundles with Lightning CSS.
*
* - base.min.css: resolves the @import chain in style.css into a single minified file.
* This eliminates ~17 sequential @import requests on every page.
* - admin.min.css: minifies admin.css
* - form.min.css: bundles form-base.css + form-admin.css + filepond vendor CSS
*
* All other page-specific CSS files (public.css, tfe.css, repertoire.css, etc.)
* are minified in-place as individual files.
*
* Output: app/public/assets/dist/
*/
import { bundleAsync } from "lightningcss";
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { resolve, dirname, basename } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "..");
const cssDir = resolve(root, "app/public/assets/css");
const distDir = resolve(root, "app/public/assets/dist");
mkdirSync(distDir, { recursive: true });
const targets = {
chrome: 115 << 16,
firefox: 115 << 16,
safari: 16 << 16,
};
function makeResolver() {
return {
resolve(specifier, from) {
return resolve(dirname(from), specifier);
},
read(filePath) {
return readFileSync(filePath, "utf8");
},
};
}
async function bundleCss(filename, outName) {
const entryPath = resolve(cssDir, filename);
const result = await bundleAsync({
filename: entryPath,
resolver: makeResolver(),
minify: true,
sourceMap: false,
targets,
});
const outPath = resolve(distDir, outName);
writeFileSync(outPath, result.code);
const size = Buffer.byteLength(result.code, "utf8");
console.log(`${outName} (${size.toLocaleString()} bytes)`);
return size;
}
async function concatBundle(filenames, outName) {
const parts = [];
for (const f of filenames) {
const fp = resolve(cssDir, f);
parts.push(readFileSync(fp, "utf8"));
}
const combined = parts.join("\n");
// Use lightningcss to minify the combined content
const result = await bundleAsync({
filename: resolve(cssDir, filenames[0]),
resolver: {
resolve() {
throw new Error("unexpected @import in concat bundle");
},
read(fp) {
if (fp === resolve(cssDir, filenames[0])) {
return combined;
}
// Allow vendor CSS @imports within the combined content
throw new Error(`unexpected file in concat: ${fp}`);
},
},
minify: true,
sourceMap: false,
targets,
});
const outPath = resolve(distDir, outName);
writeFileSync(outPath, result.code);
const size = Buffer.byteLength(result.code, "utf8");
console.log(`${outName} (${size.toLocaleString()} bytes)`);
return size;
}
async function main() {
console.log("🎨 Building CSS bundles…\n");
let total = 0;
// 1. Base bundle: resolve style.css @import chain
total += await bundleCss("style.css", "base.min.css");
// 2. Admin bundle: just minify admin.css (standalone)
total += await bundleCss("admin.css", "admin.min.css");
// 3. Form bundle: concat + minify form-base.css + form-admin.css + filepond vendor CSS
total += await concatBundle(
[
"form-base.css",
"form-admin.css",
"filepond.min.css",
"filepond-plugin-image-preview.min.css",
],
"form.min.css"
);
// 4. Individual page-specific CSS files: minify each
const individualFiles = [
"public.css",
"tfe.css",
"repertoire.css",
"content-page.css",
"system.css",
"file-access.css",
];
for (const f of individualFiles) {
const outName = f.replace(/\.css$/, ".min.css");
total += await bundleCss(f, outName);
}
// 5. Form-base standalone (for partage pages without FilePond)
total += await bundleCss("form-base.css", "form-base.min.css");
// 6. Partage bundle: form-base + filepond (for partage pages with FilePond)
total += await concatBundle(
[
"form-base.css",
"filepond.min.css",
"filepond-plugin-image-preview.min.css",
],
"partage-form.min.css"
);
console.log(`\n✅ CSS bundles done — ${total.toLocaleString()} bytes total\n`);
}
main().catch((err) => {
console.error("❌ CSS build failed:", err);
process.exit(1);
});