Skip to content

fix(plugins): track font call ranges during transform#785

Open
Divkix wants to merge 5 commits intocloudflare:mainfrom
Divkix:fix/issue-751-next-font-google-transform
Open

fix(plugins): track font call ranges during transform#785
Divkix wants to merge 5 commits intocloudflare:mainfrom
Divkix:fix/issue-751-next-font-google-transform

Conversation

@Divkix
Copy link
Copy Markdown
Contributor

@Divkix Divkix commented Apr 5, 2026

Summary

Fixes #751

The `injectSelfHostedCss` function was calling `s.overwrite()` to transform font calls but not adding the call range to `overwrittenRanges`. This could potentially cause issues when multiple font calls exist in the same file, as subsequent transforms might not properly detect that a previous call was already processed.

Changes

Testing

Checklist

  • Bug fix (non-breaking change)
  • Tests added
  • All relevant tests pass
  • Code follows existing patterns

@Divkix Divkix marked this pull request as draft April 5, 2026 12:10
@Divkix Divkix force-pushed the fix/issue-751-next-font-google-transform branch from dbdc9fe to aa3fbb3 Compare April 5, 2026 12:10
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 5, 2026

Open in StackBlitz

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

commit: 0676075

@Divkix Divkix force-pushed the fix/issue-751-next-font-google-transform branch from aa3fbb3 to 49c11de Compare April 5, 2026 12:15
@Divkix Divkix marked this pull request as ready for review April 5, 2026 12:17
@james-elicx
Copy link
Copy Markdown
Collaborator

/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: fix(plugins): track font call ranges during transform

Thanks for looking into #751. The one-line code fix (tracking overwritten ranges in injectSelfHostedCss) is a correct defensive improvement, but it does not actually fix issue #751, and the new test does not reproduce the reported bug.

The fix doesn't address the actual bug

I verified this by reverting the one-line fix and running the new test — it passes without the fix. Here's why:

The overwrittenRanges overlap check (line 757) prevents double-processing of the same source range. But in the reproduction case from #751:

const geistSans = Geist({ subsets: ["latin"] });
const geistMono = Geist_Mono({ subsets: ["latin"] });

These two calls occupy completely separate, non-overlapping character ranges in the original source. The regex at line 740 (namedCallRe) scans the original code string (not the MagicString output), so both calls are found independently. The overlap check was never the issue — both calls were already being processed correctly.

The actual bug from #751 is:

Build fails during [1/5] analyze client references... with: Error: Unexpected token in app/layout.tsx at 234..235

The issue reporter suspects the problem is related to the trailing-comma/double-comma injection pattern, but that was already fixed in the existing codebase (the separator logic at line 723 handles trailing commas correctly). The real cause of #751 likely involves something else — possibly the Vite build pipeline or the interaction between the import rewrite and the font call transform during actual vinext build, which this unit test doesn't exercise.

The test is a near-duplicate of an existing test

The new test ("transforms multiple fonts with identical options patterns") is almost identical to the existing test at line 443 ("handles multiple font imports in one file"). The only differences are:

  • Font names: Geist/Geist_Mono vs Inter/Roboto
  • Options: { subsets: ["latin"] } vs { weight: '400' }

Both test the same code path (two named font imports in one file, both transformed during build). The "identical options" angle isn't actually relevant — the transform processes each call independently based on its source position, not its options content.

PR description inaccuracies

The PR description mentions:

Added E2E fixture font-google-multiple for build verification

No such fixture exists in the diff.

Recommendation

The one-line fix to injectSelfHostedCss is a reasonable defensive improvement (it prevents a theoretical double-overwrite if ranges were to overlap), so I'd keep it. But:

  1. This PR should not claim to fix #751. The test passes without the fix. To actually fix #751, someone needs to reproduce the build failure with the reproduction repo and trace the actual error.
  2. The test should be reworked or dropped. As-is, it's a near-duplicate of an existing test and doesn't add meaningful coverage. If kept, it should at minimum exercise an actual build (e.g., via the Vite createBuilder API or an E2E test) to verify the full pipeline doesn't produce parse errors.


const replacement = `${calleeSource}(${optionsWithCSS})`;
s.overwrite(callStart, callEnd, replacement);
overwrittenRanges.push([callStart, callEnd]);
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.

This line is a reasonable defensive improvement — it ensures overwrittenRanges is consistent after injectSelfHostedCss runs, preventing theoretical double-overwrite of overlapping ranges.

However, it does not fix issue #751. I verified by reverting this line and running the new test — it still passes. The two font calls (Geist(...) and Geist_Mono(...)) occupy non-overlapping character ranges in the original source, so the overlap check at line 757 was never triggered.

The actual error from #751 (Unexpected token in app/layout.tsx at 234..235) likely originates from a different part of the build pipeline. To find the real cause, someone needs to reproduce the build failure with the reproduction repo and trace the actual error through the Vite build.

}
});

it("transforms multiple fonts with identical options patterns (issue #751)", async () => {
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.

This test is a near-duplicate of the existing test at line 443 ("handles multiple font imports in one file"). The only differences are font names (Geist/Geist_Mono vs Inter/Roboto) and options content (subsets vs weight). Both exercise the same code path: two named font imports in one file, both transformed during build.

The "identical options patterns" angle is a red herring — the transform processes each call independently based on source position, not options content. The options content doesn't cause any interaction between the two calls.

If the intent is to add a regression test for #751, it should exercise the actual vinext build pipeline (where the error occurs), not just the transform hook in isolation. The transform hook was already working correctly for this scenario.

});

it("transforms multiple fonts with identical options patterns (issue #751)", async () => {
// Regression test for issue #751: Build fails when multiple fonts have the
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.

This comment says "the second font call wasn't being transformed" — but that's not true. Both calls were being transformed correctly even without the fix. The bug reported in #751 is a parse error during the Vite build pipeline (Unexpected token), not a missing transform.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 5, 2026

Review submitted. Here's a summary of the findings:

The one-line code fix (overwrittenRanges.push([callStart, callEnd]) in injectSelfHostedCss) is a correct defensive improvement, but it does not fix issue #751. I verified this by reverting the fix and running the new test — it passes either way.

The two font calls in the reproduction case (Geist(...) and Geist_Mono(...)) have non-overlapping character ranges, so the overlap check was never triggered. The actual build failure from #751 (Unexpected token at 234..235) likely originates from a different part of the build pipeline that this unit test doesn't exercise.

The new test is also a near-duplicate of an existing test ("handles multiple font imports in one file" at line 443), and the PR description inaccurately claims an E2E fixture was added when no such fixture exists in the diff.

I requested changes with three inline comments explaining these issues.

github run

@james-elicx
Copy link
Copy Markdown
Collaborator

/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: fix(plugins): track font call ranges during transform

I agree with the prior review from bonk. The one-line fix is a correct consistency improvement, but the PR's framing and test coverage need work before this can merge.

The code fix is correct but mislabeled

The overwrittenRanges.push() addition in injectSelfHostedCss is genuinely the right thing to do — every other s.overwrite() call in this function (lines 594-599, 610-614, 640-645) correctly pushes to overwrittenRanges, and this was the only one that didn't. It's a real inconsistency that should be fixed.

However, this does not fix issue #751. The original #751 bug (double-comma from trailing-comma style) was already fixed by #719. The remaining report from DenyVeyten in the issue comments is about missing named exports from the font-google shim at build time — a completely different problem that this PR doesn't address.

The PR title and description should be updated to reflect what this actually is: a defensive consistency fix for range tracking, not a fix for #751.

The build test is valuable but needs rework

The font-google-build.test.ts file is actually a good idea — having a build integration test that exercises the full createBuilder + buildApp() pipeline for fonts is genuinely useful, and I'd like to see it kept. But:

  1. It doesn't verify the fix. The test passes with or without the overwrittenRanges.push line, because the two font calls have non-overlapping ranges.
  2. It only checks that a file exists. After a full build, it verifies fs.stat(buildOutputPath).isFile() — this doesn't assert anything about the font transform output. It should at minimum verify that both fonts appear in the built output with _selfHostedCSS injected.
  3. The fixture is nested inside app-basic. The font-google-multiple/ directory adds a nested layout to the app-basic fixture, which means every other test that builds app-basic now also processes these font files. This could cause unintended test interference. Consider either (a) creating a standalone minimal fixture, or (b) making this a page without its own layout.

Suggested path forward

  1. Keep the one-line fix — it's correct.
  2. Remove the Fixes #751 claim. This doesn't fix that issue.
  3. Rework or drop the build test. If kept, it should use a standalone minimal fixture and assert on actual build output content.
  4. The fixture files can stay if the test is reworked, but the nested layout pattern should be reconsidered.


const replacement = `${calleeSource}(${optionsWithCSS})`;
s.overwrite(callStart, callEnd, replacement);
overwrittenRanges.push([callStart, callEnd]);
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.

This is a correct fix. Every other s.overwrite() call in the transform hook (lines 594-599, 610-614, 640-645) pushes to overwrittenRanges, and this was the only one that didn't. Without this, a pathological case where namedCallRe and memberCallRe both match overlapping ranges (unlikely but theoretically possible with unusual code patterns) could double-overwrite.

That said, for the specific scenario described in the PR (two non-overlapping Geist/Geist_Mono calls), this line has no effect — both calls are found independently by namedCallRe, and their ranges never overlap.

// Verify the build produced output
expect(buildOutputPath).toBeTruthy();
const stats = await fs.stat(buildOutputPath);
expect(stats.isFile()).toBe(true);
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.

This assertion only checks that the output file exists. After running a full buildApp() (which takes up to 2 minutes), the test should verify something meaningful about the build output — for example, that both fonts were transformed correctly:

Suggested change
expect(stats.isFile()).toBe(true);
const content = await fs.readFile(buildOutputPath, 'utf-8');
expect(content).toContain('Geist');

As-is, fs.stat().isFile() would pass even if the font transform was completely broken and the fonts were silently dropped.

}
});

it("should build successfully with multiple Google fonts (Geist + Geist_Mono)", async () => {
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.

The comment claims this reproduces issue #751, but it doesn't — this test passes on main without the one-line fix. Issue #751's root cause (double-comma injection with trailing commas) was already fixed by #719. The remaining report in #751 (DenyVeyten's comment about missing named exports) is a separate issue.

Please update or remove this comment to avoid misleading future readers.

@@ -0,0 +1,30 @@
import type { Metadata } from "next";
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.

Adding a nested layout with next/font/google imports inside app-basic means every test that builds the app-basic fixture will now also process this file's font imports. This could cause unexpected side effects (e.g., tests that mock fetch differently, or tests that assert on the total number of routes/layouts).

Consider either:

  1. Making this a standalone minimal fixture (e.g., tests/fixtures/font-google-multiple/) with just a root layout and page
  2. Converting this to a page without its own layout, so the font imports don't add a layout nesting level

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 5, 2026

Review submitted. Here's a summary of my findings:

The one-line code fix is correctoverwrittenRanges.push([callStart, callEnd]) after injectSelfHostedCss is a genuine consistency fix. Every other s.overwrite() call in the transform hook tracked its range, and this was the only one that didn't.

However, this PR does not fix issue #751. The original bug (double-comma with trailing commas) was already resolved by PR #719. The test passes with or without the fix because the two font calls (Geist/Geist_Mono) have non-overlapping character ranges, so the overlap guard was never triggered.

Key changes requested:

  1. Remove the "Fixes Build fails with Unexpected token in layout.tsx (default create-next-app next/font/google) #751" claim — this is a defensive consistency fix, not a bug fix for that issue
  2. The build test only checks that an output file exists (fs.stat().isFile()), not that fonts were actually transformed correctly
  3. The fixture adds a nested layout inside app-basic which could cause side effects for other tests that build the same fixture
  4. Test comments incorrectly claim to reproduce Build fails with Unexpected token in layout.tsx (default create-next-app next/font/google) #751's error

github run

Divkix added 4 commits April 5, 2026 14:06
The injectSelfHostedCss function was calling s.overwrite() to transform
font calls but not adding the call range to overwrittenRanges. This
could potentially cause issues when multiple font calls exist in the
same file, as subsequent transforms might not properly detect that
a previous call was already processed.

Add the call range to overwrittenRanges after each font call
transformation to ensure proper tracking.

Related to cloudflare#751 - defensive consistency fix
…are#751

Removes the near-duplicate unit test 'transforms multiple fonts with
identical options patterns' from font-google.test.ts. This test was
a duplicate of 'handles multiple font imports in one file' and did not
actually reproduce issue cloudflare#751.

Adds a proper integration test in font-google-build.test.ts that uses
createBuilder + buildApp to exercise the actual Vite build pipeline
where issue cloudflare#751 occurred. Includes fixture at
app-basic/app/font-google-multiple/ with Geist + Geist_Mono fonts
matching the original issue reproduction.

The actual cloudflare#751 bug (Unexpected token error) was already fixed in
previous commits cloudflare#719 (double-comma) and cloudflare#723 (nested-brace).
The defensive overwrittenRanges tracking remains in fonts.ts as
correct defensive code.

All 78 font-related tests pass, plus 291 app-router tests.
- Remove unused beforeAll import
- Remove unnecessary Plugin type import and cast
- Fix formatting from vp check --fix
@Divkix Divkix force-pushed the fix/issue-751-next-font-google-transform branch from 19d4198 to 76cf68c Compare April 5, 2026 21:30
…improve assertions

Moves the font-google-multiple fixture from a nested location inside
app-basic to a standalone fixture at tests/fixtures/font-google-multiple/.
This prevents side effects for other tests that build the app-basic fixture.

Updates the build test to remove misleading comments about reproducing cloudflare#751.
The overwrittenRanges.push() fix in fonts.ts is a defensive consistency
improvement, not a fix for issue cloudflare#751.

- Removes the nested fixture from app-basic/app/font-google-multiple/
- Creates minimal standalone fixture with package.json and vite.config.ts
- Updates test comments to clarify this tests general build validation
- Removes incorrect claim about reproducing cloudflare#751
@Divkix Divkix force-pushed the fix/issue-751-next-font-google-transform branch from 76cf68c to 0676075 Compare April 5, 2026 21:33
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.

Build fails with Unexpected token in layout.tsx (default create-next-app next/font/google)

2 participants