mirror of
https://gitee.com/vuejs/vue.git
synced 2024-11-29 18:47:39 +08:00
feat(sfc): css v-bind
This commit is contained in:
parent
2d67641656
commit
8ab0074bab
@ -1,4 +1,4 @@
|
|||||||
<script src="../../dist/vue.min.js"></script>
|
<script src="../../dist/vue.js"></script>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="../../node_modules/todomvc-app-css/index.css"
|
href="../../node_modules/todomvc-app-css/index.css"
|
||||||
|
@ -42,6 +42,12 @@ import { isReservedTag } from 'web/util'
|
|||||||
import { dirRE } from 'compiler/parser'
|
import { dirRE } from 'compiler/parser'
|
||||||
import { parseText } from 'compiler/parser/text-parser'
|
import { parseText } from 'compiler/parser/text-parser'
|
||||||
import { DEFAULT_FILENAME } from './parseComponent'
|
import { DEFAULT_FILENAME } from './parseComponent'
|
||||||
|
import {
|
||||||
|
CSS_VARS_HELPER,
|
||||||
|
genCssVarsCode,
|
||||||
|
genNormalScriptCssVarsCode
|
||||||
|
} from './cssVars'
|
||||||
|
import { rewriteDefault } from './rewriteDefault'
|
||||||
|
|
||||||
// Special compiler macros
|
// Special compiler macros
|
||||||
const DEFINE_PROPS = 'defineProps'
|
const DEFINE_PROPS = 'defineProps'
|
||||||
@ -57,6 +63,11 @@ const isBuiltInDir = makeMap(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export interface SFCScriptCompileOptions {
|
export interface SFCScriptCompileOptions {
|
||||||
|
/**
|
||||||
|
* Scope ID for prefixing injected CSS variables.
|
||||||
|
* This must be consistent with the `id` passed to `compileStyle`.
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
/**
|
/**
|
||||||
* Production mode. Used to determine whether to generate hashed CSS variables
|
* Production mode. Used to determine whether to generate hashed CSS variables
|
||||||
*/
|
*/
|
||||||
@ -86,14 +97,15 @@ export interface ImportBinding {
|
|||||||
*/
|
*/
|
||||||
export function compileScript(
|
export function compileScript(
|
||||||
sfc: SFCDescriptor,
|
sfc: SFCDescriptor,
|
||||||
options: SFCScriptCompileOptions = {}
|
options: SFCScriptCompileOptions = { id: '' }
|
||||||
): SFCScriptBlock {
|
): SFCScriptBlock {
|
||||||
let { filename, script, scriptSetup, source } = sfc
|
let { filename, script, scriptSetup, source } = sfc
|
||||||
const isProd = !!options.isProd
|
const isProd = !!options.isProd
|
||||||
const genSourceMap = options.sourceMap !== false
|
const genSourceMap = options.sourceMap !== false
|
||||||
let refBindings: string[] | undefined
|
let refBindings: string[] | undefined
|
||||||
|
|
||||||
// const cssVars = sfc.cssVars
|
const cssVars = sfc.cssVars
|
||||||
|
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
||||||
const scriptLang = script && script.lang
|
const scriptLang = script && script.lang
|
||||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||||
const isTS =
|
const isTS =
|
||||||
@ -132,6 +144,16 @@ export function compileScript(
|
|||||||
sourceType: 'module'
|
sourceType: 'module'
|
||||||
}).program
|
}).program
|
||||||
const bindings = analyzeScriptBindings(scriptAst.body)
|
const bindings = analyzeScriptBindings(scriptAst.body)
|
||||||
|
if (cssVars.length) {
|
||||||
|
content = rewriteDefault(content, DEFAULT_VAR, plugins)
|
||||||
|
content += genNormalScriptCssVarsCode(
|
||||||
|
cssVars,
|
||||||
|
bindings,
|
||||||
|
scopeId,
|
||||||
|
isProd
|
||||||
|
)
|
||||||
|
content += `\nexport default ${DEFAULT_VAR}`
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...script,
|
...script,
|
||||||
content,
|
content,
|
||||||
@ -1082,7 +1104,13 @@ export function compileScript(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 8. inject `useCssVars` calls
|
// 8. inject `useCssVars` calls
|
||||||
// Not backported in Vue 2
|
if (cssVars.length) {
|
||||||
|
helperImports.add(CSS_VARS_HELPER)
|
||||||
|
s.prependRight(
|
||||||
|
startOffset,
|
||||||
|
`\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 9. finalize setup() argument signature
|
// 9. finalize setup() argument signature
|
||||||
let args = `__props`
|
let args = `__props`
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
StylePreprocessor,
|
StylePreprocessor,
|
||||||
StylePreprocessorResults
|
StylePreprocessorResults
|
||||||
} from './stylePreprocessors'
|
} from './stylePreprocessors'
|
||||||
|
import { cssVarsPlugin } from './cssVars'
|
||||||
|
|
||||||
export interface SFCStyleCompileOptions {
|
export interface SFCStyleCompileOptions {
|
||||||
source: string
|
source: string
|
||||||
@ -19,6 +20,7 @@ export interface SFCStyleCompileOptions {
|
|||||||
preprocessOptions?: any
|
preprocessOptions?: any
|
||||||
postcssOptions?: any
|
postcssOptions?: any
|
||||||
postcssPlugins?: any[]
|
postcssPlugins?: any[]
|
||||||
|
isProd?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
|
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
|
||||||
@ -52,6 +54,7 @@ export function doCompileStyle(
|
|||||||
id,
|
id,
|
||||||
scoped = true,
|
scoped = true,
|
||||||
trim = true,
|
trim = true,
|
||||||
|
isProd = false,
|
||||||
preprocessLang,
|
preprocessLang,
|
||||||
postcssOptions,
|
postcssOptions,
|
||||||
postcssPlugins
|
postcssPlugins
|
||||||
@ -62,6 +65,7 @@ export function doCompileStyle(
|
|||||||
const source = preProcessedSource ? preProcessedSource.code : options.source
|
const source = preProcessedSource ? preProcessedSource.code : options.source
|
||||||
|
|
||||||
const plugins = (postcssPlugins || []).slice()
|
const plugins = (postcssPlugins || []).slice()
|
||||||
|
plugins.unshift(cssVarsPlugin({ id: id.replace(/^data-v-/, ''), isProd }))
|
||||||
if (trim) {
|
if (trim) {
|
||||||
plugins.push(trimPlugin())
|
plugins.push(trimPlugin())
|
||||||
}
|
}
|
||||||
|
@ -148,8 +148,7 @@ function actuallyCompile(
|
|||||||
// version of Buble that applies ES2015 transforms + stripping `with` usage
|
// version of Buble that applies ES2015 transforms + stripping `with` usage
|
||||||
let code =
|
let code =
|
||||||
`var __render__ = ${prefixIdentifiers(
|
`var __render__ = ${prefixIdentifiers(
|
||||||
render,
|
`function render(${isFunctional ? `_c,_vm` : ``}){${render}\n}`,
|
||||||
`render`,
|
|
||||||
isFunctional,
|
isFunctional,
|
||||||
isTS,
|
isTS,
|
||||||
transpileOptions,
|
transpileOptions,
|
||||||
@ -157,8 +156,7 @@ function actuallyCompile(
|
|||||||
)}\n` +
|
)}\n` +
|
||||||
`var __staticRenderFns__ = [${staticRenderFns.map(code =>
|
`var __staticRenderFns__ = [${staticRenderFns.map(code =>
|
||||||
prefixIdentifiers(
|
prefixIdentifiers(
|
||||||
code,
|
`function (${isFunctional ? `_c,_vm` : ``}){${code}\n}`,
|
||||||
``,
|
|
||||||
isFunctional,
|
isFunctional,
|
||||||
isTS,
|
isTS,
|
||||||
transpileOptions,
|
transpileOptions,
|
||||||
|
179
packages/compiler-sfc/src/cssVars.ts
Normal file
179
packages/compiler-sfc/src/cssVars.ts
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import { BindingMetadata } from './types'
|
||||||
|
import { SFCDescriptor } from './parseComponent'
|
||||||
|
import { PluginCreator } from 'postcss'
|
||||||
|
import hash from 'hash-sum'
|
||||||
|
import { prefixIdentifiers } from './prefixIdentifiers'
|
||||||
|
|
||||||
|
export const CSS_VARS_HELPER = `useCssVars`
|
||||||
|
|
||||||
|
export function genCssVarsFromList(
|
||||||
|
vars: string[],
|
||||||
|
id: string,
|
||||||
|
isProd: boolean,
|
||||||
|
isSSR = false
|
||||||
|
): string {
|
||||||
|
return `{\n ${vars
|
||||||
|
.map(
|
||||||
|
key => `"${isSSR ? `--` : ``}${genVarName(id, key, isProd)}": (${key})`
|
||||||
|
)
|
||||||
|
.join(',\n ')}\n}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function genVarName(id: string, raw: string, isProd: boolean): string {
|
||||||
|
if (isProd) {
|
||||||
|
return hash(id + raw)
|
||||||
|
} else {
|
||||||
|
return `${id}-${raw.replace(/([^\w-])/g, '_')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeExpression(exp: string) {
|
||||||
|
exp = exp.trim()
|
||||||
|
if (
|
||||||
|
(exp[0] === `'` && exp[exp.length - 1] === `'`) ||
|
||||||
|
(exp[0] === `"` && exp[exp.length - 1] === `"`)
|
||||||
|
) {
|
||||||
|
return exp.slice(1, -1)
|
||||||
|
}
|
||||||
|
return exp
|
||||||
|
}
|
||||||
|
|
||||||
|
const vBindRE = /v-bind\s*\(/g
|
||||||
|
|
||||||
|
export function parseCssVars(sfc: SFCDescriptor): string[] {
|
||||||
|
const vars: string[] = []
|
||||||
|
sfc.styles.forEach(style => {
|
||||||
|
let match
|
||||||
|
// ignore v-bind() in comments /* ... */
|
||||||
|
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
|
||||||
|
while ((match = vBindRE.exec(content))) {
|
||||||
|
const start = match.index + match[0].length
|
||||||
|
const end = lexBinding(content, start)
|
||||||
|
if (end !== null) {
|
||||||
|
const variable = normalizeExpression(content.slice(start, end))
|
||||||
|
if (!vars.includes(variable)) {
|
||||||
|
vars.push(variable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum LexerState {
|
||||||
|
inParens,
|
||||||
|
inSingleQuoteString,
|
||||||
|
inDoubleQuoteString
|
||||||
|
}
|
||||||
|
|
||||||
|
function lexBinding(content: string, start: number): number | null {
|
||||||
|
let state: LexerState = LexerState.inParens
|
||||||
|
let parenDepth = 0
|
||||||
|
|
||||||
|
for (let i = start; i < content.length; i++) {
|
||||||
|
const char = content.charAt(i)
|
||||||
|
switch (state) {
|
||||||
|
case LexerState.inParens:
|
||||||
|
if (char === `'`) {
|
||||||
|
state = LexerState.inSingleQuoteString
|
||||||
|
} else if (char === `"`) {
|
||||||
|
state = LexerState.inDoubleQuoteString
|
||||||
|
} else if (char === `(`) {
|
||||||
|
parenDepth++
|
||||||
|
} else if (char === `)`) {
|
||||||
|
if (parenDepth > 0) {
|
||||||
|
parenDepth--
|
||||||
|
} else {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case LexerState.inSingleQuoteString:
|
||||||
|
if (char === `'`) {
|
||||||
|
state = LexerState.inParens
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case LexerState.inDoubleQuoteString:
|
||||||
|
if (char === `"`) {
|
||||||
|
state = LexerState.inParens
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// for compileStyle
|
||||||
|
export interface CssVarsPluginOptions {
|
||||||
|
id: string
|
||||||
|
isProd: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => {
|
||||||
|
const { id, isProd } = opts!
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'vue-sfc-vars',
|
||||||
|
Declaration(decl) {
|
||||||
|
// rewrite CSS variables
|
||||||
|
const value = decl.value
|
||||||
|
if (vBindRE.test(value)) {
|
||||||
|
vBindRE.lastIndex = 0
|
||||||
|
let transformed = ''
|
||||||
|
let lastIndex = 0
|
||||||
|
let match
|
||||||
|
while ((match = vBindRE.exec(value))) {
|
||||||
|
const start = match.index + match[0].length
|
||||||
|
const end = lexBinding(value, start)
|
||||||
|
if (end !== null) {
|
||||||
|
const variable = normalizeExpression(value.slice(start, end))
|
||||||
|
transformed +=
|
||||||
|
value.slice(lastIndex, match.index) +
|
||||||
|
`var(--${genVarName(id, variable, isProd)})`
|
||||||
|
lastIndex = end + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decl.value = transformed + value.slice(lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cssVarsPlugin.postcss = true
|
||||||
|
|
||||||
|
export function genCssVarsCode(
|
||||||
|
vars: string[],
|
||||||
|
bindings: BindingMetadata,
|
||||||
|
id: string,
|
||||||
|
isProd: boolean
|
||||||
|
) {
|
||||||
|
const varsExp = genCssVarsFromList(vars, id, isProd)
|
||||||
|
return `_${CSS_VARS_HELPER}((_vm, _setup) => ${prefixIdentifiers(
|
||||||
|
`(${varsExp})`,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
bindings
|
||||||
|
)})`
|
||||||
|
}
|
||||||
|
|
||||||
|
// <script setup> already gets the calls injected as part of the transform
|
||||||
|
// this is only for single normal <script>
|
||||||
|
export function genNormalScriptCssVarsCode(
|
||||||
|
cssVars: string[],
|
||||||
|
bindings: BindingMetadata,
|
||||||
|
id: string,
|
||||||
|
isProd: boolean
|
||||||
|
): string {
|
||||||
|
return (
|
||||||
|
`\nimport { ${CSS_VARS_HELPER} as _${CSS_VARS_HELPER} } from 'vue'\n` +
|
||||||
|
`const __injectCSSVars__ = () => {\n${genCssVarsCode(
|
||||||
|
cssVars,
|
||||||
|
bindings,
|
||||||
|
id,
|
||||||
|
isProd
|
||||||
|
)}}\n` +
|
||||||
|
`const __setup__ = __default__.setup\n` +
|
||||||
|
`__default__.setup = __setup__\n` +
|
||||||
|
` ? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }\n` +
|
||||||
|
` : __injectCSSVars__\n`
|
||||||
|
)
|
||||||
|
}
|
@ -7,6 +7,7 @@ export { generateCodeFrame } from 'compiler/codeframe'
|
|||||||
export { rewriteDefault } from './rewriteDefault'
|
export { rewriteDefault } from './rewriteDefault'
|
||||||
|
|
||||||
// types
|
// types
|
||||||
|
export { SFCParseOptions } from './parse'
|
||||||
export { CompilerOptions, WarningMessage } from 'types/compiler'
|
export { CompilerOptions, WarningMessage } from 'types/compiler'
|
||||||
export { TemplateCompiler } from './types'
|
export { TemplateCompiler } from './types'
|
||||||
export {
|
export {
|
||||||
|
@ -16,7 +16,7 @@ const cache = new LRU<string, SFCDescriptor>(100)
|
|||||||
const splitRE = /\r?\n/g
|
const splitRE = /\r?\n/g
|
||||||
const emptyRE = /^(?:\/\/)?\s*$/
|
const emptyRE = /^(?:\/\/)?\s*$/
|
||||||
|
|
||||||
export interface ParseOptions {
|
export interface SFCParseOptions {
|
||||||
source: string
|
source: string
|
||||||
filename?: string
|
filename?: string
|
||||||
compiler?: TemplateCompiler
|
compiler?: TemplateCompiler
|
||||||
@ -25,7 +25,7 @@ export interface ParseOptions {
|
|||||||
sourceMap?: boolean
|
sourceMap?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(options: ParseOptions): SFCDescriptor {
|
export function parse(options: SFCParseOptions): SFCDescriptor {
|
||||||
const {
|
const {
|
||||||
source,
|
source,
|
||||||
filename = DEFAULT_FILENAME,
|
filename = DEFAULT_FILENAME,
|
||||||
|
@ -4,6 +4,7 @@ import { makeMap } from 'shared/util'
|
|||||||
import { ASTAttr, WarningMessage } from 'types/compiler'
|
import { ASTAttr, WarningMessage } from 'types/compiler'
|
||||||
import { BindingMetadata, RawSourceMap } from './types'
|
import { BindingMetadata, RawSourceMap } from './types'
|
||||||
import type { ImportBinding } from './compileScript'
|
import type { ImportBinding } from './compileScript'
|
||||||
|
import { parseCssVars } from './cssVars'
|
||||||
|
|
||||||
export const DEFAULT_FILENAME = 'anonymous.vue'
|
export const DEFAULT_FILENAME = 'anonymous.vue'
|
||||||
|
|
||||||
@ -50,7 +51,9 @@ export interface SFCDescriptor {
|
|||||||
scriptSetup: SFCScriptBlock | null
|
scriptSetup: SFCScriptBlock | null
|
||||||
styles: SFCBlock[]
|
styles: SFCBlock[]
|
||||||
customBlocks: SFCCustomBlock[]
|
customBlocks: SFCCustomBlock[]
|
||||||
errors: WarningMessage[]
|
cssVars: string[]
|
||||||
|
|
||||||
|
errors: (string | WarningMessage)[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compare with an existing descriptor to determine whether HMR should perform
|
* compare with an existing descriptor to determine whether HMR should perform
|
||||||
@ -84,6 +87,7 @@ export function parseComponent(
|
|||||||
scriptSetup: null, // TODO
|
scriptSetup: null, // TODO
|
||||||
styles: [],
|
styles: [],
|
||||||
customBlocks: [],
|
customBlocks: [],
|
||||||
|
cssVars: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
shouldForceReload: null as any // attached in parse() by compiler-sfc
|
shouldForceReload: null as any // attached in parse() by compiler-sfc
|
||||||
}
|
}
|
||||||
@ -205,5 +209,8 @@ export function parseComponent(
|
|||||||
outputSourceRange: options.outputSourceRange
|
outputSourceRange: options.outputSourceRange
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// parse CSS vars
|
||||||
|
sfc.cssVars = parseCssVars(sfc)
|
||||||
|
|
||||||
return sfc
|
return sfc
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,15 @@ const doNotPrefix = makeMap(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The input is expected to be the render function code directly returned from
|
* The input is expected to be a valid expression.
|
||||||
* `compile()` calls, e.g. `with(this){return ...}`
|
|
||||||
*/
|
*/
|
||||||
export function prefixIdentifiers(
|
export function prefixIdentifiers(
|
||||||
source: string,
|
source: string,
|
||||||
fnName = '',
|
|
||||||
isFunctional = false,
|
isFunctional = false,
|
||||||
isTS = false,
|
isTS = false,
|
||||||
babelOptions: ParserOptions = {},
|
babelOptions: ParserOptions = {},
|
||||||
bindings?: BindingMetadata
|
bindings?: BindingMetadata
|
||||||
) {
|
) {
|
||||||
source = `function ${fnName}(${isFunctional ? `_c,_vm` : ``}){${source}\n}`
|
|
||||||
|
|
||||||
const s = new MagicString(source)
|
const s = new MagicString(source)
|
||||||
|
|
||||||
const plugins: ParserPlugin[] = [
|
const plugins: ParserPlugin[] = [
|
||||||
|
189
packages/compiler-sfc/test/__snapshots__/cssVars.spec.ts.snap
Normal file
189
packages/compiler-sfc/test/__snapshots__/cssVars.spec.ts.snap
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// Vitest Snapshot v1
|
||||||
|
|
||||||
|
exports[`CSS vars injection > codegen > <script> w/ default export 1`] = `
|
||||||
|
"const __default__ = { setup() {} }
|
||||||
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
const __injectCSSVars__ = () => {
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-color\\": (_vm.color)
|
||||||
|
}))}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
|
||||||
|
export default __default__"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > codegen > <script> w/ default export in strings/comments 1`] = `
|
||||||
|
"
|
||||||
|
// export default {}
|
||||||
|
const __default__ = {}
|
||||||
|
|
||||||
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
const __injectCSSVars__ = () => {
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-color\\": (_vm.color)
|
||||||
|
}))}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
|
||||||
|
export default __default__"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > codegen > <script> w/ no default export 1`] = `
|
||||||
|
"const a = 1
|
||||||
|
const __default__ = {}
|
||||||
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
const __injectCSSVars__ = () => {
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-color\\": (_vm.color)
|
||||||
|
}))}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
|
||||||
|
export default __default__"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > codegen > should ignore comments 1`] = `
|
||||||
|
"import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-width\\": (_setup.width)
|
||||||
|
}))
|
||||||
|
const color = 'red';const width = 100
|
||||||
|
return { color, width }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > codegen > should work with w/ complex expression 1`] = `
|
||||||
|
"import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-foo\\": (_setup.foo),
|
||||||
|
\\"xxxxxxxx-foo____px_\\": (_setup.foo + 'px'),
|
||||||
|
\\"xxxxxxxx-_a___b____2____px_\\": ((_setup.a + _setup.b) / 2 + 'px'),
|
||||||
|
\\"xxxxxxxx-__a___b______2___a_\\": (((_setup.a + _setup.b)) / (2 * _setup.a))
|
||||||
|
}))
|
||||||
|
|
||||||
|
let a = 100
|
||||||
|
let b = 200
|
||||||
|
let foo = 300
|
||||||
|
|
||||||
|
return { a, b, foo }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > codegen > w/ <script setup> 1`] = `
|
||||||
|
"import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-color\\": (_setup.color)
|
||||||
|
}))
|
||||||
|
const color = 'red'
|
||||||
|
return { color }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > codegen > w/ <script setup> using the same var multiple times 1`] = `
|
||||||
|
"import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-color\\": (_setup.color)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const color = 'red'
|
||||||
|
|
||||||
|
return { color }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > generating correct code for nested paths 1`] = `
|
||||||
|
"const a = 1
|
||||||
|
const __default__ = {}
|
||||||
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
const __injectCSSVars__ = () => {
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-color\\": (_vm.color),
|
||||||
|
\\"xxxxxxxx-font_size\\": (_vm.font.size)
|
||||||
|
}))}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
|
||||||
|
export default __default__"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > w/ <script setup> binding analysis 1`] = `
|
||||||
|
"import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
foo: String
|
||||||
|
},
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-color\\": (_setup.color),
|
||||||
|
\\"xxxxxxxx-size\\": (_setup.size),
|
||||||
|
\\"xxxxxxxx-foo\\": (_vm.foo)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const color = 'red'
|
||||||
|
const size = ref('10px')
|
||||||
|
|
||||||
|
|
||||||
|
return { color, size, ref }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CSS vars injection > w/ normal <script> binding analysis 1`] = `
|
||||||
|
"
|
||||||
|
const __default__ = {
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
size: ref('100px')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
const __injectCSSVars__ = () => {
|
||||||
|
_useCssVars((_vm, _setup) => ({
|
||||||
|
\\"xxxxxxxx-size\\": (_vm.size)
|
||||||
|
}))}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
|
||||||
|
export default __default__"
|
||||||
|
`;
|
@ -1,33 +1,5 @@
|
|||||||
import { BindingTypes } from '../src/types'
|
import { BindingTypes } from '../src/types'
|
||||||
import { parse, ParseOptions } from '../src/parse'
|
import { compile, assertCode } from './util'
|
||||||
import { parse as babelParse } from '@babel/parser'
|
|
||||||
import { compileScript, SFCScriptCompileOptions } from '../src/compileScript'
|
|
||||||
|
|
||||||
function compile(
|
|
||||||
source: string,
|
|
||||||
options?: Partial<SFCScriptCompileOptions>,
|
|
||||||
parseOptions?: Partial<ParseOptions>
|
|
||||||
) {
|
|
||||||
const sfc = parse({
|
|
||||||
...parseOptions,
|
|
||||||
source
|
|
||||||
})
|
|
||||||
return compileScript(sfc, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertCode(code: string) {
|
|
||||||
// parse the generated code to make sure it is valid
|
|
||||||
try {
|
|
||||||
babelParse(code, {
|
|
||||||
sourceType: 'module',
|
|
||||||
plugins: ['typescript']
|
|
||||||
})
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log(code)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
expect(code).toMatchSnapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('SFC compile <script setup>', () => {
|
describe('SFC compile <script setup>', () => {
|
||||||
test('should expose top level declarations', () => {
|
test('should expose top level declarations', () => {
|
||||||
|
247
packages/compiler-sfc/test/cssVars.spec.ts
Normal file
247
packages/compiler-sfc/test/cssVars.spec.ts
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
import { compileStyle, parse } from '../src'
|
||||||
|
import { mockId, compile, assertCode } from './util'
|
||||||
|
|
||||||
|
describe('CSS vars injection', () => {
|
||||||
|
test('generating correct code for nested paths', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script>const a = 1</script>\n` +
|
||||||
|
`<style>div{
|
||||||
|
color: v-bind(color);
|
||||||
|
font-size: v-bind('font.size');
|
||||||
|
}</style>`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`_useCssVars((_vm, _setup) => ({
|
||||||
|
"${mockId}-color": (_vm.color),
|
||||||
|
"${mockId}-font_size": (_vm.font.size)
|
||||||
|
})`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('w/ normal <script> binding analysis', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script>
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
size: ref('100px')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>\n` +
|
||||||
|
`<style>
|
||||||
|
div {
|
||||||
|
font-size: v-bind(size);
|
||||||
|
}
|
||||||
|
</style>`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`_useCssVars((_vm, _setup) => ({
|
||||||
|
"${mockId}-size": (_vm.size)
|
||||||
|
})`)
|
||||||
|
expect(content).toMatch(`import { useCssVars as _useCssVars } from 'vue'`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('w/ <script setup> binding analysis', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script setup>
|
||||||
|
import { defineProps, ref } from 'vue'
|
||||||
|
const color = 'red'
|
||||||
|
const size = ref('10px')
|
||||||
|
defineProps({
|
||||||
|
foo: String
|
||||||
|
})
|
||||||
|
</script>\n` +
|
||||||
|
`<style>
|
||||||
|
div {
|
||||||
|
color: v-bind(color);
|
||||||
|
font-size: v-bind(size);
|
||||||
|
border: v-bind(foo);
|
||||||
|
}
|
||||||
|
</style>`
|
||||||
|
)
|
||||||
|
// should handle:
|
||||||
|
// 1. local const bindings
|
||||||
|
// 2. local potential ref bindings
|
||||||
|
// 3. props bindings (analyzed)
|
||||||
|
expect(content).toMatch(`_useCssVars((_vm, _setup) => ({
|
||||||
|
"${mockId}-color": (_setup.color),
|
||||||
|
"${mockId}-size": (_setup.size),
|
||||||
|
"${mockId}-foo": (_vm.foo)
|
||||||
|
})`)
|
||||||
|
expect(content).toMatch(`import { useCssVars as _useCssVars } from 'vue'`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should rewrite CSS vars in compileStyle', () => {
|
||||||
|
const { code } = compileStyle({
|
||||||
|
source: `.foo {
|
||||||
|
color: v-bind(color);
|
||||||
|
font-size: v-bind('font.size');
|
||||||
|
}`,
|
||||||
|
filename: 'test.css',
|
||||||
|
id: 'data-v-test'
|
||||||
|
})
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
".foo[data-v-test] {
|
||||||
|
color: var(--test-color);
|
||||||
|
font-size: var(--test-font_size);
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prod mode', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script>const a = 1</script>\n` +
|
||||||
|
`<style>div{
|
||||||
|
color: v-bind(color);
|
||||||
|
font-size: v-bind('font.size');
|
||||||
|
}</style>`,
|
||||||
|
{ isProd: true }
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`_useCssVars((_vm, _setup) => ({
|
||||||
|
"4003f1a6": (_vm.color),
|
||||||
|
"41b6490a": (_vm.font.size)
|
||||||
|
}))}`)
|
||||||
|
|
||||||
|
const { code } = compileStyle({
|
||||||
|
source: `.foo {
|
||||||
|
color: v-bind(color);
|
||||||
|
font-size: v-bind('font.size');
|
||||||
|
}`,
|
||||||
|
filename: 'test.css',
|
||||||
|
id: mockId,
|
||||||
|
isProd: true
|
||||||
|
})
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
".foo[xxxxxxxx] {
|
||||||
|
color: var(--4003f1a6);
|
||||||
|
font-size: var(--41b6490a);
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('codegen', () => {
|
||||||
|
test('<script> w/ no default export', () => {
|
||||||
|
assertCode(
|
||||||
|
compile(
|
||||||
|
`<script>const a = 1</script>\n` +
|
||||||
|
`<style>div{ color: v-bind(color); }</style>`
|
||||||
|
).content
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('<script> w/ default export', () => {
|
||||||
|
assertCode(
|
||||||
|
compile(
|
||||||
|
`<script>export default { setup() {} }</script>\n` +
|
||||||
|
`<style>div{ color: v-bind(color); }</style>`
|
||||||
|
).content
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('<script> w/ default export in strings/comments', () => {
|
||||||
|
assertCode(
|
||||||
|
compile(
|
||||||
|
`<script>
|
||||||
|
// export default {}
|
||||||
|
export default {}
|
||||||
|
</script>\n` + `<style>div{ color: v-bind(color); }</style>`
|
||||||
|
).content
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('w/ <script setup>', () => {
|
||||||
|
assertCode(
|
||||||
|
compile(
|
||||||
|
`<script setup>const color = 'red'</script>\n` +
|
||||||
|
`<style>div{ color: v-bind(color); }</style>`
|
||||||
|
).content
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
//#4185
|
||||||
|
test('should ignore comments', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script setup>const color = 'red';const width = 100</script>\n` +
|
||||||
|
`<style>
|
||||||
|
/* comment **/
|
||||||
|
div{ /* color: v-bind(color); */ width:20; }
|
||||||
|
div{ width: v-bind(width); }
|
||||||
|
/* comment */
|
||||||
|
</style>`
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(content).not.toMatch(`"${mockId}-color": (_setup.color)`)
|
||||||
|
expect(content).toMatch(`"${mockId}-width": (_setup.width)`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('w/ <script setup> using the same var multiple times', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script setup>
|
||||||
|
const color = 'red'
|
||||||
|
</script>\n` +
|
||||||
|
`<style>
|
||||||
|
div {
|
||||||
|
color: v-bind(color);
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: v-bind(color);
|
||||||
|
}
|
||||||
|
</style>`
|
||||||
|
)
|
||||||
|
// color should only be injected once, even if it is twice in style
|
||||||
|
expect(content).toMatch(`_useCssVars((_vm, _setup) => ({
|
||||||
|
"${mockId}-color": (_setup.color)
|
||||||
|
})`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work with w/ complex expression', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`<script setup>
|
||||||
|
let a = 100
|
||||||
|
let b = 200
|
||||||
|
let foo = 300
|
||||||
|
</script>\n` +
|
||||||
|
`<style>
|
||||||
|
p{
|
||||||
|
width: calc(v-bind(foo) - 3px);
|
||||||
|
height: calc(v-bind('foo') - 3px);
|
||||||
|
top: calc(v-bind(foo + 'px') - 3px);
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
color: v-bind((a + b) / 2 + 'px' );
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
color: v-bind ((a + b) / 2 + 'px' );
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: v-bind(((a + b)) / (2 * a));
|
||||||
|
}
|
||||||
|
</style>`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`_useCssVars((_vm, _setup) => ({
|
||||||
|
"${mockId}-foo": (_setup.foo),
|
||||||
|
"${mockId}-foo____px_": (_setup.foo + 'px'),
|
||||||
|
"${mockId}-_a___b____2____px_": ((_setup.a + _setup.b) / 2 + 'px'),
|
||||||
|
"${mockId}-__a___b______2___a_": (((_setup.a + _setup.b)) / (2 * _setup.a))
|
||||||
|
})`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
// #6022
|
||||||
|
test('should be able to parse incomplete expressions', () => {
|
||||||
|
const { cssVars } = parse({
|
||||||
|
source: `<script setup>let xxx = 1</script>
|
||||||
|
<style scoped>
|
||||||
|
label {
|
||||||
|
font-weight: v-bind("count.toString(");
|
||||||
|
font-weight: v-bind(xxx);
|
||||||
|
}
|
||||||
|
</style>`
|
||||||
|
})
|
||||||
|
expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -3,6 +3,8 @@ import { compile } from 'web/entry-compiler'
|
|||||||
import { format } from 'prettier'
|
import { format } from 'prettier'
|
||||||
import { BindingTypes } from '../src/types'
|
import { BindingTypes } from '../src/types'
|
||||||
|
|
||||||
|
const toFn = (source: string) => `function render(){${source}\n}`
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
const { render } = compile(`<div id="app">
|
const { render } = compile(`<div id="app">
|
||||||
<div>{{ foo }}</div>
|
<div>{{ foo }}</div>
|
||||||
@ -12,7 +14,7 @@ it('should work', () => {
|
|||||||
</foo>
|
</foo>
|
||||||
</div>`)
|
</div>`)
|
||||||
|
|
||||||
const result = format(prefixIdentifiers(render, `render`), {
|
const result = format(prefixIdentifiers(toFn(render)), {
|
||||||
semi: false,
|
semi: false,
|
||||||
parser: 'babel'
|
parser: 'babel'
|
||||||
})
|
})
|
||||||
@ -59,7 +61,7 @@ it('setup bindings', () => {
|
|||||||
const { render } = compile(`<div @click="count++">{{ count }}</div>`)
|
const { render } = compile(`<div @click="count++">{{ count }}</div>`)
|
||||||
|
|
||||||
const result = format(
|
const result = format(
|
||||||
prefixIdentifiers(render, `render`, false, false, undefined, {
|
prefixIdentifiers(toFn(render), false, false, undefined, {
|
||||||
count: BindingTypes.SETUP_REF
|
count: BindingTypes.SETUP_REF
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
35
packages/compiler-sfc/test/util.ts
Normal file
35
packages/compiler-sfc/test/util.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
parse,
|
||||||
|
compileScript,
|
||||||
|
type SFCParseOptions,
|
||||||
|
type SFCScriptCompileOptions
|
||||||
|
} from '../src'
|
||||||
|
import { parse as babelParse } from '@babel/parser'
|
||||||
|
|
||||||
|
export const mockId = 'xxxxxxxx'
|
||||||
|
|
||||||
|
export function compile(
|
||||||
|
source: string,
|
||||||
|
options?: Partial<SFCScriptCompileOptions>,
|
||||||
|
parseOptions?: Partial<SFCParseOptions>
|
||||||
|
) {
|
||||||
|
const sfc = parse({
|
||||||
|
...parseOptions,
|
||||||
|
source
|
||||||
|
})
|
||||||
|
return compileScript(sfc, { id: mockId, ...options })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertCode(code: string) {
|
||||||
|
// parse the generated code to make sure it is valid
|
||||||
|
try {
|
||||||
|
babelParse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
plugins: ['typescript']
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(code)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
}
|
@ -86,13 +86,17 @@ export function proxyWithRefUnwrap(
|
|||||||
source: Record<string, any>,
|
source: Record<string, any>,
|
||||||
key: string
|
key: string
|
||||||
) {
|
) {
|
||||||
let raw = source[key]
|
|
||||||
Object.defineProperty(target, key, {
|
Object.defineProperty(target, key, {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: () => (isRef(raw) ? raw.value : raw),
|
get: () => {
|
||||||
set: newVal =>
|
const raw = source[key]
|
||||||
isRef(raw) ? (raw.value = newVal) : (raw = source[key] = newVal)
|
return isRef(raw) ? raw.value : raw
|
||||||
|
},
|
||||||
|
set: newVal => {
|
||||||
|
const raw = source[key]
|
||||||
|
isRef(raw) ? (raw.value = newVal) : (source[key] = newVal)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,7 @@ export { nextTick } from 'core/util/next-tick'
|
|||||||
export { set, del } from 'core/observer'
|
export { set, del } from 'core/observer'
|
||||||
|
|
||||||
export { useCssModule } from './sfc-helpers/useCssModule'
|
export { useCssModule } from './sfc-helpers/useCssModule'
|
||||||
|
export { useCssVars } from './sfc-helpers/useCssVars'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal type is manually declared in <root>/types/v3-define-component.d.ts
|
* @internal type is manually declared in <root>/types/v3-define-component.d.ts
|
||||||
|
34
src/v3/sfc-helpers/useCssVars.ts
Normal file
34
src/v3/sfc-helpers/useCssVars.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { watchPostEffect } from '../'
|
||||||
|
import { inBrowser, warn } from 'core/util'
|
||||||
|
import { currentInstance } from '../currentInstance'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime helper for SFC's CSS variable injection feature.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function useCssVars(
|
||||||
|
getter: (
|
||||||
|
vm: Record<string, any>,
|
||||||
|
setupProxy: Record<string, any>
|
||||||
|
) => Record<string, string>
|
||||||
|
) {
|
||||||
|
if (!inBrowser && !__TEST__) return
|
||||||
|
|
||||||
|
const instance = currentInstance
|
||||||
|
if (!instance) {
|
||||||
|
__DEV__ &&
|
||||||
|
warn(`useCssVars is called without current active component instance.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
watchPostEffect(() => {
|
||||||
|
const el = instance.$el
|
||||||
|
const vars = getter(instance, instance._setupProxy!)
|
||||||
|
if (el && el.nodeType === 1) {
|
||||||
|
const style = (el as HTMLElement).style
|
||||||
|
for (const key in vars) {
|
||||||
|
style.setProperty(`--${key}`, vars[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
48
test/unit/features/v3/useCssVars.spec.ts
Normal file
48
test/unit/features/v3/useCssVars.spec.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import { useCssVars, h, reactive, nextTick } from 'v3'
|
||||||
|
|
||||||
|
describe('useCssVars', () => {
|
||||||
|
async function assertCssVars(getApp: (state: any) => any) {
|
||||||
|
const state = reactive({ color: 'red' })
|
||||||
|
const App = getApp(state)
|
||||||
|
const vm = new Vue(App).$mount()
|
||||||
|
await nextTick()
|
||||||
|
expect((vm.$el as HTMLElement).style.getPropertyValue(`--color`)).toBe(
|
||||||
|
`red`
|
||||||
|
)
|
||||||
|
|
||||||
|
state.color = 'green'
|
||||||
|
await nextTick()
|
||||||
|
expect((vm.$el as HTMLElement).style.getPropertyValue(`--color`)).toBe(
|
||||||
|
`green`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test('basic', async () => {
|
||||||
|
await assertCssVars(state => ({
|
||||||
|
setup() {
|
||||||
|
// test receiving render context
|
||||||
|
useCssVars(vm => ({
|
||||||
|
color: vm.color
|
||||||
|
}))
|
||||||
|
return state
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return h('div')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('on HOCs', async () => {
|
||||||
|
const Child = {
|
||||||
|
render: () => h('div')
|
||||||
|
}
|
||||||
|
|
||||||
|
await assertCssVars(state => ({
|
||||||
|
setup() {
|
||||||
|
useCssVars(() => state)
|
||||||
|
return () => h(Child)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
@ -13,7 +13,8 @@ export default defineConfig({
|
|||||||
shared: resolve('src/shared'),
|
shared: resolve('src/shared'),
|
||||||
web: resolve('src/platforms/web'),
|
web: resolve('src/platforms/web'),
|
||||||
v3: resolve('src/v3'),
|
v3: resolve('src/v3'),
|
||||||
vue: resolve('src/platforms/web/entry-runtime-with-compiler')
|
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
|
||||||
|
types: resolve('src/types')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
|
Loading…
Reference in New Issue
Block a user