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

close #11735
This commit is contained in:
edison 2024-09-03 08:25:00 +08:00 committed by GitHub
parent cb843e0be3
commit ca12e776bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 38 additions and 14 deletions

View File

@ -121,15 +121,3 @@ export const propNameEscapeSymbolsRE: RegExp =
export function getEscapedPropName(key: string): string { export function getEscapedPropName(key: string): string {
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key 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}`,
)
}

View File

@ -8,9 +8,9 @@ import {
processExpression, processExpression,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import type { SFCDescriptor } from '../parse' import type { SFCDescriptor } from '../parse'
import { getEscapedCssVarName } from '../script/utils'
import type { PluginCreator } from 'postcss' import type { PluginCreator } from 'postcss'
import hash from 'hash-sum' import hash from 'hash-sum'
import { getEscapedCssVarName } from '@vue/shared'
export const CSS_VARS_HELPER = `useCssVars` export const CSS_VARS_HELPER = `useCssVars`

View File

@ -2021,6 +2021,26 @@ describe('SSR hydration', () => {
app.mount(container) app.mount(container)
expect(`Hydration style mismatch`).not.toHaveBeenWarned() 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', () => { describe('data-allow-mismatch', () => {

View File

@ -18,6 +18,7 @@ import {
PatchFlags, PatchFlags,
ShapeFlags, ShapeFlags,
def, def,
getEscapedCssVarName,
includeBooleanAttr, includeBooleanAttr,
isBooleanAttr, isBooleanAttr,
isKnownHtmlAttr, isKnownHtmlAttr,
@ -915,7 +916,10 @@ function resolveCssVars(
) { ) {
const cssVars = instance.getCssVars() const cssVars = instance.getCssVars()
for (const key in cssVars) { for (const key in cssVars) {
expectedMap.set(`--${key}`, String(cssVars[key])) expectedMap.set(
`--${getEscapedCssVarName(key, false)}`,
String(cssVars[key]),
)
} }
} }
if (vnode === root && instance.parent) { if (vnode === root && instance.parent) {

View File

@ -50,3 +50,15 @@ const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g
export function escapeHtmlComment(src: string): string { export function escapeHtmlComment(src: string): string {
return src.replace(commentStripRE, '') 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}`,
)
}