Skip to content

feat: implement next/font/google and next/font/local with build-time metrics#158

Open
dknecht wants to merge 1 commit intomainfrom
feat/next-font-implementation
Open

feat: implement next/font/google and next/font/local with build-time metrics#158
dknecht wants to merge 1 commit intomainfrom
feat/next-font-implementation

Conversation

@dknecht
Copy link
Copy Markdown
Member

@dknecht dknecht commented Feb 27, 2026

Summary

  • Implement full next/font/google and next/font/local parity with Next.js, including deterministic hashing, CSS sanitization, and build-time fallback font metrics via @capsizecss/metrics and fontkitten
  • Extract shared modules: font-utils.ts (sanitization, hashing, SSR injection) and font-metrics.ts (capsize math, metrics lookup, fallback generation)
  • Rewrite both Vite plugins with balanced-brace regex parsing, import rename handling, and self-hosted @font-face CSS generation with size-adjust fallbacks

Details

New files

  • packages/vinext/src/font-metrics.ts — Build-time module: capsize math, Google/local font metrics lookup, hashed font-family names, fallback @font-face generation (async, uses dynamic imports for @capsizecss/metrics and fontkitten)
  • packages/vinext/src/shims/font-utils.ts — Shared runtime module: CSS sanitization helpers (escapeCSSString, sanitizeCSSVarName, sanitizeFallback, etc.), deterministic djb2 hashing, FontSSRState container, CSS injection helpers with dedup, shared :root variable dedup across font modules
  • tests/font-utils.test.ts — 45 unit tests for all font-utils functions
  • tests/font-metrics.test.ts — 55 tests including local font file parsing with real Inter woff2 fixture

Plugin changes

  • google-fonts plugin (index.ts): Balanced-brace matching replaces fragile [^}]* regex; localToOriginal map handles import { Inter as MyFont } renames; build mode fetches CSS + woff2 files, rewrites font-family, generates fallback metrics
  • local-fonts plugin (index.ts): Build mode reads font files via fontkitten, generates self-hosted CSS with hashed families and size-adjust fallbacks
  • Both shims refactored to import shared code from font-utils.ts, eliminating ~400 lines of duplication

Type safety

  • Zero as any or ! non-null assertions in test files
  • All test casts use typed interfaces (GoogleFontsTestPlugin, GoogleFontProxy, Record<string, unknown>)
  • Guard-and-throw pattern replaces all non-null assertions

Test coverage

  • 752 tests across 6 files (font-utils, font-metrics, font-google, font-local-transform, shims, check)
  • Covers: CSS injection attacks, unitsPerEm=0 edge case, multiple localFont() calls per file, import renames, balanced-brace parsing, SSR injection dedup

Known limitations (pre-existing, not introduced here)

  • _selfHostedCSS contains absolute filesystem paths — production on Workers needs web-relative paths (requires separate rearchitecting)
  • (this as any)._isBuild in plugins — pre-existing Vite ecosystem pattern

…metrics

Reimplement both font modules with deterministic hashing, CSS sanitization,
fallback font metrics via @capsizecss/metrics, and local font parsing via
fontkitten. Build mode generates self-hosted @font-face CSS with size-adjust
fallbacks matching Next.js behavior.

- Extract shared font-utils.ts (sanitization, hashing, SSR injection helpers)
- Extract font-metrics.ts (capsize math, Google/local metrics, fallback gen)
- Rewrite google-fonts plugin: balanced-brace regex, import rename handling
- Rewrite local-fonts plugin: build-time metrics injection, woff2 preloads
- Add 752 tests across 6 test files (font-utils, font-metrics, font-google,
  font-local-transform, shims, check)
- Zero `as any` or non-null assertions in test files
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Feb 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@158

commit: f3f1381

@github-actions
Copy link
Copy Markdown

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

@dknecht
Copy link
Copy Markdown
Member Author

dknecht commented Feb 27, 2026

/bonk review and fix.

@FredKSchott
Copy link
Copy Markdown
Collaborator

rebase onto latest main to fix your failing lint CI

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Feb 27, 2026

@elithrar Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

@elithrar
Copy link
Copy Markdown
Collaborator

/bigbonk run an extensive review over this change, fix any issues, and then re-review your fixes.

Look at tests to ensure they are high quality and relevant.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Feb 27, 2026

PROMPT_TOO_LARGE: The prompt exceeds the model's context limit.

github run

southpolesteve added a commit that referenced this pull request Feb 27, 2026
…#158)

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: southpolesteve <southpolesteve@users.noreply.github.com>
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
blitss added a commit to streamloop/vinext that referenced this pull request Apr 2, 2026
…parity)

- Fix import regex: [^;\n] prevents cross-line matching
- Add destructure detection for default import pattern (const { Font } = googleFonts)
- Remove isBuild gate — self-hosting runs in dev and build
- Build manual Google Fonts URL to avoid URLSearchParams encoding issues
- Copy woff2 to public/fonts/ for web serving
- Generate hashed font-family names (__FontName_hash)
- Generate fallback @font-face with size-adjust metrics via @capsizecss/metrics
- Inject _hashedFamily, _fallbackCSS, _fallbackFamily into font loader calls
- Runtime: handle hashed families with fallback in font-google-base.ts
- Add font-metrics.ts from PR cloudflare#158 for capsize metric calculation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

3 participants