Skip to content

fix(client): skip rewriting unchanged adopted styles - #23697

Merged
sapphi-red merged 2 commits into
vitejs:mainfrom
xyrolle:fix/client-skip-unchanged-style
Oct 8, 2026
Merged

sapphi-red merged 2 commits into
vitejs:mainfrom
xyrolle:fix/client-skip-unchanged-style

Conversation

@xyrolle

@xyrolle xyrolle commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

fixes #23696

When the dev client reuses a server-rendered <style data-vite-dev-id>, updateStyle writes the module's CSS into it even if it's the same text. In Chrome 154 that replaces the stylesheet and re-creates its @font-face rules, the fonts get revalidated, and a font-display: optional font that's still loading at render time falls back for good.

This only writes when the text is different. HMR updates that change the CSS work as before. One thing that changes: if something edited the stylesheet through the CSSOM, an update with the same text no longer resets it.

For the test I added a css-style page to the hmr playground, next to css-link. A small plugin renders the <style data-vite-dev-id> the way an SSR framework would, and a script counts mutations of that style. The test checks that loading the module leaves the style alone (one style, zero mutations) and that editing the file still updates it. Without the fix it gets [1, 1]. Because it counts mutations, it also catches the rewrite in Playwright's Chromium, where the font symptom itself doesn't show.

I used an AI assistant while investigating and drafting this. I reviewed the change myself: I read updateStyle and ran the new test without the fix (fails with [1, 1]) and with it (passes).

`updateStyle` reuses a server-rendered `<style data-vite-dev-id>` and then
sets its `textContent` to the module's CSS, even when the text is already
the same. Chrome 154 replaces the stylesheet on that write and re-creates
the font faces it declares. Those fonts are then revalidated, since the dev
server serves them with `Cache-Control: no-cache`, and a
`font-display: optional` font that is still loading when the page renders
stays on its fallback.

Only write the CSS when it differs.
@github-actions github-actions Bot added the bot: maybe Maybe a bot, LLM, or agent label Oct 8, 2026
@sapphi-red sapphi-red added feat: css p2-edge-case Bug, but has workaround or limited in scope (priority) labels Oct 8, 2026
@sapphi-red

Copy link
Copy Markdown
Member

It seems this is related to https://chromium-review.googlesource.com/c/chromium/src/+/8252693. But regardless of that, I think it's good to have this change.

@sapphi-red
sapphi-red merged commit fa71469 into vitejs:main Oct 8, 2026
18 checks passed
renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Oct 9, 2026
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| npm        | vite    | 8.3.0 | 8.3.4 |


## [v8.3.4](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-834-2026-10-08-small)

##### Features

- **bundled-dev:** support `import.meta.hot.acceptExports` ([#23463](vitejs/vite#23463)) ([bc0f21c](vitejs/vite@bc0f21c))

##### Bug Fixes

- avoid mutating hmr defaults (fix [#23575](vitejs/vite#23575)) ([#23576](vitejs/vite#23576)) ([574d5e3](vitejs/vite@574d5e3))
- **build:** correct preloads when chunkImportMap is enabled with non-root base ([#23698](vitejs/vite#23698)) ([2a8c0f0](vitejs/vite@2a8c0f0))
- **build:** wait for in-flight stylesheets in preload helper (fix [#23652](vitejs/vite#23652)) ([#23662](vitejs/vite#23662)) ([036b745](vitejs/vite@036b745))
- **bundled-dev:** serve `/@vite/client` and stub `@vite/env` ([#23664](vitejs/vite#23664)) ([5afc5a8](vitejs/vite@5afc5a8))
- **client:** skip rewriting unchanged adopted styles ([#23697](vitejs/vite#23697)) ([fa71469](vitejs/vite@fa71469))
- **css:** resolve imported preprocessors from their file path with lightningcss ([#23688](vitejs/vite#23688)) ([cf52011](vitejs/vite@cf52011))
- **deps:** update all non-major dependencies ([#23648](vitejs/vite#23648)) ([8a4c19c](vitejs/vite@8a4c19c))
- **deps:** update rolldown-related dependencies ([#23649](vitejs/vite#23649)) ([794516d](vitejs/vite@794516d))
- **dev:** a restart requested during a restart used to be dropped (fix [#23392](vitejs/vite#23392)) ([#23484](vitejs/vite#23484)) ([70d56ea](vitejs/vite@70d56ea))
- don't treat modules ending with js-like query as js ([#23687](vitejs/vite#23687)) ([fcb6f12](vitejs/vite@fcb6f12))
- **html:** avoid adding filesystem root to watcher ([#23689](vitejs/vite#23689)) ([3a7f2ae](vitejs/vite@3a7f2ae))
- **html:** skip caching CSS of chunks in a cycle (fix [#23628](vitejs/vite#23628)) ([#23636](vitejs/vite#23636)) ([15d82f7](vitejs/vite@15d82f7))
- **optimizer:** scan deep imports with custom extensions ([#23678](vitejs/vite#23678)) ([8579189](vitejs/vite@8579189))
- remove input option unescaping for now ([#23694](vitejs/vite#23694)) ([facc2fb](vitejs/vite@facc2fb))
- **server:** match static aliases on path boundaries ([#23643](vitejs/vite#23643)) ([b28db28](vitejs/vite@b28db28))
- **watcher:** normalize file before root check in `ensureWatchedFile` ([#23673](vitejs/vite#23673)) ([12bb28c](vitejs/vite@12bb28c))

##### Performance Improvements

- **module-runner:** skip cloning call sites that have no source map ([#23646](vitejs/vite#23646)) ([3d67486](vitejs/vite@3d67486))

##### Code Refactoring

- **bundled-dev:** import CSS HMR helpers from `/@vite/client` ([#23674](vitejs/vite#23674)) ([182f5b2](vitejs/vite@182f5b2))


## [v8.3.3](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-833-2026-10-06-small)

##### Bug Fixes

- **deps:** update launch-editor to v2.14.2 ([#23654](vitejs/vite#23654)) ([22fd1d5](vitejs/vite@22fd1d5))
- **html:** filename passed to transformIndexHtml should not include queries ([#23653](vitejs/vite#23653)) ([7dafd8e](vitejs/vite@7dafd8e))
- **server:** check `fs.serve` for `?vite-wasm-instance` ([#23655](vitejs/vite#23655)) ([ba8b7ab](vitejs/vite@ba8b7ab))
- **server:** store ids to `safeModulePaths` rather than URLs ([#23656](vitejs/vite#23656)) ([c3e06f9](vitejs/vite@c3e06f9))


## [v8.3.2](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-832-2026-10-01-small)

##### Bug Fixes

- **build:** preload CSS correctly when `renderBuiltUrl` returns URLs with queries ([#23611](vitejs/vite#23611)) ([64e0a21](vitejs/vite@64e0a21))
- **bundled-dev:** serve lazy chunk sourcemaps ([#23026](vitejs/vite#23026)) ([eb7aa9a](vitejs/vite@eb7aa9a))
- **bundled-dev:** serve the rolldown runtime from the installed rolldown ([#23568](vitejs/vite#23568)) ([bc598a6](vitejs/vite@bc598a6))
- **deps:** update all non-major dependencies ([#23601](vitejs/vite#23601)) ([9944fa6](vitejs/vite@9944fa6))
- **deps:** update rolldown-related dependencies ([#23602](vitejs/vite#23602)) ([88c1741](vitejs/vite@88c1741))
- **html:** resolve percent-encoded srcset urls ([#23609](vitejs/vite#23609)) ([53f1ce7](vitejs/vite@53f1ce7))
- limit size of object and array printing via `forwardConsole` ([#23565](vitejs/vite#23565)) ([e64a587](vitejs/vite@e64a587))
- merge `build.rolldownOptions.output.minify` correctly ([#23536](vitejs/vite#23536)) ([bba3bb8](vitejs/vite@bba3bb8))
- **optimize-deps:** avoid "unsupported" warnings for browser:false mappings ([#23590](vitejs/vite#23590)) ([5e4b9ca](vitejs/vite@5e4b9ca))
- **optimizer:** preserve excluded optional peer require fallbacks ([#23600](vitejs/vite#23600)) ([a2bd6fa](vitejs/vite@a2bd6fa))
- pass queries to `renderBuiltUrl` ([#23586](vitejs/vite#23586)) ([744269e](vitejs/vite@744269e))
- **server:** handle file watcher errors without crashing ([#23503](vitejs/vite#23503)) ([6894f5c](vitejs/vite@6894f5c))
- **server:** release previous environments after initialization ([#23499](vitejs/vite#23499)) ([5a3a010](vitejs/vite@5a3a010))
- **ssr:** encode whitespace in module runner sourceURL ([#23513](vitejs/vite#23513)) ([bbc8812](vitejs/vite@bbc8812))
- **worker:** align worker urls in client and server when using terser ([#23614](vitejs/vite#23614)) ([24bd331](vitejs/vite@24bd331))

##### Performance Improvements

- avoid encoding intermediate source maps ([#23461](vitejs/vite#23461)) ([89574f6](vitejs/vite@89574f6))
- **build:** avoid quadratic link scan in the preload helper ([#23510](vitejs/vite#23510)) ([cf5c028](vitejs/vite@cf5c028))
- only register time middleware when debug logging is enabled ([#23621](vitejs/vite#23621)) ([94d0080](vitejs/vite@94d0080))

##### Documentation

- fix dead og-image PNG links in vite6/vite7 changelog entries ([#23594](vitejs/vite#23594)) ([1929b4c](vitejs/vite@1929b4c))

##### Miscellaneous Chores

- **deps:** update vitest monorepo to v5 ([#23604](vitejs/vite#23604)) ([24339f4](vitejs/vite@24339f4))

##### Code Refactoring

- use `tinyexec` instead of `cross-spawn` ([#23583](vitejs/vite#23583)) ([db915e5](vitejs/vite@db915e5))

##### Tests

- **bundled-dev:** accept a rolldown dev runtime with no helper imports ([#23606](vitejs/vite#23606)) ([634745d](vitejs/vite@634745d))


## [v8.3.1](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-831-2026-09-24-small)

##### Bug Fixes

- **deps:** update all non-major dependencies ([#23482](vitejs/vite#23482)) ([3c752c8](vitejs/vite@3c752c8))
- **deps:** update all non-major dependencies ([#23537](vitejs/vite#23537)) ([e8990c4](vitejs/vite@e8990c4))
- **deps:** update rolldown-related dependencies ([#23483](vitejs/vite#23483)) ([9aecbbf](vitejs/vite@9aecbbf))
- handle `server.ws: false` in mergeConfig ([#23511](vitejs/vite#23511)) ([f68c0d5](vitejs/vite@f68c0d5))
- merge `build.rolldownOptions.output.comments` correctly ([#23514](vitejs/vite#23514)) ([4aba8d8](vitejs/vite@4aba8d8))
- **optimizer:** don't skip imports whose binding starts with type ([#23540](vitejs/vite#23540)) ([39330f4](vitejs/vite@39330f4))
- **optimizer:** resolve pending discovered dep processing on close before init ([#23567](vitejs/vite#23567)) ([5f89433](vitejs/vite@5f89433))
- **server:** avoid reinitializing watcher when adding file after server close ([#23572](vitejs/vite#23572)) ([6f831f9](vitejs/vite@6f831f9))
- **sourcemap:** skip URL source roots when injecting sources content ([#23519](vitejs/vite#23519)) ([04fc30a](vitejs/vite@04fc30a))

##### Miscellaneous Chores

- merge prereleases in changelog ([#23466](vitejs/vite#23466)) ([99bd9d1](vitejs/vite@99bd9d1))
- **optimizer:** add debug log when waiting for dep before init ([#23566](vitejs/vite#23566)) ([63567c7](vitejs/vite@63567c7))
- update `optimizeDeps.include` comment ([#23489](vitejs/vite#23489)) ([6a84c72](vitejs/vite@6a84c72))

##### Code Refactoring

- assets regexp use non-capture ([#23491](vitejs/vite#23491)) ([f4b4431](vitejs/vite@f4b4431))
- remove duplicate configurations ([#23532](vitejs/vite#23532)) ([9abd99b](vitejs/vite@9abd99b))
- replace `find` with `some` ([#23554](vitejs/vite#23554)) ([af7cdf6](vitejs/vite@af7cdf6))
renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Oct 10, 2026
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| npm        | vite    | 8.3.0 | 8.3.4 |


## [v8.3.4](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-834-2026-10-08-small)

##### Features

- **bundled-dev:** support `import.meta.hot.acceptExports` ([#23463](vitejs/vite#23463)) ([bc0f21c](vitejs/vite@bc0f21c))

##### Bug Fixes

- avoid mutating hmr defaults (fix [#23575](vitejs/vite#23575)) ([#23576](vitejs/vite#23576)) ([574d5e3](vitejs/vite@574d5e3))
- **build:** correct preloads when chunkImportMap is enabled with non-root base ([#23698](vitejs/vite#23698)) ([2a8c0f0](vitejs/vite@2a8c0f0))
- **build:** wait for in-flight stylesheets in preload helper (fix [#23652](vitejs/vite#23652)) ([#23662](vitejs/vite#23662)) ([036b745](vitejs/vite@036b745))
- **bundled-dev:** serve `/@vite/client` and stub `@vite/env` ([#23664](vitejs/vite#23664)) ([5afc5a8](vitejs/vite@5afc5a8))
- **client:** skip rewriting unchanged adopted styles ([#23697](vitejs/vite#23697)) ([fa71469](vitejs/vite@fa71469))
- **css:** resolve imported preprocessors from their file path with lightningcss ([#23688](vitejs/vite#23688)) ([cf52011](vitejs/vite@cf52011))
- **deps:** update all non-major dependencies ([#23648](vitejs/vite#23648)) ([8a4c19c](vitejs/vite@8a4c19c))
- **deps:** update rolldown-related dependencies ([#23649](vitejs/vite#23649)) ([794516d](vitejs/vite@794516d))
- **dev:** a restart requested during a restart used to be dropped (fix [#23392](vitejs/vite#23392)) ([#23484](vitejs/vite#23484)) ([70d56ea](vitejs/vite@70d56ea))
- don't treat modules ending with js-like query as js ([#23687](vitejs/vite#23687)) ([fcb6f12](vitejs/vite@fcb6f12))
- **html:** avoid adding filesystem root to watcher ([#23689](vitejs/vite#23689)) ([3a7f2ae](vitejs/vite@3a7f2ae))
- **html:** skip caching CSS of chunks in a cycle (fix [#23628](vitejs/vite#23628)) ([#23636](vitejs/vite#23636)) ([15d82f7](vitejs/vite@15d82f7))
- **optimizer:** scan deep imports with custom extensions ([#23678](vitejs/vite#23678)) ([8579189](vitejs/vite@8579189))
- remove input option unescaping for now ([#23694](vitejs/vite#23694)) ([facc2fb](vitejs/vite@facc2fb))
- **server:** match static aliases on path boundaries ([#23643](vitejs/vite#23643)) ([b28db28](vitejs/vite@b28db28))
- **watcher:** normalize file before root check in `ensureWatchedFile` ([#23673](vitejs/vite#23673)) ([12bb28c](vitejs/vite@12bb28c))

##### Performance Improvements

- **module-runner:** skip cloning call sites that have no source map ([#23646](vitejs/vite#23646)) ([3d67486](vitejs/vite@3d67486))

##### Code Refactoring

- **bundled-dev:** import CSS HMR helpers from `/@vite/client` ([#23674](vitejs/vite#23674)) ([182f5b2](vitejs/vite@182f5b2))


## [v8.3.3](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-833-2026-10-06-small)

##### Bug Fixes

- **deps:** update launch-editor to v2.14.2 ([#23654](vitejs/vite#23654)) ([22fd1d5](vitejs/vite@22fd1d5))
- **html:** filename passed to transformIndexHtml should not include queries ([#23653](vitejs/vite#23653)) ([7dafd8e](vitejs/vite@7dafd8e))
- **server:** check `fs.serve` for `?vite-wasm-instance` ([#23655](vitejs/vite#23655)) ([ba8b7ab](vitejs/vite@ba8b7ab))
- **server:** store ids to `safeModulePaths` rather than URLs ([#23656](vitejs/vite#23656)) ([c3e06f9](vitejs/vite@c3e06f9))


## [v8.3.2](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-832-2026-10-01-small)

##### Bug Fixes

- **build:** preload CSS correctly when `renderBuiltUrl` returns URLs with queries ([#23611](vitejs/vite#23611)) ([64e0a21](vitejs/vite@64e0a21))
- **bundled-dev:** serve lazy chunk sourcemaps ([#23026](vitejs/vite#23026)) ([eb7aa9a](vitejs/vite@eb7aa9a))
- **bundled-dev:** serve the rolldown runtime from the installed rolldown ([#23568](vitejs/vite#23568)) ([bc598a6](vitejs/vite@bc598a6))
- **deps:** update all non-major dependencies ([#23601](vitejs/vite#23601)) ([9944fa6](vitejs/vite@9944fa6))
- **deps:** update rolldown-related dependencies ([#23602](vitejs/vite#23602)) ([88c1741](vitejs/vite@88c1741))
- **html:** resolve percent-encoded srcset urls ([#23609](vitejs/vite#23609)) ([53f1ce7](vitejs/vite@53f1ce7))
- limit size of object and array printing via `forwardConsole` ([#23565](vitejs/vite#23565)) ([e64a587](vitejs/vite@e64a587))
- merge `build.rolldownOptions.output.minify` correctly ([#23536](vitejs/vite#23536)) ([bba3bb8](vitejs/vite@bba3bb8))
- **optimize-deps:** avoid "unsupported" warnings for browser:false mappings ([#23590](vitejs/vite#23590)) ([5e4b9ca](vitejs/vite@5e4b9ca))
- **optimizer:** preserve excluded optional peer require fallbacks ([#23600](vitejs/vite#23600)) ([a2bd6fa](vitejs/vite@a2bd6fa))
- pass queries to `renderBuiltUrl` ([#23586](vitejs/vite#23586)) ([744269e](vitejs/vite@744269e))
- **server:** handle file watcher errors without crashing ([#23503](vitejs/vite#23503)) ([6894f5c](vitejs/vite@6894f5c))
- **server:** release previous environments after initialization ([#23499](vitejs/vite#23499)) ([5a3a010](vitejs/vite@5a3a010))
- **ssr:** encode whitespace in module runner sourceURL ([#23513](vitejs/vite#23513)) ([bbc8812](vitejs/vite@bbc8812))
- **worker:** align worker urls in client and server when using terser ([#23614](vitejs/vite#23614)) ([24bd331](vitejs/vite@24bd331))

##### Performance Improvements

- avoid encoding intermediate source maps ([#23461](vitejs/vite#23461)) ([89574f6](vitejs/vite@89574f6))
- **build:** avoid quadratic link scan in the preload helper ([#23510](vitejs/vite#23510)) ([cf5c028](vitejs/vite@cf5c028))
- only register time middleware when debug logging is enabled ([#23621](vitejs/vite#23621)) ([94d0080](vitejs/vite@94d0080))

##### Documentation

- fix dead og-image PNG links in vite6/vite7 changelog entries ([#23594](vitejs/vite#23594)) ([1929b4c](vitejs/vite@1929b4c))

##### Miscellaneous Chores

- **deps:** update vitest monorepo to v5 ([#23604](vitejs/vite#23604)) ([24339f4](vitejs/vite@24339f4))

##### Code Refactoring

- use `tinyexec` instead of `cross-spawn` ([#23583](vitejs/vite#23583)) ([db915e5](vitejs/vite@db915e5))

##### Tests

- **bundled-dev:** accept a rolldown dev runtime with no helper imports ([#23606](vitejs/vite#23606)) ([634745d](vitejs/vite@634745d))


## [v8.3.1](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-831-2026-09-24-small)

##### Bug Fixes

- **deps:** update all non-major dependencies ([#23482](vitejs/vite#23482)) ([3c752c8](vitejs/vite@3c752c8))
- **deps:** update all non-major dependencies ([#23537](vitejs/vite#23537)) ([e8990c4](vitejs/vite@e8990c4))
- **deps:** update rolldown-related dependencies ([#23483](vitejs/vite#23483)) ([9aecbbf](vitejs/vite@9aecbbf))
- handle `server.ws: false` in mergeConfig ([#23511](vitejs/vite#23511)) ([f68c0d5](vitejs/vite@f68c0d5))
- merge `build.rolldownOptions.output.comments` correctly ([#23514](vitejs/vite#23514)) ([4aba8d8](vitejs/vite@4aba8d8))
- **optimizer:** don't skip imports whose binding starts with type ([#23540](vitejs/vite#23540)) ([39330f4](vitejs/vite@39330f4))
- **optimizer:** resolve pending discovered dep processing on close before init ([#23567](vitejs/vite#23567)) ([5f89433](vitejs/vite@5f89433))
- **server:** avoid reinitializing watcher when adding file after server close ([#23572](vitejs/vite#23572)) ([6f831f9](vitejs/vite@6f831f9))
- **sourcemap:** skip URL source roots when injecting sources content ([#23519](vitejs/vite#23519)) ([04fc30a](vitejs/vite@04fc30a))

##### Miscellaneous Chores

- merge prereleases in changelog ([#23466](vitejs/vite#23466)) ([99bd9d1](vitejs/vite@99bd9d1))
- **optimizer:** add debug log when waiting for dep before init ([#23566](vitejs/vite#23566)) ([63567c7](vitejs/vite@63567c7))
- update `optimizeDeps.include` comment ([#23489](vitejs/vite#23489)) ([6a84c72](vitejs/vite@6a84c72))

##### Code Refactoring

- assets regexp use non-capture ([#23491](vitejs/vite#23491)) ([f4b4431](vitejs/vite@f4b4431))
- remove duplicate configurations ([#23532](vitejs/vite#23532)) ([9abd99b](vitejs/vite@9abd99b))
- replace `find` with `some` ([#23554](vitejs/vite#23554)) ([af7cdf6](vitejs/vite@af7cdf6))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot: maybe Maybe a bot, LLM, or agent feat: css p2-edge-case Bug, but has workaround or limited in scope (priority)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dev client rewrites a server-rendered <style data-vite-dev-id> with identical CSS, and Chrome 154 then drops font-display: optional fonts

2 participants