cssVars.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 { rewriteDefault } from './rewriteDefault'
  12. import { ParserPlugin } from '@babel/parser'
  13. import postcss, { Root } from 'postcss'
  14. import hash from 'hash-sum'
  15. export const CSS_VARS_HELPER = `useCssVars`
  16. export const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g
  17. /**
  18. * Given an SFC descriptor, generate the CSS variables object string that can be
  19. * passed to `compileTemplate` as `compilerOptions.ssrCssVars`.
  20. * @public
  21. */
  22. export function generateCssVars(
  23. sfc: SFCDescriptor,
  24. id: string,
  25. isProd: boolean
  26. ): string {
  27. return sfc.cssVars.length ? genCssVarsFromList(sfc.cssVars, id, isProd) : ''
  28. }
  29. function genCssVarsFromList(
  30. vars: string[],
  31. id: string,
  32. isProd: boolean
  33. ): string {
  34. return `{\n ${vars
  35. .map(v => `"${genVarName(id, v, isProd)}": (${v})`)
  36. .join(',\n ')}\n}`
  37. }
  38. function genVarName(id: string, raw: string, isProd: boolean): string {
  39. if (isProd) {
  40. return hash(id + raw)
  41. } else {
  42. return `${id}-${raw.replace(/([^\w-])/g, '_')}`
  43. }
  44. }
  45. export function parseCssVars(sfc: SFCDescriptor): string[] {
  46. const vars: string[] = []
  47. sfc.styles.forEach(style => {
  48. let match
  49. while ((match = cssVarRE.exec(style.content))) {
  50. vars.push(match[1] || match[2] || match[3])
  51. }
  52. })
  53. return vars
  54. }
  55. // for compileStyle
  56. export interface CssVarsPluginOptions {
  57. id: string
  58. isProd: boolean
  59. }
  60. export const cssVarsPlugin = postcss.plugin<CssVarsPluginOptions>(
  61. 'vue-scoped',
  62. opts => (root: Root) => {
  63. const { id, isProd } = opts!
  64. const shortId = id.replace(/^data-v-/, '')
  65. root.walkDecls(decl => {
  66. // rewrite CSS variables
  67. if (cssVarRE.test(decl.value)) {
  68. decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => {
  69. return `var(--${genVarName(shortId, $1 || $2 || $3, isProd)})`
  70. })
  71. }
  72. })
  73. }
  74. )
  75. export function genCssVarsCode(
  76. vars: string[],
  77. bindings: BindingMetadata,
  78. id: string,
  79. isProd: boolean
  80. ) {
  81. const varsExp = genCssVarsFromList(vars, id, isProd)
  82. const exp = createSimpleExpression(varsExp, false)
  83. const context = createTransformContext(createRoot([]), {
  84. prefixIdentifiers: true,
  85. inline: true,
  86. bindingMetadata: bindings
  87. })
  88. const transformed = processExpression(exp, context)
  89. const transformedString =
  90. transformed.type === NodeTypes.SIMPLE_EXPRESSION
  91. ? transformed.content
  92. : transformed.children
  93. .map(c => {
  94. return typeof c === 'string'
  95. ? c
  96. : (c as SimpleExpressionNode).content
  97. })
  98. .join('')
  99. return `_${CSS_VARS_HELPER}(_ctx => (${transformedString}))`
  100. }
  101. // <script setup> already gets the calls injected as part of the transform
  102. // this is only for single normal <script>
  103. export function injectCssVarsCalls(
  104. sfc: SFCDescriptor,
  105. cssVars: string[],
  106. bindings: BindingMetadata,
  107. id: string,
  108. isProd: boolean,
  109. parserPlugins: ParserPlugin[]
  110. ): string {
  111. const script = rewriteDefault(
  112. sfc.script!.content,
  113. `__default__`,
  114. parserPlugins
  115. )
  116. return (
  117. script +
  118. `\nimport { ${CSS_VARS_HELPER} as _${CSS_VARS_HELPER} } from 'vue'\n` +
  119. `const __injectCSSVars__ = () => {\n${genCssVarsCode(
  120. cssVars,
  121. bindings,
  122. id,
  123. isProd
  124. )}}\n` +
  125. `const __setup__ = __default__.setup\n` +
  126. `__default__.setup = __setup__\n` +
  127. ` ? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }\n` +
  128. ` : __injectCSSVars__\n` +
  129. `export default __default__`
  130. )
  131. }