Skip to content

[DS 2.0] Ship the fonts as assets instead of base64 - #93

Open
librowski wants to merge 6 commits into
ds2-nav-buttonfrom
ds2-font-assets
Open

[DS 2.0] Ship the fonts as assets instead of base64#93
librowski wants to merge 6 commits into
ds2-nav-buttonfrom
ds2-font-assets

Conversation

@librowski

Copy link
Copy Markdown
Collaborator

Fonts stop being base64-inlined. Vite's library mode inlines every asset unconditionally (assetsInlineLimit has no effect there), so the built stylesheets carried 382 KB of font payload.

What changed — the @font-face rules are generated after Vite finishes, from the fontsource metadata, and the files are copied into dist/assets:

  • Poppins 400 and 600 latin stay inlined — the only weights typography.css declares — so ordinary text needs no extra request and shows no swap flash.
  • Every other face (Poppins 300/500/700, all latin-ext, Inter) is a .woff2 fetched on demand.
  • Each face now carries unicode-range, which the per-subset fontsource stylesheets omit. Without it a browser has to consider both subsets; with it, a document without extended latin never fetches those files.
  • The legacy .woff source is gone — that duplication is why twelve faces produced twenty-four payloads.

Sizesui/dist/index.css 509 KB → 150 KB, sdk/dist/style.css 591 KB → 230 KB, ui/dist/styles.css 7 KB → 32 KB (it now carries the two inlined faces). Ten .woff2 files ship in each package's dist/assets.

Why the SDK is in scope — it bundles the UI stylesheet into its own, so it carried the same payload; it now appends the generated block and copies the assets, reading them as artifacts from the UI build rather than importing its build code.

New gatecheck-built-css fails when a stylesheet references an asset missing from dist. That is the failure mode this arrangement invites, and the one an earlier font change hit only on a clean CI install. Verified by deleting a file and watching the build fail.

For consumers — imports are unchanged, and the subpath path finally works as documented: styles.css now really does bring the typefaces. Two caveats, both in the changeset: a Content-Security-Policy naming font-src needs 'self' (or the serving origin) rather than data:, and the dist layout has to survive copying, since the stylesheets reference ./assets/*.woff2. Bundlers handle that themselves.

overview.mdx gains a short section listing what each style surface provides and what it does not — which also closes the gap a reviewer flagged on the typography PR, where subpath consumers were told to add styles.css for typography that then rendered in a system font.

Verified: ui/sdk lint and typecheck, build:ui, build:lib, stylelint, all test suites, docs build, demo build, and the asset gate proven by removing a font file.

@librowski
librowski force-pushed the ds2-font-assets branch 2 times, most recently from 7cf4e39 to 24fddf9 Compare August 26, 2026 11:53
@librowski librowski changed the title Ship the fonts as assets instead of base64 [DS 2.0] Ship the fonts as assets instead of base64 Aug 26, 2026
@librowski
librowski marked this pull request as ready for review August 27, 2026 14:17
}

type FontFaceDefinition = {
family: 'Inter' | 'Poppins';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poppins and Inter are licensed under the SIL OFL 1.1, and this PR starts redistributing their .woff2 files as standalone assets in the npm package. The OFL asks for the license text to accompany the font files when they are redistributed. Strictly speaking this predates the PR, since base64-embedded fonts are redistribution too, but standalone files in dist/assets make it visible. Not a blocker for this PR. As a follow-up, let's either copy the OFL texts from the fontsource packages into dist/assets or add a fonts section to the package LICENSE file. The check-built-css gate could then assert the license file ships next to the assets, so it never silently drops out of the publish.

@piotrblaszczyk

Copy link
Copy Markdown
Contributor

Two follow-up asks, neither blocking. First, could you add a packages/ui/font-assets.decision-log.md for this arrangement? The PR description already carries the context, the decision, and the numbers. What is worth capturing on top are the rejected alternatives: keep everything inline, ship everything as assets, patch the Vite config, or use a CDN. This setup is exactly the kind a future refactor might "simplify" back to fontsource imports, silently reintroducing the 382 KB.

Second, the docs give one preload example, but consumers have to inspect dist/assets to learn the other file names. A small table in overview.mdx listing the twelve shipped faces (family, weight, subset, file name, inline or asset) would close that gap.

The library inlined twelve font faces as base64 because Vite's library
mode inlines every asset unconditionally, so the built stylesheet
carried 382 KB of fonts: index.css was 509 KB and the SDK stylesheet,
which bundles it, 591 KB.

The faces are generated after Vite finishes, from the fontsource
metadata, and copied into dist/assets. Poppins 400 and 600 latin stay
inlined - the only weights the typography classes declare - so the
common text needs no extra request. Everything else is fetched on
demand, and each face now carries the unicode-range the per-subset
fontsource files omit, so a document without extended latin skips
those files entirely. The legacy woff source is gone.

index.css is 150 KB, the SDK stylesheet 230 KB, and dist gains ten
woff2 files. A new gate fails the build when a stylesheet references
an asset that is not in dist - the failure mode this arrangement
invites, and the one a previous font change hit only on clean CI.

Consumers keep their imports; a Content-Security-Policy naming
font-src needs 'self' rather than 'data:', and the dist layout has to
survive copying.
The role table recommended the emphasized large label for chips, while
the chip component set specifies the small regular label at every size
- the row now names buttons alone and chips get their measured role.

Knip's ignore entry moves to the ui workspace, where the fontsource
packages now live and are consumed outside its JS/TS walk, and the
CLAUDE.md table stops claiming knip runs as part of pnpm check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants