123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- 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`
- )
- }
|