cssVars.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import {
  2. processExpression,
  3. createTransformContext,
  4. createSimpleExpression,
  5. createRoot,
  6. NodeTypes,
  7. SimpleExpressionNode,
  8. BindingMetadata
  9. } from '@vue/compiler-dom'
  10. import { SFCDescriptor } from './parse'
  11. import { PluginCreator } from 'postcss'
  12. import hash from 'hash-sum'
  13. export const CSS_VARS_HELPER = `useCssVars`
  14. export const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g
  15. export function genCssVarsFromList(
  16. vars: string[],
  17. id: string,
  18. isProd: boolean
  19. ): string {
  20. return `{\n ${vars
  21. .map(key => `"${genVarName(id, key, isProd)}": (${key})`)
  22. .join(',\n ')}\n}`
  23. }
  24. function genVarName(id: string, raw: string, isProd: boolean): string {
  25. if (isProd) {
  26. return hash(id + raw)
  27. } else {
  28. return `${id}-${raw.replace(/([^\w-])/g, '_')}`
  29. }
  30. }
  31. export function parseCssVars(sfc: SFCDescriptor): string[] {
  32. const vars: string[] = []
  33. sfc.styles.forEach(style => {
  34. let match
  35. while ((match = cssVarRE.exec(style.content))) {
  36. vars.push(match[1] || match[2] || match[3])
  37. }
  38. })
  39. return vars
  40. }
  41. // for compileStyle
  42. export interface CssVarsPluginOptions {
  43. id: string
  44. isProd: boolean
  45. }
  46. export const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => {
  47. const { id, isProd } = opts!
  48. return {
  49. postcssPlugin: 'vue-sfc-vars',
  50. Declaration(decl) {
  51. // rewrite CSS variables
  52. if (cssVarRE.test(decl.value)) {
  53. decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => {
  54. return `var(--${genVarName(id, $1 || $2 || $3, isProd)})`
  55. })
  56. }
  57. }
  58. }
  59. }
  60. cssVarsPlugin.postcss = true
  61. export function genCssVarsCode(
  62. vars: string[],
  63. bindings: BindingMetadata,
  64. id: string,
  65. isProd: boolean
  66. ) {
  67. const varsExp = genCssVarsFromList(vars, id, isProd)
  68. const exp = createSimpleExpression(varsExp, false)
  69. const context = createTransformContext(createRoot([]), {
  70. prefixIdentifiers: true,
  71. inline: true,
  72. bindingMetadata: bindings.__isScriptSetup === false ? undefined : bindings
  73. })
  74. const transformed = processExpression(exp, context)
  75. const transformedString =
  76. transformed.type === NodeTypes.SIMPLE_EXPRESSION
  77. ? transformed.content
  78. : transformed.children
  79. .map(c => {
  80. return typeof c === 'string'
  81. ? c
  82. : (c as SimpleExpressionNode).content
  83. })
  84. .join('')
  85. return `_${CSS_VARS_HELPER}(_ctx => (${transformedString}))`
  86. }
  87. // <script setup> already gets the calls injected as part of the transform
  88. // this is only for single normal <script>
  89. export function genNormalScriptCssVarsCode(
  90. cssVars: string[],
  91. bindings: BindingMetadata,
  92. id: string,
  93. isProd: boolean
  94. ): string {
  95. return (
  96. `\nimport { ${CSS_VARS_HELPER} as _${CSS_VARS_HELPER} } from 'vue'\n` +
  97. `const __injectCSSVars__ = () => {\n${genCssVarsCode(
  98. cssVars,
  99. bindings,
  100. id,
  101. isProd
  102. )}}\n` +
  103. `const __setup__ = __default__.setup\n` +
  104. `__default__.setup = __setup__\n` +
  105. ` ? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }\n` +
  106. ` : __injectCSSVars__\n`
  107. )
  108. }