mirror of https://github.com/vuejs/core.git
Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
|
e60edc06f2 | |
|
21b685ad9d | |
|
ce933390ad | |
|
d3af67e878 | |
|
e0e8221d7f | |
|
347ef1d3f5 | |
|
f97c4d4e6e | |
|
a0bd1f518e | |
|
01a122283f | |
|
eca0e1ccff | |
|
c85f1b5a13 | |
|
7e133dbe01 | |
|
ba391f5fdf | |
|
50a1c30899 | |
|
b8b926cdee |
10
package.json
10
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.17",
|
"version": "3.5.17",
|
||||||
"packageManager": "pnpm@10.12.1",
|
"packageManager": "pnpm@10.12.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -69,9 +69,9 @@
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@swc/core": "^1.12.1",
|
"@swc/core": "^1.12.11",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^22.15.31",
|
"@types/node": "^22.16.0",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"@types/serve-handler": "^6.1.4",
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitest/coverage-v8": "^3.1.4",
|
"@vitest/coverage-v8": "^3.1.4",
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^5.0.0",
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.25.5",
|
"esbuild": "^0.25.6",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.27.0",
|
||||||
"eslint-plugin-import-x": "^4.13.1",
|
"eslint-plugin-import-x": "^4.13.1",
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~24.9.0",
|
"puppeteer": "~24.9.0",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.43.0",
|
"rollup": "^4.44.2",
|
||||||
"rollup-plugin-dts": "^6.2.1",
|
"rollup-plugin-dts": "^6.2.1",
|
||||||
"rollup-plugin-esbuild": "^6.2.1",
|
"rollup-plugin-esbuild": "^6.2.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"vite": "catalog:"
|
"vite": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^4.6.1",
|
"@vue/repl": "^4.6.2",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import Header from './Header.vue'
|
import Header from './Header.vue'
|
||||||
import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
|
import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
|
||||||
import Monaco from '@vue/repl/monaco-editor'
|
import Monaco from '@vue/repl/monaco-editor'
|
||||||
import { ref, watchEffect, onMounted, computed } from 'vue'
|
import { ref, watchEffect, onMounted, computed, watch } from 'vue'
|
||||||
|
|
||||||
const replRef = ref<InstanceType<typeof Repl>>()
|
const replRef = ref<InstanceType<typeof Repl>>()
|
||||||
|
|
||||||
|
@ -115,6 +115,34 @@ onMounted(() => {
|
||||||
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
||||||
window.process = { env: {} }
|
window.process = { env: {} }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isVaporSupported = ref(false)
|
||||||
|
watch(
|
||||||
|
() => store.vueVersion,
|
||||||
|
(version, oldVersion) => {
|
||||||
|
const [major, minor] = (version || store.compiler.version)
|
||||||
|
.split('.')
|
||||||
|
.map((v: string) => parseInt(v, 10))
|
||||||
|
isVaporSupported.value = major > 3 || (major === 3 && minor >= 6)
|
||||||
|
if (oldVersion) reloadPage()
|
||||||
|
},
|
||||||
|
{ immediate: true, flush: 'pre' },
|
||||||
|
)
|
||||||
|
|
||||||
|
const previewOptions = computed(() => ({
|
||||||
|
customCode: {
|
||||||
|
importCode: `import { initCustomFormatter${isVaporSupported.value ? ', vaporInteropPlugin' : ''} } from 'vue'`,
|
||||||
|
useCode: `
|
||||||
|
${isVaporSupported.value ? 'app.use(vaporInteropPlugin)' : ''}
|
||||||
|
if (window.devtoolsFormatters) {
|
||||||
|
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
||||||
|
window.devtoolsFormatters.splice(index, 1)
|
||||||
|
initCustomFormatter()
|
||||||
|
} else {
|
||||||
|
initCustomFormatter()
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -145,18 +173,7 @@ onMounted(() => {
|
||||||
:showOpenSourceMap="true"
|
:showOpenSourceMap="true"
|
||||||
:autoResize="true"
|
:autoResize="true"
|
||||||
:clearConsole="false"
|
:clearConsole="false"
|
||||||
:preview-options="{
|
:preview-options="previewOptions"
|
||||||
customCode: {
|
|
||||||
importCode: `import { initCustomFormatter } from 'vue'`,
|
|
||||||
useCode: `if (window.devtoolsFormatters) {
|
|
||||||
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
|
||||||
window.devtoolsFormatters.splice(index, 1)
|
|
||||||
initCustomFormatter()
|
|
||||||
} else {
|
|
||||||
initCustomFormatter()
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ import {
|
||||||
isCoreComponent,
|
isCoreComponent,
|
||||||
isSimpleIdentifier,
|
isSimpleIdentifier,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
|
isVPre,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { decodeHTML } from 'entities/lib/decode.js'
|
import { decodeHTML } from 'entities/lib/decode.js'
|
||||||
import {
|
import {
|
||||||
|
@ -246,7 +247,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
ondirarg(start, end) {
|
ondirarg(start, end) {
|
||||||
if (start === end) return
|
if (start === end) return
|
||||||
const arg = getSlice(start, end)
|
const arg = getSlice(start, end)
|
||||||
if (inVPre) {
|
if (inVPre && !isVPre(currentProp!)) {
|
||||||
;(currentProp as AttributeNode).name += arg
|
;(currentProp as AttributeNode).name += arg
|
||||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
} else {
|
} else {
|
||||||
|
@ -262,7 +263,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
|
|
||||||
ondirmodifier(start, end) {
|
ondirmodifier(start, end) {
|
||||||
const mod = getSlice(start, end)
|
const mod = getSlice(start, end)
|
||||||
if (inVPre) {
|
if (inVPre && !isVPre(currentProp!)) {
|
||||||
;(currentProp as AttributeNode).name += '.' + mod
|
;(currentProp as AttributeNode).name += '.' + mod
|
||||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
arg.children.unshift(`(`)
|
arg.children.unshift(`(`)
|
||||||
arg.children.push(`) || ""`)
|
arg.children.push(`) || ""`)
|
||||||
} else if (!arg.isStatic) {
|
} else if (!arg.isStatic) {
|
||||||
arg.content = `${arg.content} || ""`
|
arg.content = arg.content ? `${arg.content} || ""` : `""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// .sync is replaced by v-model:arg
|
// .sync is replaced by v-model:arg
|
||||||
|
|
|
@ -63,7 +63,7 @@ export function isCoreComponent(tag: string): symbol | void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nonIdentifierRE = /^\d|[^\$\w\xA0-\uFFFF]/
|
const nonIdentifierRE = /^$|^\d|[^\$\w\xA0-\uFFFF]/
|
||||||
export const isSimpleIdentifier = (name: string): boolean =>
|
export const isSimpleIdentifier = (name: string): boolean =>
|
||||||
!nonIdentifierRE.test(name)
|
!nonIdentifierRE.test(name)
|
||||||
|
|
||||||
|
@ -343,6 +343,10 @@ export function isText(
|
||||||
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isVPre(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||||
|
return p.type === NodeTypes.DIRECTIVE && p.name === 'pre'
|
||||||
|
}
|
||||||
|
|
||||||
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ if (__TEST__) {
|
||||||
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`DOMErrorCodes need to be updated to ${
|
`DOMErrorCodes need to be updated to ${
|
||||||
ErrorCodes.__EXTEND_POINT__ + 1
|
ErrorCodes.__EXTEND_POINT__
|
||||||
} to match extension point from core ErrorCodes.`,
|
} to match extension point from core ErrorCodes.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -884,9 +884,9 @@ export default {
|
||||||
|
|
||||||
return (_ctx, _push, _parent, _attrs) => {
|
return (_ctx, _push, _parent, _attrs) => {
|
||||||
const _cssVars = { style: {
|
const _cssVars = { style: {
|
||||||
"--xxxxxxxx-count": (count.value),
|
":--xxxxxxxx-count": (count.value),
|
||||||
"--xxxxxxxx-style\\\\.color": (style.color),
|
":--xxxxxxxx-style\\\\.color": (style.color),
|
||||||
"--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
":--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
||||||
}}
|
}}
|
||||||
_push(\`<!--[--><div\${
|
_push(\`<!--[--><div\${
|
||||||
_ssrRenderAttrs(_cssVars)
|
_ssrRenderAttrs(_cssVars)
|
||||||
|
|
|
@ -652,10 +652,10 @@ describe('SFC compile <script setup>', () => {
|
||||||
expect(content).toMatch(`return (_ctx, _push`)
|
expect(content).toMatch(`return (_ctx, _push`)
|
||||||
expect(content).toMatch(`ssrInterpolate`)
|
expect(content).toMatch(`ssrInterpolate`)
|
||||||
expect(content).not.toMatch(`useCssVars`)
|
expect(content).not.toMatch(`useCssVars`)
|
||||||
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
|
expect(content).toMatch(`":--${mockId}-count": (count.value)`)
|
||||||
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
|
expect(content).toMatch(`":--${mockId}-style\\\\.color": (style.color)`)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
`":--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
||||||
)
|
)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,6 +18,7 @@ import type {
|
||||||
Declaration,
|
Declaration,
|
||||||
ExportSpecifier,
|
ExportSpecifier,
|
||||||
Identifier,
|
Identifier,
|
||||||
|
LVal,
|
||||||
Node,
|
Node,
|
||||||
ObjectPattern,
|
ObjectPattern,
|
||||||
Statement,
|
Statement,
|
||||||
|
@ -540,7 +541,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
// defineProps
|
// defineProps
|
||||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
|
||||||
if (ctx.propsDestructureRestId) {
|
if (ctx.propsDestructureRestId) {
|
||||||
setupBindings[ctx.propsDestructureRestId] =
|
setupBindings[ctx.propsDestructureRestId] =
|
||||||
BindingTypes.SETUP_REACTIVE_CONST
|
BindingTypes.SETUP_REACTIVE_CONST
|
||||||
|
@ -548,10 +549,10 @@ export function compileScript(
|
||||||
|
|
||||||
// defineEmits
|
// defineEmits
|
||||||
const isDefineEmits =
|
const isDefineEmits =
|
||||||
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
!isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
|
||||||
!isDefineEmits &&
|
!isDefineEmits &&
|
||||||
(processDefineSlots(ctx, init, decl.id) ||
|
(processDefineSlots(ctx, init, decl.id as LVal) ||
|
||||||
processDefineModel(ctx, init, decl.id))
|
processDefineModel(ctx, init, decl.id as LVal))
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDefineProps &&
|
isDefineProps &&
|
||||||
|
|
|
@ -23,7 +23,12 @@ export function genCssVarsFromList(
|
||||||
return `{\n ${vars
|
return `{\n ${vars
|
||||||
.map(
|
.map(
|
||||||
key =>
|
key =>
|
||||||
`"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
|
// The `:` prefix here is used in `ssrRenderStyle` to distinguish whether
|
||||||
|
// a custom property comes from `ssrCssVars`. If it does, we need to reset
|
||||||
|
// its value to `initial` on the component instance to avoid unintentionally
|
||||||
|
// inheriting the same property value from a different instance of the same
|
||||||
|
// component in the outer scope.
|
||||||
|
`"${isSSR ? `:--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
|
||||||
)
|
)
|
||||||
.join(',\n ')}\n}`
|
.join(',\n ')}\n}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { warn } from '../warn'
|
||||||
|
|
||||||
const animationNameRE = /^(-\w+-)?animation-name$/
|
const animationNameRE = /^(-\w+-)?animation-name$/
|
||||||
const animationRE = /^(-\w+-)?animation$/
|
const animationRE = /^(-\w+-)?animation$/
|
||||||
|
const keyframesRE = /^(?:-\w+-)?keyframes$/
|
||||||
|
|
||||||
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||||
const keyframes = Object.create(null)
|
const keyframes = Object.create(null)
|
||||||
|
@ -21,10 +22,7 @@ const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||||
processRule(id, rule)
|
processRule(id, rule)
|
||||||
},
|
},
|
||||||
AtRule(node) {
|
AtRule(node) {
|
||||||
if (
|
if (keyframesRE.test(node.name) && !node.params.endsWith(`-${shortId}`)) {
|
||||||
/-?keyframes$/.test(node.name) &&
|
|
||||||
!node.params.endsWith(`-${shortId}`)
|
|
||||||
) {
|
|
||||||
// register keyframes
|
// register keyframes
|
||||||
keyframes[node.params] = node.params = node.params + '-' + shortId
|
keyframes[node.params] = node.params = node.params + '-' + shortId
|
||||||
}
|
}
|
||||||
|
@ -72,7 +70,7 @@ function processRule(id: string, rule: Rule) {
|
||||||
processedRules.has(rule) ||
|
processedRules.has(rule) ||
|
||||||
(rule.parent &&
|
(rule.parent &&
|
||||||
rule.parent.type === 'atrule' &&
|
rule.parent.type === 'atrule' &&
|
||||||
/-?keyframes$/.test((rule.parent as AtRule).name))
|
keyframesRE.test((rule.parent as AtRule).name))
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ if (__TEST__) {
|
||||||
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`SSRErrorCodes need to be updated to ${
|
`SSRErrorCodes need to be updated to ${
|
||||||
DOMErrorCodes.__EXTEND_POINT__ + 1
|
DOMErrorCodes.__EXTEND_POINT__
|
||||||
} to match extension point from core DOMErrorCodes.`,
|
} to match extension point from core DOMErrorCodes.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
import { currentInstance } from './component'
|
import { currentInstance, getCurrentInstance } from './component'
|
||||||
import { currentRenderingInstance } from './componentRenderContext'
|
|
||||||
import { currentApp } from './apiCreateApp'
|
import { currentApp } from './apiCreateApp'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ export function inject(
|
||||||
) {
|
) {
|
||||||
// fallback to `currentRenderingInstance` so that this can be called in
|
// fallback to `currentRenderingInstance` so that this can be called in
|
||||||
// a functional component
|
// a functional component
|
||||||
const instance = currentInstance || currentRenderingInstance
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
// also support looking up from app-level provides w/ `app.runWithContext()`
|
// also support looking up from app-level provides w/ `app.runWithContext()`
|
||||||
if (instance || currentApp) {
|
if (instance || currentApp) {
|
||||||
|
@ -90,5 +89,5 @@ export function inject(
|
||||||
* user. One example is `useRoute()` in `vue-router`.
|
* user. One example is `useRoute()` in `vue-router`.
|
||||||
*/
|
*/
|
||||||
export function hasInjectionContext(): boolean {
|
export function hasInjectionContext(): boolean {
|
||||||
return !!(currentInstance || currentRenderingInstance || currentApp)
|
return !!(getCurrentInstance() || currentApp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -585,13 +585,13 @@ export interface ComponentInternalInstance {
|
||||||
* For updating css vars on contained teleports
|
* For updating css vars on contained teleports
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
ut?: (vars?: Record<string, string>) => void
|
ut?: (vars?: Record<string, unknown>) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dev only. For style v-bind hydration mismatch checks
|
* dev only. For style v-bind hydration mismatch checks
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
getCssVars?: () => Record<string, string>
|
getCssVars?: () => Record<string, unknown>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* v2 compat only, for caching mutated $options
|
* v2 compat only, for caching mutated $options
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
isString,
|
isString,
|
||||||
normalizeClass,
|
normalizeClass,
|
||||||
|
normalizeCssVarValue,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
stringifyStyle,
|
stringifyStyle,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
@ -945,10 +946,8 @@ function resolveCssVars(
|
||||||
) {
|
) {
|
||||||
const cssVars = instance.getCssVars()
|
const cssVars = instance.getCssVars()
|
||||||
for (const key in cssVars) {
|
for (const key in cssVars) {
|
||||||
expectedMap.set(
|
const value = normalizeCssVarValue(cssVars[key])
|
||||||
`--${getEscapedCssVarName(key, false)}`,
|
expectedMap.set(`--${getEscapedCssVarName(key, false)}`, value)
|
||||||
String(cssVars[key]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (vnode === root && instance.parent) {
|
if (vnode === root && instance.parent) {
|
||||||
|
|
|
@ -465,4 +465,27 @@ describe('useCssVars', () => {
|
||||||
render(h(App), root)
|
render(h(App), root)
|
||||||
expect(colorInOnMount).toBe(`red`)
|
expect(colorInOnMount).toBe(`red`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should set vars as `initial` for nullish values', async () => {
|
||||||
|
// `getPropertyValue` cannot reflect the real value for white spaces and JSDOM also
|
||||||
|
// doesn't 100% reflect the real behavior of browsers, so we only keep the test for
|
||||||
|
// `initial` value here.
|
||||||
|
// The value normalization is tested in packages/shared/__tests__/cssVars.spec.ts.
|
||||||
|
const state = reactive<Record<string, unknown>>({
|
||||||
|
foo: undefined,
|
||||||
|
bar: null,
|
||||||
|
})
|
||||||
|
const root = document.createElement('div')
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
useCssVars(() => state)
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
await nextTick()
|
||||||
|
const style = (root.children[0] as HTMLElement).style
|
||||||
|
expect(style.getPropertyValue('--foo')).toBe('initial')
|
||||||
|
expect(style.getPropertyValue('--bar')).toBe('initial')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,14 +10,16 @@ import {
|
||||||
warn,
|
warn,
|
||||||
watch,
|
watch,
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { NOOP, ShapeFlags } from '@vue/shared'
|
import { NOOP, ShapeFlags, normalizeCssVarValue } from '@vue/shared'
|
||||||
|
|
||||||
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
|
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
|
||||||
/**
|
/**
|
||||||
* Runtime helper for SFC's CSS variable injection feature.
|
* Runtime helper for SFC's CSS variable injection feature.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function useCssVars(getter: (ctx: any) => Record<string, string>): void {
|
export function useCssVars(
|
||||||
|
getter: (ctx: any) => Record<string, unknown>,
|
||||||
|
): void {
|
||||||
if (!__BROWSER__ && !__TEST__) return
|
if (!__BROWSER__ && !__TEST__) return
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
const instance = getCurrentInstance()
|
||||||
|
@ -64,7 +66,7 @@ export function useCssVars(getter: (ctx: any) => Record<string, string>): void {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {
|
function setVarsOnVNode(vnode: VNode, vars: Record<string, unknown>) {
|
||||||
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
const suspense = vnode.suspense!
|
const suspense = vnode.suspense!
|
||||||
vnode = suspense.activeBranch!
|
vnode = suspense.activeBranch!
|
||||||
|
@ -94,13 +96,14 @@ function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setVarsOnNode(el: Node, vars: Record<string, string>) {
|
function setVarsOnNode(el: Node, vars: Record<string, unknown>) {
|
||||||
if (el.nodeType === 1) {
|
if (el.nodeType === 1) {
|
||||||
const style = (el as HTMLElement).style
|
const style = (el as HTMLElement).style
|
||||||
let cssText = ''
|
let cssText = ''
|
||||||
for (const key in vars) {
|
for (const key in vars) {
|
||||||
style.setProperty(`--${key}`, vars[key])
|
const value = normalizeCssVarValue(vars[key])
|
||||||
cssText += `--${key}: ${vars[key]};`
|
style.setProperty(`--${key}`, value)
|
||||||
|
cssText += `--${key}: ${value};`
|
||||||
}
|
}
|
||||||
;(style as any)[CSS_VAR_TEXT] = cssText
|
;(style as any)[CSS_VAR_TEXT] = cssText
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,4 +203,19 @@ describe('ssr: renderStyle', () => {
|
||||||
}),
|
}),
|
||||||
).toBe(`color:"><script;`)
|
).toBe(`color:"><script;`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('useCssVars handling', () => {
|
||||||
|
expect(
|
||||||
|
ssrRenderStyle({
|
||||||
|
fontSize: null,
|
||||||
|
':--v1': undefined,
|
||||||
|
':--v2': null,
|
||||||
|
':--v3': '',
|
||||||
|
':--v4': ' ',
|
||||||
|
':--v5': 'foo',
|
||||||
|
':--v6': 0,
|
||||||
|
'--foo': 1,
|
||||||
|
}),
|
||||||
|
).toBe(`--v1:initial;--v2:initial;--v3: ;--v4: ;--v5:foo;--v6:0;--foo:1;`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import {
|
import {
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
isArray,
|
||||||
|
isObject,
|
||||||
isRenderableAttrValue,
|
isRenderableAttrValue,
|
||||||
isSVGTag,
|
isSVGTag,
|
||||||
stringifyStyle,
|
stringifyStyle,
|
||||||
|
@ -12,6 +14,7 @@ import {
|
||||||
isString,
|
isString,
|
||||||
makeMap,
|
makeMap,
|
||||||
normalizeClass,
|
normalizeClass,
|
||||||
|
normalizeCssVarValue,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
propsToAttrMap,
|
propsToAttrMap,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
@ -93,6 +96,22 @@ export function ssrRenderStyle(raw: unknown): string {
|
||||||
if (isString(raw)) {
|
if (isString(raw)) {
|
||||||
return escapeHtml(raw)
|
return escapeHtml(raw)
|
||||||
}
|
}
|
||||||
const styles = normalizeStyle(raw)
|
const styles = normalizeStyle(ssrResetCssVars(raw))
|
||||||
return escapeHtml(stringifyStyle(styles))
|
return escapeHtml(stringifyStyle(styles))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ssrResetCssVars(raw: unknown) {
|
||||||
|
if (!isArray(raw) && isObject(raw)) {
|
||||||
|
const res: Record<string, unknown> = {}
|
||||||
|
for (const key in raw) {
|
||||||
|
// `:` prefixed keys are coming from `ssrCssVars`
|
||||||
|
if (key.startsWith(':--')) {
|
||||||
|
res[key.slice(1)] = normalizeCssVarValue(raw[key])
|
||||||
|
} else {
|
||||||
|
res[key] = raw[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { normalizeCssVarValue } from '../src'
|
||||||
|
|
||||||
|
describe('utils/cssVars', () => {
|
||||||
|
test('should normalize css binding values correctly', () => {
|
||||||
|
expect(normalizeCssVarValue(null)).toBe('initial')
|
||||||
|
expect(normalizeCssVarValue(undefined)).toBe('initial')
|
||||||
|
expect(normalizeCssVarValue('')).toBe(' ')
|
||||||
|
expect(normalizeCssVarValue(' ')).toBe(' ')
|
||||||
|
expect(normalizeCssVarValue('foo')).toBe('foo')
|
||||||
|
expect(normalizeCssVarValue(0)).toBe('0')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should warn on invalid css binding values', () => {
|
||||||
|
const warning =
|
||||||
|
'[Vue warn] Invalid value used for CSS binding. Expected a string or a finite number but received:'
|
||||||
|
expect(normalizeCssVarValue(NaN)).toBe('NaN')
|
||||||
|
expect(warning).toHaveBeenWarnedTimes(1)
|
||||||
|
expect(normalizeCssVarValue(Infinity)).toBe('Infinity')
|
||||||
|
expect(warning).toHaveBeenWarnedTimes(2)
|
||||||
|
expect(normalizeCssVarValue(-Infinity)).toBe('-Infinity')
|
||||||
|
expect(warning).toHaveBeenWarnedTimes(3)
|
||||||
|
expect(normalizeCssVarValue({})).toBe('[object Object]')
|
||||||
|
expect(warning).toHaveBeenWarnedTimes(4)
|
||||||
|
expect(normalizeCssVarValue([])).toBe('')
|
||||||
|
expect(warning).toHaveBeenWarnedTimes(5)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Normalize CSS var value created by `v-bind` in `<style>` block
|
||||||
|
* See https://github.com/vuejs/core/pull/12461#issuecomment-2495804664
|
||||||
|
*/
|
||||||
|
export function normalizeCssVarValue(value: unknown): string {
|
||||||
|
if (value == null) {
|
||||||
|
return 'initial'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value === '' ? ' ' : value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
||||||
|
if (__DEV__) {
|
||||||
|
console.warn(
|
||||||
|
'[Vue warn] Invalid value used for CSS binding. Expected a string or a finite number but received:',
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value)
|
||||||
|
}
|
|
@ -12,3 +12,4 @@ export * from './escapeHtml'
|
||||||
export * from './looseEqual'
|
export * from './looseEqual'
|
||||||
export * from './toDisplayString'
|
export * from './toDisplayString'
|
||||||
export * from './typeUtils'
|
export * from './typeUtils'
|
||||||
|
export * from './cssVars'
|
||||||
|
|
765
pnpm-lock.yaml
765
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -3,8 +3,8 @@ packages:
|
||||||
- 'packages-private/*'
|
- 'packages-private/*'
|
||||||
|
|
||||||
catalog:
|
catalog:
|
||||||
'@babel/parser': ^7.27.5
|
'@babel/parser': ^7.28.0
|
||||||
'@babel/types': ^7.27.6
|
'@babel/types': ^7.28.0
|
||||||
'estree-walker': ^2.0.2
|
'estree-walker': ^2.0.2
|
||||||
'magic-string': ^0.30.17
|
'magic-string': ^0.30.17
|
||||||
'source-map-js': ^1.2.1
|
'source-map-js': ^1.2.1
|
||||||
|
|
Loading…
Reference in New Issue