|
|
@ -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) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|