Skip to content

feat(font): replace generated google font catalog with virtual import rewrites#656

Merged
southpolesteve merged 1 commit intomainfrom
codex/font-google-transform-pr
Mar 22, 2026
Merged

feat(font): replace generated google font catalog with virtual import rewrites#656
southpolesteve merged 1 commit intomainfrom
codex/font-google-transform-pr

Conversation

@southpolesteve
Copy link
Collaborator

Summary

Closes #200.

This reworks the core idea from #262 by @yunus25jmi1, but switches the implementation to per-import virtual modules instead of local const rewrites.

That keeps the maintenance win of deleting the generated font-google.generated.ts catalog, while preserving normal ESM semantics for named imports and broadening compatibility for real-world call sites.

What Changed

  • delete packages/vinext/src/shims/font-google.generated.ts
  • rewrite named next/font/google imports and re-exports to virtual modules that export only the requested fonts
  • keep createFontLoader as a public shim export for direct runtime use and tests
  • continue self-hosting in build mode, now with support for aliased lowercase locals and proxy member calls like fonts.Roboto_Mono(...)
  • transform dependency files that import next/font/google, not just app source files
  • keep the generated .d.ts for IDE autocomplete and types

Why This Shape

The earlier approach in #262 had a good goal, but converting imports into local const declarations changed module evaluation semantics and dropped compatibility for some import patterns.

Using virtual modules avoids the TDZ/hoisting regression for named imports while still removing the giant generated runtime catalog.

Testing

  • vp test run tests/font-google.test.ts
  • vp test run tests/shims.test.ts -t "next/font/google shim"
  • vp check packages/vinext/src/index.ts packages/vinext/src/shims/font-google.ts packages/vinext/src/shims/font-google-base.ts packages/vinext/src/shims/next-shims-font-google.generated.d.ts scripts/generate-google-fonts.js tests/font-google.test.ts tests/shims.test.ts

Credit

Credit to @yunus25jmi1 for the original PR and direction in #262: #262

Reworks the font-catalog removal from #262 using per-import virtual modules so named imports keep normal ESM semantics, aliased/default calls still self-host in build mode, and dependency imports are transformed too.

Credit to @yunus25jmi1 for the original PR and direction in #262.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 22, 2026

Open in StackBlitz

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

commit: 7fa91cb

@github-actions
Copy link

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

@southpolesteve
Copy link
Collaborator Author

/bigbonk review please

Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Good change. Replacing the 2,251-line generated runtime catalog with per-import virtual modules is a clear win — smaller package, same ESM semantics, no TDZ regressions from the const-rewrite approach in #262.

What I verified

  • Virtual module lifecycle: resolveIdloadgenerateGoogleFontsVirtualModule correctly handles the \0-prefix, root-prefix, and query-string patterns. The generated module re-exports default/utilities from the real shim and creates per-font createFontLoader bindings.

  • Import clause parsing: parseGoogleFontImportClause handles the main patterns correctly — named { Inter, Roboto }, default, namespace * as fonts, mixed default, { Inter }, type-only. The "type " prefix detection for type-only imports is straightforward.

  • Build-mode self-hosting: fontLocals maps local names (including aliases like inter from Inter as inter) back to the original font name for the namedCallRe scan. proxyObjectLocals tracks default/namespace locals for memberCallRe. The overwrittenRanges guard prevents MagicString double-overwrites.

  • Proxy underscore fix in font-google-base.ts: Adding .replace(/_/g, " ") before the PascalCase split means Roboto_Mono"Roboto Mono" works correctly. Previously only PascalCase splitting was applied, so underscored names wouldn't resolve to the right family. This is a real bug fix.

  • Script changes: generate-google-fonts.js now only generates the .d.ts (for IDE autocomplete) and the test fixture JSON, dropping the .ts runtime catalog. Clean separation.

  • Filter broadening: Removing exclude: /node_modules/ from the transform filter means dependencies that re-export from next/font/google get rewritten too. This is correct — third-party libraries shouldn't need to know about vinext internals.

Minor observations (non-blocking)

  1. [^}]* in namedCallRe / memberCallRe: These regexes won't match options objects containing nested braces (e.g., Inter({ weight: '400', axes: { wght: 400 } })). This was already the case before this PR and parseStaticObjectLiteral would reject nested objects for self-hosting anyway, so no regression, but worth noting for future work if someone reports nested font options not self-hosting.

  2. Utility-only imports pass through untransformed: import { createFontLoader } from 'next/font/google' with no font imports — fontImports.length === 0 so neither virtual module nor namespace branch fires. The import resolves via the existing shim alias, which is correct. But it means the transform returns null for this case (no changes). Just want to confirm this is intentional — I believe it is since the shim resolver already handles next/font/googlefont-google.ts.

  3. URL encoding mismatch between plugin and runtime: The build plugin constructs font URLs with family.replace(/\s+/g, "+") then passes through URLSearchParams.set(), producing Roboto%2BMono (double-encoded). The runtime buildGoogleFontsUrl passes raw "Roboto Mono" to URLSearchParams.set(), producing Roboto+Mono. These are semantically equivalent to Google's API (both work), and they don't share a cache, so no functional issue. But if you ever unify the URL construction, this divergence would cause cache misses.

CI is green across all checks. LGTM.

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 22, 2026

I've approved PR #656 after a thorough review. Here's the summary:

Verdict: Approved

The PR cleanly replaces the 2,251-line generated runtime font catalog (font-google.generated.ts) with per-import virtual modules that generate only the fonts each file actually needs. The approach preserves normal ESM semantics (avoiding the TDZ regression from the earlier const-rewrite approach in #262), supports all import patterns (named, default, namespace, mixed, aliased, re-exports), and broadens compatibility to dependency files in node_modules.

Key things I verified:

  • Virtual module resolution lifecycle (resolveId/load) handles all Vite ID prefixing variants correctly
  • Import clause parser covers the main ESM patterns
  • Build-mode self-hosting correctly tracks aliased locals and proxy member calls
  • The underscore→space fix in the proxy's get trap is a real bug fix
  • Removal of node_modules from the transform filter exclusion is correct for third-party compatibility
  • CI is fully green

I noted three non-blocking observations: the [^}]* regex limitation with nested braces (pre-existing), utility-only imports passing through to the shim resolver (intentional), and a URL encoding divergence between the plugin and runtime (no functional impact since they don't share a cache).

github run

@southpolesteve southpolesteve merged commit 7325f0f into main Mar 22, 2026
30 checks passed
@southpolesteve southpolesteve deleted the codex/font-google-transform-pr branch March 22, 2026 19:06
NathanDrake2406 pushed a commit to NathanDrake2406/vinext that referenced this pull request Mar 23, 2026
… rewrites (cloudflare#656)

Reworks the font-catalog removal from cloudflare#262 using per-import virtual modules so named imports keep normal ESM semantics, aliased/default calls still self-host in build mode, and dependency imports are transformed too.

Credit to @yunus25jmi1 for the original PR and direction in cloudflare#262.
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.

Explore Vite transform for next/font/google imports (eliminate generated font catalog)

1 participant