Skip to content

fix(fonts): handle nested-brace options objects in self-hosting transform#723

Merged
james-elicx merged 2 commits intomainfrom
fix/font-nested-brace-options
Mar 30, 2026
Merged

fix(fonts): handle nested-brace options objects in self-hosting transform#723
james-elicx merged 2 commits intomainfrom
fix/font-nested-brace-options

Conversation

@james-elicx
Copy link
Copy Markdown
Collaborator

Summary

  • namedCallRe and memberCallRe used \{[^}]*\} which stops at the first }, silently skipping self-hosting for any font call whose options contain a nested object (e.g. Inter({ subsets: ["latin"], axes: { wght: 400 } }))
  • Replace both regexes with a lookahead-only pattern + a findBalancedObject() helper that tracks brace depth and skips string literals
  • Adds regression tests for both the named-import path and the default-import proxy (namespace member) path

…form

namedCallRe and memberCallRe used \{[^}]*\} which stops at the first
closing brace, so calls like Inter({ axes: { wght: 400 } }) were silently
skipped and fell back to CDN delivery.

Replace both regexes with a lookahead-only pattern plus a
findBalancedObject() helper that tracks brace depth and skips string
literals, correctly extracting the full options object regardless of
nesting depth.

Adds two regression tests: one for the named-import path and one for the
default-import proxy (namespace member) path.
@james-elicx
Copy link
Copy Markdown
Collaborator Author

/bigbonk review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 30, 2026

Open in StackBlitz

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

commit: cb9754f

@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

Copy link
Copy Markdown
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.

Review: LGTM

Clean fix for a real silent-failure bug. The old \{[^}]*\} regex in both namedCallRe and memberCallRe would stop at the first }, so any font call with nested objects (e.g. axes: { wght: 400 }) silently skipped self-hosting. The new approach — lookahead-only regex + findBalancedObject() with depth tracking and string-literal skipping — handles this correctly.

What I verified

  • All 62 tests in font-google.test.ts pass, including the two new regression tests
  • findBalancedObject correctly handles: nested braces, single/double/backtick string literals with escape sequences, leading whitespace, and unbalanced input (returns null)
  • Both the named-import path (Inter({...})) and namespace-member path (fonts.Inter({...})) are covered
  • The close-paren scanning after the balanced object correctly tolerates whitespace between } and )
  • No risk of infinite loops — the scanner is bounded by code.length

Minor observations (non-blocking)

  1. Comments and regex literals inside options: findBalancedObject doesn't skip //, /* */ comments or regex literals containing braces. A brace inside a comment or regex would affect the depth count. This is extremely unlikely in font option objects — noting it only for completeness.

  2. Template literal ${...} expressions: Backtick strings are handled by skipping until the closing backtick, which means ${expr} with braces inside is correctly treated as part of the string. However, nested backticks (tagged templates inside template expressions) could theoretically confuse the scanner. Again, not realistic for font options.

  3. Code duplication: The close-paren scanning logic (whitespace skip + ) check + callEnd computation) is identical between the namedCallRe and memberCallRe loops. Could be a small helper, but it's fine as-is for two call sites.

  4. Nested function allocation: findBalancedObject is defined inside if (isBuild) within the transform handler, so it's re-created on every transform call during builds. Could be hoisted to module or closure scope. Negligible perf impact since very few files import next/font/google.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Mar 30, 2026

Review posted. The PR is approved — it's a clean fix for a real silent-failure bug where nested braces in font options (e.g. axes: { wght: 400 }) caused self-hosting to be silently skipped during builds. The findBalancedObject() helper with depth tracking and string-literal skipping is a solid solution, and both code paths (named imports and namespace member calls) are covered by new regression tests. All 62 tests pass.

github run

@james-elicx james-elicx marked this pull request as ready for review March 30, 2026 19:36
@james-elicx james-elicx marked this pull request as draft March 30, 2026 19:38
…dup, hoisting

- Fix _findBalancedObject to correctly skip ${...} interpolations inside
  template literals. The previous backtick skipper treated any '}' as the
  end of the string, which would prematurely exit for values like
  `val ${nested}`. Now tracks an exprDepth counter inside interpolations.

- Extract _findCallEnd(code, objEnd) helper that skips whitespace and
  returns the index after ')'. Removes ~20 lines of duplicated logic
  between the named-call and member-call branches.

- Hoist both helpers to module level (top-level exported functions) so
  they are not recreated on every transform() call.

- Add tests: integration test for string values containing brace chars
  (Inter({ label: "font {bold}" })), plus unit describe blocks for
  _findBalancedObject and _findCallEnd covering string/template/escape/
  interpolation/whitespace/null edge cases.
@james-elicx
Copy link
Copy Markdown
Collaborator Author

/bigbonk review

Copy link
Copy Markdown
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.

Review: Approved

Clean, well-scoped fix for a real silent-failure bug. The old \{[^}]*\} regex in both namedCallRe and memberCallRe would terminate at the first }, silently skipping self-hosting for any font call with nested objects (e.g. axes: { wght: 400 }). The fix is sound.

What I verified

  • All 77 tests pass (including the new regression tests and the _findBalancedObject/_findCallEnd unit tests)
  • _findBalancedObject correctly handles: nested braces, single/double/backtick string literals with escape sequences, ${...} interpolations, leading whitespace, and unbalanced input (returns null)
  • Both code paths are covered: named imports (Inter({...})) and namespace member calls (fonts.Inter({...}))
  • The close-paren scanning via _findCallEnd is clean and correctly handles whitespace between } and )
  • The regex change from capturing the object to a lookahead-only pattern ((?=\{)) is the right approach — it lets the balanced scanner do the heavy lifting without duplicating logic
  • Extracting _findBalancedObject and _findCallEnd as named exported functions (rather than inlining them) makes them independently testable, which the new unit test block takes advantage of

One thing worth noting

The _findBalancedObject function is exported at the top level of index.ts with an underscore-prefix convention for test access. This is consistent with how _parseStaticObjectLiteral, _stripServerExports, etc. are already exported — no issue there.

No concerns. Ship it.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Mar 30, 2026

I've reviewed and approved PR #723. Here's the summary:

The fix is correct. The old \{[^}]*\} regex pattern in both namedCallRe and memberCallRe would stop at the first } character, which meant any font call with nested objects (e.g. Inter({ subsets: ["latin"], axes: { wght: 400 } })) silently skipped self-hosting during builds.

The replacement approach — a lookahead-only regex ((?=\{)) paired with a _findBalancedObject() helper that tracks brace depth and skips string literals — handles this correctly. Both the named-import path and namespace-member path are covered by new regression tests. All 77 tests pass.

github run

@james-elicx james-elicx marked this pull request as ready for review March 30, 2026 19:48
@james-elicx james-elicx merged commit d60c810 into main Mar 30, 2026
32 checks passed
@james-elicx james-elicx deleted the fix/font-nested-brace-options branch March 30, 2026 19:48
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.

1 participant