|
|
@@ -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[],
|
|
|
@@ -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
|
|
|
@@ -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)
|
|
|
}
|
|
|
}
|
|
|
}
|