]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hydration): escape css var name to avoid mismatch (#11739)
authoredison <daiwei521@126.com>
Tue, 3 Sep 2024 00:25:00 +0000 (08:25 +0800)
committerGitHub <noreply@github.com>
Tue, 3 Sep 2024 00:25:00 +0000 (08:25 +0800)
close #11735

packages/compiler-sfc/src/script/utils.ts
packages/compiler-sfc/src/style/cssVars.ts
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts
packages/shared/src/escapeHtml.ts

index f9b4ad68625cc5772f040961470a59b30d920454..39f163c6b77d9a8b5a071be5ba3e5a79e7bb33de 100644 (file)
@@ -121,15 +121,3 @@ export const propNameEscapeSymbolsRE: RegExp =
 export function getEscapedPropName(key: string): string {
   return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
 }
-
-export const cssVarNameEscapeSymbolsRE: RegExp =
-  /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
-
-export function getEscapedCssVarName(
-  key: string,
-  doubleEscape: boolean,
-): string {
-  return key.replace(cssVarNameEscapeSymbolsRE, s =>
-    doubleEscape ? `\\\\${s}` : `\\${s}`,
-  )
-}
index 47bb7c083bb2579fa29a4c090d279fee8daf55c2..0397c7d790a4bb40098693ce6061d0e70de8f8a0 100644 (file)
@@ -8,9 +8,9 @@ import {
   processExpression,
 } from '@vue/compiler-dom'
 import type { SFCDescriptor } from '../parse'
-import { getEscapedCssVarName } from '../script/utils'
 import type { PluginCreator } from 'postcss'
 import hash from 'hash-sum'
+import { getEscapedCssVarName } from '@vue/shared'
 
 export const CSS_VARS_HELPER = `useCssVars`
 
index a2ea72638f9aa6bc8dfa4afe9d73b82a45e2fcf6..bf07c9773b33414011e3d0147b01f6414364d668 100644 (file)
@@ -2021,6 +2021,26 @@ describe('SSR hydration', () => {
       app.mount(container)
       expect(`Hydration style mismatch`).not.toHaveBeenWarned()
     })
+
+    test('escape css var name', () => {
+      const container = document.createElement('div')
+      container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
+      const app = createSSRApp({
+        setup() {
+          useCssVars(() => ({
+            'foo.bar': 'red',
+          }))
+          return () => h(Child)
+        },
+      })
+      const Child = {
+        setup() {
+          return () => h('div', { style: 'padding: 4px' })
+        },
+      }
+      app.mount(container)
+      expect(`Hydration style mismatch`).not.toHaveBeenWarned()
+    })
   })
 
   describe('data-allow-mismatch', () => {
index 15a460f99b4b92021031d2bde22cde2e12766095..b941635b2e9a4c44533fcf3ec09947bd2277e9f9 100644 (file)
@@ -18,6 +18,7 @@ import {
   PatchFlags,
   ShapeFlags,
   def,
+  getEscapedCssVarName,
   includeBooleanAttr,
   isBooleanAttr,
   isKnownHtmlAttr,
@@ -915,7 +916,10 @@ function resolveCssVars(
   ) {
     const cssVars = instance.getCssVars()
     for (const key in cssVars) {
-      expectedMap.set(`--${key}`, String(cssVars[key]))
+      expectedMap.set(
+        `--${getEscapedCssVarName(key, false)}`,
+        String(cssVars[key]),
+      )
     }
   }
   if (vnode === root && instance.parent) {
index 22ce632eb11acb166789f533455f44fe8f105254..94d07fd4120dd2434abe93b8afa5e1bf4676a4d0 100644 (file)
@@ -50,3 +50,15 @@ const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g
 export function escapeHtmlComment(src: string): string {
   return src.replace(commentStripRE, '')
 }
+
+export const cssVarNameEscapeSymbolsRE: RegExp =
+  /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
+
+export function getEscapedCssVarName(
+  key: string,
+  doubleEscape: boolean,
+): string {
+  return key.replace(cssVarNameEscapeSymbolsRE, s =>
+    doubleEscape ? `\\\\${s}` : `\\${s}`,
+  )
+}