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

Open
opened 2026-05-06 12:39:24 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/158
Author: @dknecht
Created: 2/27/2026
Status: 🔄 Open

Base: mainHead: feat/next-font-implementation


📝 Commits (1)

  • f069e47 feat: implement next/font/google and next/font/local with build-time metrics

📊 Changes

19 files changed (+2375 additions, -753 deletions)

View changed files

📝 .agents/skills/migrate-to-vinext/SKILL.md (+1 -1)
📝 .agents/skills/migrate-to-vinext/references/compatibility.md (+2 -2)
📝 README.md (+3 -3)
📝 packages/vinext/package.json (+2 -0)
📝 packages/vinext/src/check.ts (+2 -2)
packages/vinext/src/font-metrics.ts (+391 -0)
📝 packages/vinext/src/index.ts (+298 -56)
📝 packages/vinext/src/shims/font-google.ts (+131 -310)
📝 packages/vinext/src/shims/font-local.ts (+104 -268)
packages/vinext/src/shims/font-utils.ts (+304 -0)
📝 pnpm-lock.yaml (+24 -5)
📝 tests/check.test.ts (+11 -14)
tests/fixtures/fonts/LICENSE (+7 -0)
tests/fixtures/fonts/inter-latin-400.woff2 (+0 -0)
📝 tests/font-google.test.ts (+63 -30)
📝 tests/font-local-transform.test.ts (+188 -49)
tests/font-metrics.test.ts (+504 -0)
tests/font-utils.test.ts (+326 -0)
📝 tests/shims.test.ts (+14 -13)

📄 Description

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

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/158 **Author:** [@dknecht](https://github.com/dknecht) **Created:** 2/27/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feat/next-font-implementation` --- ### 📝 Commits (1) - [`f069e47`](https://github.com/cloudflare/vinext/commit/f069e47be4012e9fb422ff40278b45581b779a50) feat: implement next/font/google and next/font/local with build-time metrics ### 📊 Changes **19 files changed** (+2375 additions, -753 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/migrate-to-vinext/SKILL.md` (+1 -1) 📝 `.agents/skills/migrate-to-vinext/references/compatibility.md` (+2 -2) 📝 `README.md` (+3 -3) 📝 `packages/vinext/package.json` (+2 -0) 📝 `packages/vinext/src/check.ts` (+2 -2) ➕ `packages/vinext/src/font-metrics.ts` (+391 -0) 📝 `packages/vinext/src/index.ts` (+298 -56) 📝 `packages/vinext/src/shims/font-google.ts` (+131 -310) 📝 `packages/vinext/src/shims/font-local.ts` (+104 -268) ➕ `packages/vinext/src/shims/font-utils.ts` (+304 -0) 📝 `pnpm-lock.yaml` (+24 -5) 📝 `tests/check.test.ts` (+11 -14) ➕ `tests/fixtures/fonts/LICENSE` (+7 -0) ➕ `tests/fixtures/fonts/inter-latin-400.woff2` (+0 -0) 📝 `tests/font-google.test.ts` (+63 -30) 📝 `tests/font-local-transform.test.ts` (+188 -49) ➕ `tests/font-metrics.test.ts` (+504 -0) ➕ `tests/font-utils.test.ts` (+326 -0) 📝 `tests/shims.test.ts` (+14 -13) </details> ### 📄 Description ## 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 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#357
No description provided.