| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- import {
- processExpression,
- createTransformContext,
- createSimpleExpression,
- createRoot,
- NodeTypes,
- SimpleExpressionNode,
- BindingMetadata
- } from '@vue/compiler-dom'
- import { SFCDescriptor } from './parse'
- 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[],
- 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
- }
- 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)
- }
- }
- })
- return vars
- }
- // 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
- if (cssVarRE.test(decl.value)) {
- decl.value = decl.value.replace(cssVarRE, (_, $1) => {
- return `var(--${genVarName(id, normalizeExpression($1), isProd)})`
- })
- }
- }
- }
- }
- cssVarsPlugin.postcss = true
- export function genCssVarsCode(
- vars: string[],
- bindings: BindingMetadata,
- id: string,
- isProd: boolean
- ) {
- const varsExp = genCssVarsFromList(vars, id, isProd)
- const exp = createSimpleExpression(varsExp, false)
- const context = createTransformContext(createRoot([]), {
- prefixIdentifiers: true,
- inline: true,
- bindingMetadata: bindings.__isScriptSetup === false ? undefined : bindings
- })
- const transformed = processExpression(exp, context)
- const transformedString =
- transformed.type === NodeTypes.SIMPLE_EXPRESSION
- ? transformed.content
- : transformed.children
- .map(c => {
- return typeof c === 'string'
- ? c
- : (c as SimpleExpressionNode).content
- })
- .join('')
- return `_${CSS_VARS_HELPER}(_ctx => (${transformedString}))`
- }
- // <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`
- )
- }
|