postinstall runs scripts/strip-clr-base64-fonts.mjs, which edits Clarity's CSS
inside node_modules and then deletes client/.angular/cache - because the
builder treats node_modules as immutable and would otherwise serve styles built
from the unstripped file. Measured on the Angular 20 builder: a marker
@font-face injected into clr-ui.min.css was absent from a warm build's CSS
(28s) and present in a cold one (88s).
The restore used to run before the install, so that clear deleted the entry
before anything could use it - run c732d75 restored 396MB and logged "cleared
/workspace/.../client/.angular/cache" minutes later. It now runs immediately
after "Install dependencies": the clear has already happened, and every entry
in the cache was produced from a stripped stylesheet, because every run strips
before it builds. The invariant the clear exists to enforce is preserved.
No restore-keys on this cache, deliberately: because the builder cannot see a
change inside node_modules, a prefix match could restore entries compiled
against a different Clarity after a lockfile bump. Exact key only, so a
dependency change misses and rebuilds.
Measured locally with the tree matching the lockfile (CI=true): cold with the
cache cleared 90s, warm with no source change 27s, warm with version.ts
regenerated as a new commit does 58s. So a build that is 99-109s in CI should
land near 58s; the rest needs the version out of the compiled graph, which is
an application change.
The strip script's comment described webpack 5; it now describes what was
measured, and notes that CI restores after it runs.
63 lines
2.3 KiB
JavaScript
63 lines
2.3 KiB
JavaScript
import { readFileSync, writeFileSync, statSync, rmSync, existsSync } from 'fs'
|
|
import { resolve } from 'path'
|
|
|
|
/**
|
|
* Remove Clarity's Metropolis @font-face blocks from clr-ui.min.css.
|
|
*
|
|
* Why: Clarity ships Metropolis as base64 data: URLs. The deployed app
|
|
* runs under CSP `default-src 'self'` (no data: font-src), so every page
|
|
* logs a font-load failure for each weight. Firefox preemptively
|
|
* validates every parsed src against CSP even when a later @font-face
|
|
* supersedes the rule at render time, so the only way to silence the
|
|
* console is to remove the offending blocks from the parsed CSS.
|
|
*
|
|
* Our styles.scss declares the same family/weight/style with same-origin
|
|
* .woff files, so removing Clarity's blocks entirely is safe and leaves
|
|
* Metropolis fully functional.
|
|
*
|
|
* Idempotent: matches by font-family, so works on a fresh install or a
|
|
* file that's already been stripped on a previous run.
|
|
*/
|
|
const target = resolve('node_modules/@clr/ui/clr-ui.min.css')
|
|
|
|
let css
|
|
try {
|
|
css = readFileSync(target, 'utf8')
|
|
} catch (err) {
|
|
if (err.code === 'ENOENT') {
|
|
console.log(`skip: ${target} not found (likely pre-install run)`)
|
|
process.exit(0)
|
|
}
|
|
throw err
|
|
}
|
|
|
|
const sizeBefore = statSync(target).size
|
|
const blockRe = /@font-face\{[^}]*Metropolis[^}]*\}/g
|
|
const matches = css.match(blockRe) ?? []
|
|
|
|
if (matches.length === 0) {
|
|
console.log(`already stripped: ${target}`)
|
|
process.exit(0)
|
|
}
|
|
|
|
const stripped = css.replace(blockRe, '')
|
|
writeFileSync(target, stripped)
|
|
const sizeAfter = Buffer.byteLength(stripped)
|
|
console.log(
|
|
`removed ${matches.length} Metropolis @font-face block(s) from clr-ui.min.css ` +
|
|
`(${sizeBefore} -> ${sizeAfter} bytes, saved ${sizeBefore - sizeAfter})`
|
|
)
|
|
|
|
// The build cache treats node_modules as immutable, so an in-place edit
|
|
// here does not invalidate the entries compiled from this file. Measured
|
|
// on the Angular 20 builder: a marker @font-face injected into
|
|
// clr-ui.min.css was absent from a warm build's CSS and present in a cold
|
|
// one. Drop the cache so the next build re-reads the stripped stylesheet.
|
|
// CI restores its cache after this script runs, so the restore is never
|
|
// deleted by this clear.
|
|
const cacheDir = resolve('.angular/cache')
|
|
if (existsSync(cacheDir)) {
|
|
rmSync(cacheDir, { recursive: true, force: true })
|
|
console.log(`cleared ${cacheDir} (webpack persistent cache)`)
|
|
}
|