Skip to content

Commit 9947bb2

Browse files
committed
fix(css-vars): reset css vars for nullish bindings
1 parent 06310e8 commit 9947bb2

File tree

8 files changed

+62
-19
lines changed

8 files changed

+62
-19
lines changed

packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -884,9 +884,9 @@ export default {
884884
885885
return (_ctx, _push, _parent, _attrs) => {
886886
const _cssVars = { style: {
887-
"--xxxxxxxx-count": (count.value),
888-
"--xxxxxxxx-style\\\\.color": (style.color),
889-
"--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
887+
":--xxxxxxxx-count": (count.value),
888+
":--xxxxxxxx-style\\\\.color": (style.color),
889+
":--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
890890
}}
891891
_push(\`<!--[--><div\${
892892
_ssrRenderAttrs(_cssVars)

packages/compiler-sfc/__tests__/compileScript.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,10 +646,10 @@ describe('SFC compile <script setup>', () => {
646646
expect(content).toMatch(`return (_ctx, _push`)
647647
expect(content).toMatch(`ssrInterpolate`)
648648
expect(content).not.toMatch(`useCssVars`)
649-
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
650-
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
649+
expect(content).toMatch(`":--${mockId}-count": (count.value)`)
650+
expect(content).toMatch(`":--${mockId}-style\\\\.color": (style.color)`)
651651
expect(content).toMatch(
652-
`"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
652+
`":--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
653653
)
654654
assertCode(content)
655655
})

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ export function genCssVarsFromList(
2323
return `{\n ${vars
2424
.map(
2525
key =>
26-
`"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
26+
// The `:` prefix here is used in `ssrRenderStyle` to distinguish whether
27+
// a custom property comes from `ssrCssVars`. If it does, we need to reset
28+
// its value to `initial` on the component instance to avoid unintentionally
29+
// inheriting the same property value from a different instance of the same
30+
// component in the outer scope.
31+
`"${isSSR ? `:--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
2732
)
2833
.join(',\n ')}\n}`
2934
}

packages/runtime-core/src/component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -582,13 +582,13 @@ export interface ComponentInternalInstance {
582582
* For updating css vars on contained teleports
583583
* @internal
584584
*/
585-
ut?: (vars?: Record<string, string>) => void
585+
ut?: (vars?: Record<string, unknown>) => void
586586

587587
/**
588588
* dev only. For style v-bind hydration mismatch checks
589589
* @internal
590590
*/
591-
getCssVars?: () => Record<string, string>
591+
getCssVars?: () => Record<string, unknown>
592592

593593
/**
594594
* v2 compat only, for caching mutated $options

packages/runtime-core/src/hydration.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -938,10 +938,8 @@ function resolveCssVars(
938938
) {
939939
const cssVars = instance.getCssVars()
940940
for (const key in cssVars) {
941-
expectedMap.set(
942-
`--${getEscapedCssVarName(key, false)}`,
943-
String(cssVars[key]),
944-
)
941+
const value = String(cssVars[key] ?? 'initial')
942+
expectedMap.set(`--${getEscapedCssVarName(key, false)}`, value)
945943
}
946944
}
947945
if (vnode === root && instance.parent) {

packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,4 +465,23 @@ describe('useCssVars', () => {
465465
render(h(App), root)
466466
expect(colorInOnMount).toBe(`red`)
467467
})
468+
469+
test('should set vars as `initial` for nullish values', async () => {
470+
const state = reactive<Record<string, unknown>>({
471+
color: undefined,
472+
size: null,
473+
})
474+
const root = document.createElement('div')
475+
const App = {
476+
setup() {
477+
useCssVars(() => state)
478+
return () => h('div')
479+
},
480+
}
481+
render(h(App), root)
482+
await nextTick()
483+
const style = (root.children[0] as HTMLElement).style
484+
expect(style.getPropertyValue(`--color`)).toBe('initial')
485+
expect(style.getPropertyValue(`--size`)).toBe('initial')
486+
})
468487
})

packages/runtime-dom/src/helpers/useCssVars.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
1717
* Runtime helper for SFC's CSS variable injection feature.
1818
* @private
1919
*/
20-
export function useCssVars(getter: (ctx: any) => Record<string, string>): void {
20+
export function useCssVars(
21+
getter: (ctx: any) => Record<string, unknown>,
22+
): void {
2123
if (!__BROWSER__ && !__TEST__) return
2224

2325
const instance = getCurrentInstance()
@@ -64,7 +66,7 @@ export function useCssVars(getter: (ctx: any) => Record<string, string>): void {
6466
})
6567
}
6668

67-
function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {
69+
function setVarsOnVNode(vnode: VNode, vars: Record<string, unknown>) {
6870
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
6971
const suspense = vnode.suspense!
7072
vnode = suspense.activeBranch!
@@ -94,13 +96,14 @@ function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {
9496
}
9597
}
9698

97-
function setVarsOnNode(el: Node, vars: Record<string, string>) {
99+
function setVarsOnNode(el: Node, vars: Record<string, unknown>) {
98100
if (el.nodeType === 1) {
99101
const style = (el as HTMLElement).style
100102
let cssText = ''
101103
for (const key in vars) {
102-
style.setProperty(`--${key}`, vars[key])
103-
cssText += `--${key}: ${vars[key]};`
104+
const value = String(vars[key] ?? 'initial')
105+
style.setProperty(`--${key}`, value)
106+
cssText += `--${key}: ${value};`
104107
}
105108
;(style as any)[CSS_VAR_TEXT] = cssText
106109
}

packages/server-renderer/src/helpers/ssrRenderAttrs.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
22
escapeHtml,
3+
isArray,
4+
isObject,
35
isRenderableAttrValue,
46
isSVGTag,
57
stringifyStyle,
@@ -93,6 +95,22 @@ export function ssrRenderStyle(raw: unknown): string {
9395
if (isString(raw)) {
9496
return escapeHtml(raw)
9597
}
96-
const styles = normalizeStyle(raw)
98+
const styles = normalizeStyle(ssrResetCssVars(raw))
9799
return escapeHtml(stringifyStyle(styles))
98100
}
101+
102+
function ssrResetCssVars(raw: unknown) {
103+
if (!isArray(raw) && isObject(raw)) {
104+
const res: Record<string, unknown> = {}
105+
for (const key in raw) {
106+
// `:` prefixed keys are coming from `ssrCssVars`
107+
if (key.startsWith(':--')) {
108+
res[key.slice(1)] = raw[key] ?? 'initial'
109+
} else {
110+
res[key] = raw[key]
111+
}
112+
}
113+
return res
114+
}
115+
return raw
116+
}

0 commit comments

Comments
 (0)