Skip to content

fix(i18n): keep production sourcemaps usable when the i18n plugin rewrites a chunk - #25691

Merged
platosha merged 3 commits into
mainfrom
fix/i18n-plugin-breaking-production-sourcemaps
Sep 14, 2026
Merged

platosha merged 3 commits into
mainfrom
fix/i18n-plugin-breaking-production-sourcemaps

Conversation

@totally-not-ai

@totally-not-ai totally-not-ai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The Vaadin i18n build plugin replaced the sourcemap of every chunk with an empty one, so all .map files of an app built with build.sourcemap enabled were useless. The plugin now rewrites chunks at a point where the bundler keeps the original sourcemap, and leaves untouched chunks alone.

Fixes #16679

What changed

Behavior change: only apps that build with build.sourcemap enabled are affected, and only for the better — their .map files now contain the real sources instead of being empty. Chunk output itself is unchanged, so no API or runtime behavior changes for anyone else.

  • rollup-plugin-vaadin-i18n.js moves the chunk rewriting from generateBundle to renderChunk. The bundler then composes the map returned by the plugin with the map it already has for that chunk. Chunks the plugin does not touch return null and keep their sourcemap as is. generateBundle still collects the translation keys.
  • The registerChunk calls are now found with a regular expression instead of an exact string match. It tolerates a renamed i18n binding and rewritten string quotes, so duplicate calls are removed and the chunk name marker is replaced also when the bundler has reformatted the rendered chunk.
  • The vite-production test app now uses Hilla translations in two frontend modules (via a small stub of @vaadin/hilla-react-i18n) and builds with sourcemaps on, so the tests actually run through the rewriting path.

Test summary

# Status What the test verifies Why it matters
1 ✅ Every .map file reachable from the entry bundle has non-empty sources, non-empty mappings, and the contents of each source This is the bug: a rewritten chunk got a map with no sources, so the browser could not map the bundle back to the original files
2 ✅ Chunks the plugin does not rewrite still have a usable sourcemap The early return null must not drop maps the bundler produced
3 ✅ The chunk that contains translations calls registerChunk with its own served file name, and the marker is gone A wrong or unreplaced name means the runtime cannot load the translations of that chunk
4 ✅ That chunk has exactly one registerChunk call Duplicate calls from several modules must be removed; a missed match would also leave a stale marker behind
5 ❗ gap Matching when the bundler renames the i18n binding or rewrites the string quotes The regex exists for this case, but no test forces that output shape; whether it happens depends on the minifier settings of the test app

Tests added on this branch:

  • SourceMapsIT.bundleSourceMapsPointToOriginalSources — rows 1, 2
  • I18nChunkIT.chunkRegistersItselfUnderItsOwnName — rows 3, 4

Left untested on purpose: BundleAccess is a test helper that only downloads bundle files, so it is covered indirectly by both ITs.

The i18n Vite plugin rewrote every chunk in generateBundle and replaced
the chunk sourcemap with a MagicString map that has no sources, so all
.map files of an application built with build.sourcemap enabled came out
empty. The rewriting now happens in renderChunk, where the bundler
composes the returned map with the one it already has for the chunk, and
chunks the plugin does not touch keep their sourcemap as is.

The registerChunk calls are now matched with a pattern that tolerates
renamed bindings and rewritten string quotes, so the duplicate calls are
removed and every chunk name marker is replaced also when the bundler
has reformatted the rendered chunk.

Related to #16679
The production test application now uses Hilla translations in two
frontend modules, so that the i18n build plugin rewrites the chunk they
end up in. Without them the plugin left every chunk untouched and the
sourcemap test only covered the chunks the plugin does not rewrite.

The sourcemap test also follows the imports of the entry bundle
transitively and checks that the sources of a map are the original
files, with their contents, instead of only counting them.
@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

 1 474 files   1 558 suites   1h 37m 22s ⏱️
12 239 tests 12 171 ✅ 68 💤 0 ❌
12 557 runs  12 489 ✅ 68 💤 0 ❌

Results for commit 9523867.

♻️ This comment has been updated with latest results.

@sonarqubecloud

Copy link
Copy Markdown

@platosha
platosha added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit aaf20ce Sep 14, 2026
42 checks passed
@platosha
platosha deleted the fix/i18n-plugin-breaking-production-sourcemaps branch September 14, 2026 16:46
vaadin-bot added a commit that referenced this pull request Sep 14, 2026
…rites a chunk (#25691) (CP: 25.3) (#25707)

This PR cherry-picks changes from the original PR #25691 to branch 25.3.
---
#### Original PR description
> ## Summary
> 
> The Vaadin i18n build plugin replaced the sourcemap of every chunk
with an empty one, so all `.map` files of an app built with
`build.sourcemap` enabled were useless. The plugin now rewrites chunks
at a point where the bundler keeps the original sourcemap, and leaves
untouched chunks alone.
> 
> Fixes #16679
> 
> ## What changed
> 
> **Behavior change:** only apps that build with `build.sourcemap`
enabled are affected, and only for the better — their `.map` files now
contain the real sources instead of being empty. Chunk output itself is
unchanged, so no API or runtime behavior changes for anyone else.
> 
> - `rollup-plugin-vaadin-i18n.js` moves the chunk rewriting from
`generateBundle` to `renderChunk`. The bundler then composes the map
returned by the plugin with the map it already has for that chunk.
Chunks the plugin does not touch return `null` and keep their sourcemap
as is. `generateBundle` still collects the translation keys.
> - The `registerChunk` calls are now found with a regular expression
instead of an exact string match. It tolerates a renamed `i18n` binding
and rewritten string quotes, so duplicate calls are removed and the
chunk name marker is replaced also when the bundler has reformatted the
rendered chunk.
> - The `vite-production` test app now uses Hilla translations in two
frontend modules (via a small stub of `@vaadin/hilla-react-i18n`) and
builds with sourcemaps on, so the tests actually run through the
rewriting path.
> 
> ## Test summary
> 
> | # | Status | What the test verifies | Why it matters |
> |---|--------|------------------------|----------------|
> | 1 | ✅ | Every `.map` file reachable from the entry bundle has
non-empty `sources`, non-empty `mappings`, and the contents of each
source | This is the bug: a rewritten chunk got a map with no sources,
so the browser could not map the bundle back to the original files |
> | 2 | ✅ | Chunks the plugin does not rewrite still have a usable
sourcemap | The early `return null` must not drop maps the bundler
produced |
> | 3 | ✅ | The chunk that contains translations calls `registerChunk`
with its own served file name, and the marker is gone | A wrong or
unreplaced name means the runtime cannot load the translations of that
chunk |
> | 4 | ✅ | That chunk has exactly one `registerChunk` call | Duplicate
calls from several modules must be removed; a missed match would also
leave a stale marker behind |
> | 5 | ❗ **gap** | Matching when the bundler renames the `i18n` binding
or rewrites the string quotes | The regex exists for this case, but no
test forces that output shape; whether it happens depends on the
minifier settings of the test app |
> 
> Tests added on this branch:
> 
> - `SourceMapsIT.bundleSourceMapsPointToOriginalSources` — rows 1, 2
> - `I18nChunkIT.chunkRegistersItselfUnderItsOwnName` — rows 3, 4
> 
> Left untested on purpose: `BundleAccess` is a test helper that only
downloads bundle files, so it is covered indirectly by both ITs.

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
Co-authored-by: Artur Signell <artur@vaadin.com>
vaadin-bot added a commit that referenced this pull request Sep 15, 2026
…lugin (#25708) (CP: 25.3) (#25715)

This PR cherry-picks changes from the original PR #25708 to branch 25.3.
---
#### Original PR description
> ## Summary
> The Vite plugin that preserves the Vaadin usage statistics comment
returned the code of every frontend module without a sourcemap, so the
bundler dropped all of them from the emitted `.map` files. Dev bundles
built with `build.sourcemap` enabled got maps with no sources, and the
browser could not show the original frontend files. The plugin now
leaves untouched modules alone and returns a real sourcemap for the one
module it rewrites.
> 
> ## What changed
> **Behavior change:** for anyone building a dev bundle with
`build.sourcemap` enabled, the emitted `.map` files now contain the
original frontend sources instead of being empty. No public Java API
behaviour changes, and no application code needs updating.
> 
> - `vite.generated.ts`: the `vaadin:preserve-usage-stats` plugin
returns `null` for modules it does not change, instead of returning the
unchanged code with no map. Returning code without a map makes the
bundler drop that module from the sourcemap of its chunk, and this hook
sees every module in the bundle.
> - The one module it does change (the usage statistics comment rewrite,
`/**` → `/*!` so minifiers keep it) is now rewritten with
`magic-string`, so the plugin can return a proper sourcemap along with
the code.
> - Error cases (`vaadin-dev-mode:start` tag missing, rewritten comment
no longer matching the expected pattern) still log the same messages and
now leave the module untouched.
> - Test support: the dev bundle test app builds with `build.sourcemap`
enabled, gets a `vaadin-usage-statistics-stub.js` frontend module that
carries the comment in its plain form (the published
`@vaadin/vaadin-usage-statistics` already carries it rewritten), and the
sourcemap assertions shared by the dev and production bundle tests moved
into a new `SourceMapTestUtil` in `flow-test-util`.
> 
> Fixes #16679, follow-up to #25691.
> 
> ## API Changes
> 
> ### com.vaadin.flow.testutil.SourceMapTestUtil
> 
> ```java
> // Added
> public final class SourceMapTestUtil
> public static List<String> assertSourceMapUsable(String name, String
sourceMap) // asserts the map has sources, mappings and source contents;
returns the source file names
> ```
> 
> ## Test summary
> 
> | # | Status | What the test verifies | Why it matters |
> |---|--------|------------------------|----------------|
> | 1 | ✅ | Every `.js.map` of the dev bundle has sources, non-empty
mappings, and the content of each source | This is the bug: the maps
were emitted empty and the browser could not map the bundle back to the
sources |
> | 2 | ✅ | A dev bundle sourcemap refers to a normal frontend file
(`lit-view.ts`) the plugin does not touch | Covers the `return null`
path — modules the plugin skips must stay in the chunk map |
> | 3 | ✅ | A dev bundle sourcemap refers to the rewritten usage
statistics module itself | Covers the path where the plugin does change
code — its own map must chain onto the bundler's |
> | 4 | ✅ | In every chunk, the usage statistics dev mode comment
appears in the rewritten `/*!` form | The rewrite is the only thing the
plugin does; a broken rewrite would let a minifier strip the comment |
> | 5 | ✅ | Production bundle sourcemaps still point to the original
sources after the assertions moved to the shared helper | Guards against
the refactor weakening the existing production check |
> | 6 | ❗ **gap** | The error paths (missing `vaadin-dev-mode:start`
tag, rewritten comment failing the expected pattern) log and leave the
module unchanged | Only reachable with a malformed module; not exercised
by a test |
> 
> - `DevBundleSourceMapsIT.devBundleSourceMapsPointToOriginalSources` —
rows 1, 2, 3
> - `DevBundleSourceMapsIT.usageStatisticsCommentIsRewrittenInTheBundle`
— row 4
> - `SourceMapsIT.bundleSourceMapsPointToOriginalSources` (changed to
use the shared helper) — row 5
> 
> Deliberately untested: `SourceMapTestUtil` itself, which is exercised
through both ITs, and the plugin's console error branches (row 6), which
need a deliberately malformed usage statistics module.

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
javier-godoy pushed a commit to javier-godoy/flow that referenced this pull request Sep 29, 2026
…lugin (vaadin#25708)

## Summary
The Vite plugin that preserves the Vaadin usage statistics comment
returned the code of every frontend module without a sourcemap, so the
bundler dropped all of them from the emitted `.map` files. Dev bundles
built with `build.sourcemap` enabled got maps with no sources, and the
browser could not show the original frontend files. The plugin now
leaves untouched modules alone and returns a real sourcemap for the one
module it rewrites.

## What changed
**Behavior change:** for anyone building a dev bundle with
`build.sourcemap` enabled, the emitted `.map` files now contain the
original frontend sources instead of being empty. No public Java API
behaviour changes, and no application code needs updating.

- `vite.generated.ts`: the `vaadin:preserve-usage-stats` plugin returns
`null` for modules it does not change, instead of returning the
unchanged code with no map. Returning code without a map makes the
bundler drop that module from the sourcemap of its chunk, and this hook
sees every module in the bundle.
- The one module it does change (the usage statistics comment rewrite,
`/**` → `/*!` so minifiers keep it) is now rewritten with
`magic-string`, so the plugin can return a proper sourcemap along with
the code.
- Error cases (`vaadin-dev-mode:start` tag missing, rewritten comment no
longer matching the expected pattern) still log the same messages and
now leave the module untouched.
- Test support: the dev bundle test app builds with `build.sourcemap`
enabled, gets a `vaadin-usage-statistics-stub.js` frontend module that
carries the comment in its plain form (the published
`@vaadin/vaadin-usage-statistics` already carries it rewritten), and the
sourcemap assertions shared by the dev and production bundle tests moved
into a new `SourceMapTestUtil` in `flow-test-util`.

Fixes vaadin#16679, follow-up to vaadin#25691.

## API Changes

### com.vaadin.flow.testutil.SourceMapTestUtil

```java
// Added
public final class SourceMapTestUtil
public static List<String> assertSourceMapUsable(String name, String sourceMap) // asserts the map has sources, mappings and source contents; returns the source file names
```

## Test summary

| # | Status | What the test verifies | Why it matters |
|---|--------|------------------------|----------------|
| 1 | ✅ | Every `.js.map` of the dev bundle has sources, non-empty
mappings, and the content of each source | This is the bug: the maps
were emitted empty and the browser could not map the bundle back to the
sources |
| 2 | ✅ | A dev bundle sourcemap refers to a normal frontend file
(`lit-view.ts`) the plugin does not touch | Covers the `return null`
path — modules the plugin skips must stay in the chunk map |
| 3 | ✅ | A dev bundle sourcemap refers to the rewritten usage
statistics module itself | Covers the path where the plugin does change
code — its own map must chain onto the bundler's |
| 4 | ✅ | In every chunk, the usage statistics dev mode comment appears
in the rewritten `/*!` form | The rewrite is the only thing the plugin
does; a broken rewrite would let a minifier strip the comment |
| 5 | ✅ | Production bundle sourcemaps still point to the original
sources after the assertions moved to the shared helper | Guards against
the refactor weakening the existing production check |
| 6 | ❗ **gap** | The error paths (missing `vaadin-dev-mode:start` tag,
rewritten comment failing the expected pattern) log and leave the module
unchanged | Only reachable with a malformed module; not exercised by a
test |

- `DevBundleSourceMapsIT.devBundleSourceMapsPointToOriginalSources` —
rows 1, 2, 3
- `DevBundleSourceMapsIT.usageStatisticsCommentIsRewrittenInTheBundle` —
row 4
- `SourceMapsIT.bundleSourceMapsPointToOriginalSources` (changed to use
the shared helper) — row 5

Deliberately untested: `SourceMapTestUtil` itself, which is exercised
through both ITs, and the plugin's console error branches (row 6), which
need a deliberately malformed usage statistics module.

---------

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
@vaadin-bot

Copy link
Copy Markdown
Collaborator

This ticket/PR has been released with Vaadin 25.4.0-alpha1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sourcemaps are not generated with vaadin 24

3 participants