Files
ta1sph.info/scripts/subset-fonts.mjs
T
suphicakir 7d8fcce2e1 feat: add OG images, favicon set, manifest and performance tuning
- Build-time Open Graph cards per post and a default card (Satori + Sharp)
- Favicon/ICO/apple-touch/manifest icons generated from one SVG (npm run icons)
- robots.txt and web manifest generated from site.json
- Subset self-hosted fonts to Latin + Turkish (188 KB -> 66 KB) with preload
- Inline the stylesheet to remove the render-blocking request
- Post-build site audit (npm run check:site): h1, alt, SEO tags, links
- Omit canonical on noindex pages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:36:56 +03:00

83 lines
3.0 KiB
JavaScript
Raw 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.
// Builds small self-hosted variable fonts (Latin + Turkish + symbols used in posts) into public/fonts/.
// Sources: @fontsource-variable/{rubik,inter} (SIL OFL 1.1). Usage: npm run fonts
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import subsetFont from 'subset-font';
const require = createRequire(import.meta.url);
const ranges = [
[0x20, 0x7e], // Basic Latin
[0xa0, 0xff], // Latin-1 (ç ö ü · ° × …)
[0x11e, 0x11f], // Ğ ğ
[0x130, 0x131], // İ ı
[0x15e, 0x15f], // Ş ş
[0x394, 0x394], // Δ
[0x3a9, 0x3a9], // Ω
[0x3bb, 0x3bc], // λ μ
[0x2013, 0x2014], // – —
[0x2018, 0x201e], // ‘ ’ ‚ “ ” „
[0x2022, 0x2022], // •
[0x2026, 0x2026], // …
[0x2032, 0x2033], // ′ ″
[0x20ba, 0x20ba], // ₺
[0x2122, 0x2122], // ™
[0x2190, 0x2193], // ← ↑ → ↓
[0x2212, 0x2212], // −
[0x2248, 0x2248], // ≈
[0x2264, 0x2265], // ≤ ≥
];
const text = ranges
.flatMap(([a, b]) => Array.from({ length: b - a + 1 }, (_, i) => String.fromCodePoint(a + i)))
.join('');
// Fontsource ships each family split by unicode-range (latin / latin-ext). Each part is subset
// on its own and keeps Fontsource's unicode-range, so browsers fetch only the parts a page needs.
const families = [
{ pkg: '@fontsource-variable/rubik', family: 'Rubik Variable', slug: 'rubik', wght: [400, 900] },
{ pkg: '@fontsource-variable/inter', family: 'Inter Variable', slug: 'inter', wght: [400, 700] },
];
const parts = ['latin', 'latin-ext'];
/** unicode-range of one Fontsource part, read from the package's own CSS. */
function unicodeRange(pkg, slug, part) {
const css = readFileSync(require.resolve(`${pkg}/index.css`), 'utf8');
const block = css
.split('@font-face')
.find((b) => b.includes(`${slug}-${part}-wght-normal.woff2`));
const m = block?.match(/unicode-range:\s*([^;]+);/);
if (!m) throw new Error(`unicode-range not found for ${slug} ${part}`);
return m[1].trim();
}
mkdirSync('public/fonts', { recursive: true });
const faces = [];
for (const f of families) {
for (const part of parts) {
const source = readFileSync(
require.resolve(`${f.pkg}/files/${f.slug}-${part}-wght-normal.woff2`),
);
const out = await subsetFont(source, text, {
targetFormat: 'woff2',
variationAxes: { wght: { min: f.wght[0], max: f.wght[1] } },
});
const file = `${f.slug}-${part}.woff2`;
writeFileSync(`public/fonts/${file}`, out);
console.log(`${file}: ${source.length} → ${out.length} bytes`);
faces.push(`@font-face {
font-family: '${f.family}';
font-style: normal;
font-display: swap;
font-weight: ${f.wght[0]} ${f.wght[1]};
src: url('/fonts/${file}') format('woff2-variations');
unicode-range: ${unicodeRange(f.pkg, f.slug, part)};
}`);
}
}
writeFileSync(
'src/styles/fonts.css',
`/* Generated by scripts/subset-fonts.mjs — do not edit. Fonts: SIL OFL 1.1 (Rubik, Inter). */\n${faces.join('\n')}\n`,
);
console.log('Wrote src/styles/fonts.css');