Skip to content

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

Merged
vaadin-bot merged 1 commit into
25.3from
cherry-pick-25691-to-25.3-1789404450852
Sep 14, 2026
Merged

vaadin-bot merged 1 commit into
25.3from
cherry-pick-25691-to-25.3-1789404450852

Conversation

@vaadin-bot

Copy link
Copy Markdown
Collaborator

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.

…rites a chunk (#25691)

## 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>
@github-actions

Copy link
Copy Markdown
Contributor

The chunk rewrite moves from generateBundle to renderChunk, so the bundler composes the sourcemap instead of the plugin overwriting it

flowchart LR
    subgraph Before
        direction TB
        B1["bundler renders chunk (has original map)"] -->|passes chunk| B2["generateBundle() rewrites chunk, sets chunk.map"]
        B2 -->|overwrites with source-less generateMap| B3[".map has no sources (empty)"]
    end
    subgraph After
        direction TB
        A1["renderChunk() rewrites chunk, returns map or null (new)"]:::changed -->|returns code+map| A2["bundler composes returned map with original"]:::changed
        A2 -->|composed map| A3[".map has real sources"]
        A1 -.->|keys only, no map write| A4["generateBundle() collects translation keys"]
    end
    Before ~~~ After
    classDef changed stroke:#c9a227,stroke-width:3px
Loading

Before, rollup-plugin-vaadin-i18n.js rewrote each chunk in generateBundle and assigned chunk.map = magicString.generateMap(), overwriting the bundler's sourcemap with one that has no sources, so every .map file was empty. After, the rewrite runs in renderChunk, which returns {code, map} (or null for untouched chunks); the bundler then composes that map with the chunk's original, keeping real sources, while generateBundle only collects keys. Mechanism per the pull request description and the diff.

Diagram Bot draws the mechanism this pull request touches; it does not review the change. Verify it against the diff.

Generated by Diagram Bot for issue #25707 · ◷

@vaadin-bot

Copy link
Copy Markdown
Collaborator Author

This PR is eligible for auto-merging policy, so it has been approved automatically. If there are pending conditions, auto merge (with 'squash' method) has been enabled for this PR [Message is sent from bot]

@vaadin-bot
vaadin-bot enabled auto-merge (squash) September 14, 2026 17:01
@github-actions

Copy link
Copy Markdown
Contributor

Test Results

 1 443 files  +2   1 527 suites  +2   1h 37m 53s ⏱️ + 1m 3s
12 078 tests +2  12 010 ✅ +2  68 💤 ±0  0 ❌ ±0 
12 396 runs  +2  12 328 ✅ +2  68 💤 ±0  0 ❌ ±0 

Results for commit 45018c2. ± Comparison against base commit 5112714.

@vaadin-bot
vaadin-bot merged commit 8314f78 into 25.3 Sep 14, 2026
40 checks passed
@vaadin-bot
vaadin-bot deleted the cherry-pick-25691-to-25.3-1789404450852 branch September 14, 2026 17:04
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants