parent
9734b31c31
commit
e60244bcdf
@ -1,4 +1,4 @@
|
|||||||
import { compileStyle } from '../src'
|
import { compileStyle, parse } from '../src'
|
||||||
import { mockId, compileSFCScript, assertCode } from './utils'
|
import { mockId, compileSFCScript, assertCode } from './utils'
|
||||||
|
|
||||||
describe('CSS vars injection', () => {
|
describe('CSS vars injection', () => {
|
||||||
@ -231,5 +231,21 @@ describe('CSS vars injection', () => {
|
|||||||
})`)
|
})`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #6022
|
||||||
|
test('should be able to parse incomplete expressions', () => {
|
||||||
|
const {
|
||||||
|
descriptor: { cssVars }
|
||||||
|
} = parse(
|
||||||
|
`<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`])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -12,8 +12,6 @@ import { PluginCreator } from 'postcss'
|
|||||||
import hash from 'hash-sum'
|
import hash from 'hash-sum'
|
||||||
|
|
||||||
export const CSS_VARS_HELPER = `useCssVars`
|
export const CSS_VARS_HELPER = `useCssVars`
|
||||||
// match v-bind() with max 2-levels of nested parens.
|
|
||||||
const cssVarRE = /v-bind\s*\(((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*)\)/g
|
|
||||||
|
|
||||||
export function genCssVarsFromList(
|
export function genCssVarsFromList(
|
||||||
vars: string[],
|
vars: string[],
|
||||||
@ -47,22 +45,71 @@ function normalizeExpression(exp: string) {
|
|||||||
return exp
|
return exp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const vBindRE = /v-bind\s*\(/g
|
||||||
|
|
||||||
export function parseCssVars(sfc: SFCDescriptor): string[] {
|
export function parseCssVars(sfc: SFCDescriptor): string[] {
|
||||||
const vars: string[] = []
|
const vars: string[] = []
|
||||||
sfc.styles.forEach(style => {
|
sfc.styles.forEach(style => {
|
||||||
let match
|
let match
|
||||||
// ignore v-bind() in comments /* ... */
|
// ignore v-bind() in comments /* ... */
|
||||||
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
|
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
|
||||||
while ((match = cssVarRE.exec(content))) {
|
while ((match = vBindRE.exec(content))) {
|
||||||
const variable = normalizeExpression(match[1])
|
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)) {
|
if (!vars.includes(variable)) {
|
||||||
vars.push(variable)
|
vars.push(variable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return vars
|
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
|
// for compileStyle
|
||||||
export interface CssVarsPluginOptions {
|
export interface CssVarsPluginOptions {
|
||||||
id: string
|
id: string
|
||||||
@ -75,10 +122,24 @@ export const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => {
|
|||||||
postcssPlugin: 'vue-sfc-vars',
|
postcssPlugin: 'vue-sfc-vars',
|
||||||
Declaration(decl) {
|
Declaration(decl) {
|
||||||
// rewrite CSS variables
|
// rewrite CSS variables
|
||||||
if (cssVarRE.test(decl.value)) {
|
const value = decl.value
|
||||||
decl.value = decl.value.replace(cssVarRE, (_, $1) => {
|
if (vBindRE.test(value)) {
|
||||||
return `var(--${genVarName(id, normalizeExpression($1), isProd)})`
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user