Skip to content

krosdai/webfonts

Repository files navigation

webfonts

Build pipeline for self-hosted OFL CJK webfonts that the popular CDNs (Google Fonts, Fontsource) don't carry yet. Each font is subset and chunked with cn-font-split into unicode-range-gated .woff2, then published to npm under @krosdai and served over jsDelivr:

https://cdn.jsdelivr.net/npm/@krosdai/<font>@<version>/...

Browsers download only the glyph chunks a page actually uses.

Available fonts

Package font-family Weights Styles License Upstream jsDelivr
@krosdai/lxgw-bright LXGW Bright 300, 400, 500 normal, italic OFL-1.1 lxgw/LxgwBright@v5.528 index.css
@krosdai/lxgw-bright-gb LXGW Bright GB 300, 400, 500 normal, italic OFL-1.1 lxgw/LxgwBright@v5.528 index.css
@krosdai/lxgw-bright-tc LXGW Bright TC 300, 400, 500 normal, italic OFL-1.1 lxgw/LxgwBright@v5.528 index.css

Served from npm via jsDelivr: https://cdn.jsdelivr.net/npm/@krosdai/<name>@<version>/... — subset with cn-font-split (wrapper 7.4.2, core 7.6.8). Pin an exact version.

Usage

Pick a package from the table above. Each package ships a grid of CSS entry points so you only load what you use — every file is still lazy per unicode-range, and .woff2 bytes are shared across files (mixing entry points never double-downloads a glyph):

File Contains
index.css all weights × all styles
weight-<weight>.css one weight, all styles (e.g. weight-400.css)
style-<style>.css one style, all weights (e.g. style-italic.css)
<weight>-<style>.css exactly one cut (e.g. 400-italic.css)

Via jsDelivr (no install):

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@krosdai/lxgw-bright@2.0.0/index.css"
/>
<style>
  body {
    font-family: "LXGW Bright", serif;
  }
</style>

Swap index.css for any narrower entry point, e.g. .../weight-400.css or .../400-italic.css.

Via npm (bundlers):

npm install @krosdai/lxgw-bright
import "@krosdai/lxgw-bright"; // full bundle (index.css)
import "@krosdai/lxgw-bright/weight-400.css"; // one weight, all styles
import "@krosdai/lxgw-bright/style-italic.css"; // one style, all weights
import "@krosdai/lxgw-bright/400-italic.css"; // exactly one cut

Pin an exact version. The CSS already contains the @font-face rules; you never hand-write them.

Install locally (without publishing)

Build the packages, then pack them into gitignored artifacts/*.tgz:

pnpm pack:fonts        # build:fonts --clean + verify:fonts + npm pack each package
npm install ./artifacts/krosdai-lxgw-bright-2.0.0.tgz

Or point a dependency straight at a built package folder (run pnpm build:fonts first so the generated css/woff2 exist):

// package.json
{ "dependencies": { "@krosdai/lxgw-bright": "file:../webfonts/packages/lxgw-bright" } }

Why npm instead of committing fonts to this repo

Git history is append-only, so committing the generated .woff2 would bloat the repo permanently with every font update. Publishing to npm keeps this repo small (tooling + per-package manifests only), and jsDelivr serves npm packages with the same immutable version pinning. The generated .woff2 is gitignored and ships only inside the published package (via each package.json files allowlist).

Keep packages lean and versions infrequent. Flooding npm with large, many-versioned font packages is the abuse pattern that got accounts blocked from jsDelivr in Dec 2025 — a handful of real, rarely-updated subset fonts (the Fontsource model) is exactly what this is.

Layout

packages/<font>/                 = one npm package, @krosdai/<font>
├── package.json                 name, version, exports, files allowlist (generated)
├── README.md                    per-package usage (generated)
├── OFL.txt                      license
├── metadata.json                provenance + css entry-point map
├── index.css                    full bundle: every weight × style inlined
├── weight-<weight>.css          one weight, all styles      (e.g. weight-400.css)
├── style-<style>.css            one style, all weights       (e.g. style-italic.css)
├── <weight>-<style>.css         one cut, @font-face → ./<weight>-<style>/<hash>.woff2
└── <weight>-<style>/            e.g. 400-normal, 500-italic
    └── <hash>.woff2 (×N)        gitignored; shipped in the npm package; shared by every css above

Every CSS entry point references the same content-hashed .woff2 chunks (no byte duplication — only the small @font-face text repeats per file).

Each package uses independent SemVer (starting 1.0.0); the upstream font version is recorded in metadata.json, the package README, and the CHANGELOG. Bump the package version in fonts.config.ts on any change to the (font source + pipeline) tuple.

Adding or updating a font

  1. Make the source reachable — set upstream.releaseUrl (a tagged tarball URL) and/or keep a local clone at upstream.localPath. sourceMode: "auto" (default) uses the clone if present, else the release tarball, so CI builds from a clean checkout.
  2. Add/adjust an entry in fonts.config.ts: slug (= npm name + jsDelivr path — choose it once), cssFamily, license, upstream, instances, and an independent SemVer version. Bump version whenever the font source or pipeline changes.
  3. Locally: pnpm build:fontspnpm verify:fonts (then pnpm format). Commit, PR, merge.
  4. Note the upstream↔package version mapping in CHANGELOG.md.

Publishing

Merging a version bump to main triggers .github/workflows/publish.yml, which publishes any package whose version isn't on npm yet (scripts/ci-publish.ts queries the registry; unchanged packages are skipped). @krosdai/* is scoped, so all publishes use --access public.

  • New package (not yet on npm) → first publish with the NPM_TOKEN secret — OIDC can't bootstrap a package that doesn't exist yet.
  • Existing package, new version → OIDC trusted publishing (no token, automatic provenance).

One-time setup:

  1. Create a granular npm token — scope @krosdai, Read and write to packages, ≤90-day expiry (the Dec 2025 cap; set a rotation reminder). Add it as the repo secret NPM_TOKEN (Settings → Secrets and variables → Actions). Required before the first publish.
  2. After a package's first (token) publish, add a Trusted Publisher in its npmjs.com settings → repository krosdai/webfonts, workflow publish.yml. Subsequent bumps then publish via OIDC without the token.

Manual alternative (local): npm login, then pnpm release — builds with --clean, verifies, and runs pnpm -r publish --access public.

Development

pnpm install
pnpm build:fonts      # generate packages/**
pnpm verify:fonts     # structural checks
pnpm catalog          # regenerate this README's table only
pnpm lint             # eslint + prettier

License

Each font keeps its upstream license (see each packages/<font>/OFL.txt and the catalog above). Fonts are redistributed under the SIL Open Font License 1.1 with attribution to their original authors. The build tooling in this repo is provided as-is.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages

Generated from krosdai/scaffold