Files
landing/scripts/critters.mjs

72 lines
2.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Post-build: inline critical CSS in dist/*.html (SvelteKit adapter-static output).
* Runs after vite build; Critters reads/writes relative to dist/.
*
* Critters with preload:'swap' adds onload but does not set rel="preload" as="style",
* so the link stays render-blocking. We fix that in postProcessSwapLinks().
*/
import Critters from 'critters';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, '..');
const DIST = path.join(ROOT, 'dist');
/**
* Critters leaves rel="stylesheet" on swap links; change to rel="preload" as="style"
* so the full CSS loads async and only applies on load (non-blocking).
*/
function postProcessSwapLinks(html) {
return html.replace(/<link\s+([^>]*)>/gi, (full, attrs) => {
if (
!/rel="stylesheet"/i.test(attrs) ||
!/onload="this\.rel='stylesheet'"/i.test(attrs)
) {
return full;
}
const fixed = attrs
.replace(/\brel="stylesheet"\s*/i, 'rel="preload" as="style" ')
.replace(
/\bonload="this\.rel='stylesheet'"/i,
'onload="this.onload=null;this.rel=\'stylesheet\'"',
);
return `<link ${fixed}>`;
});
}
async function main() {
if (!fs.existsSync(DIST)) {
console.error('dist/ not found. Run vite build first.');
process.exit(1);
}
const critters = new Critters({
path: DIST,
preload: 'swap',
noscriptFallback: true,
pruneSource: false,
logLevel: 'warn',
});
const files = fs.readdirSync(DIST).filter((f) => f.endsWith('.html'));
for (const file of files) {
const filePath = path.join(DIST, file);
let html = fs.readFileSync(filePath, 'utf8');
html = await critters.process(html);
html = postProcessSwapLinks(html);
fs.writeFileSync(filePath, html, 'utf8');
console.log('✓ Critical CSS inlined → dist/' + file);
}
console.log('Critical CSS step complete.');
}
main().catch((err) => {
console.error(err);
process.exit(1);
});