Jelajahi Sumber

fix(hydration): escape css var name to avoid mismatch (#11739)

close #11735
edison 1 tahun lalu
induk
melakukan
ca12e776bc

+ 0 - 12
packages/compiler-sfc/src/script/utils.ts

@@ -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}`,
-  )
-}

+ 1 - 1
packages/compiler-sfc/src/style/cssVars.ts

@@ -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`
 

+ 20 - 0
packages/runtime-core/__tests__/hydration.spec.ts

@@ -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', () => {

+ 5 - 1
packages/runtime-core/src/hydration.ts

@@ -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) {

+ 12 - 0
packages/shared/src/escapeHtml.ts

@@ -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}`,
+  )
+}