Skip to content

Commit

Permalink
fix(compiler-sfc): improve css v-bind parsing
Browse files Browse the repository at this point in the history
fix #6022
  • Loading branch information
yyx990803 committed Jun 6, 2022
1 parent 9734b31 commit e60244b
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 11 deletions.
18 changes: 17 additions & 1 deletion packages/compiler-sfc/__tests__/cssVars.spec.ts
@@ -1,4 +1,4 @@
import { compileStyle } from '../src'
import { compileStyle, parse } from '../src'
import { mockId, compileSFCScript, assertCode } from './utils'

describe('CSS vars injection', () => {
Expand Down Expand Up @@ -231,5 +231,21 @@ describe('CSS vars injection', () => {
})`)
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`])
})
})
})
81 changes: 71 additions & 10 deletions packages/compiler-sfc/src/cssVars.ts
Expand Up @@ -12,8 +12,6 @@ import { PluginCreator } from 'postcss'
import hash from 'hash-sum'

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(
vars: string[],
Expand Down Expand Up @@ -47,22 +45,71 @@ function normalizeExpression(exp: string) {
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 = cssVarRE.exec(content))) {
const variable = normalizeExpression(match[1])
if (!vars.includes(variable)) {
vars.push(variable)
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
Expand All @@ -75,10 +122,24 @@ export const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => {
postcssPlugin: 'vue-sfc-vars',
Declaration(decl) {
// rewrite CSS variables
if (cssVarRE.test(decl.value)) {
decl.value = decl.value.replace(cssVarRE, (_, $1) => {
return `var(--${genVarName(id, normalizeExpression($1), isProd)})`
})
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)
}
}
}
Expand Down

0 comments on commit e60244b

Please sign in to comment.