cssVars.spec.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { compileStyle, parse } from '../src'
  2. import { mockId, compileSFCScript, assertCode } from './utils'
  3. describe('CSS vars injection', () => {
  4. test('generating correct code for nested paths', () => {
  5. const { content } = compileSFCScript(
  6. `<script>const a = 1</script>\n` +
  7. `<style>div{
  8. color: v-bind(color);
  9. font-size: v-bind('font.size');
  10. }</style>`
  11. )
  12. expect(content).toMatch(`_useCssVars(_ctx => ({
  13. "${mockId}-color": (_ctx.color),
  14. "${mockId}-font_size": (_ctx.font.size)
  15. })`)
  16. assertCode(content)
  17. })
  18. test('w/ normal <script> binding analysis', () => {
  19. const { content } = compileSFCScript(
  20. `<script>
  21. export default {
  22. setup() {
  23. return {
  24. size: ref('100px')
  25. }
  26. }
  27. }
  28. </script>\n` +
  29. `<style>
  30. div {
  31. font-size: v-bind(size);
  32. }
  33. </style>`
  34. )
  35. expect(content).toMatch(`_useCssVars(_ctx => ({
  36. "${mockId}-size": (_ctx.size)
  37. })`)
  38. expect(content).toMatch(`import { useCssVars as _useCssVars } from 'vue'`)
  39. assertCode(content)
  40. })
  41. test('w/ <script setup> binding analysis', () => {
  42. const { content } = compileSFCScript(
  43. `<script setup>
  44. import { defineProps, ref } from 'vue'
  45. const color = 'red'
  46. const size = ref('10px')
  47. defineProps({
  48. foo: String
  49. })
  50. </script>\n` +
  51. `<style>
  52. div {
  53. color: v-bind(color);
  54. font-size: v-bind(size);
  55. border: v-bind(foo);
  56. }
  57. </style>`
  58. )
  59. // should handle:
  60. // 1. local const bindings
  61. // 2. local potential ref bindings
  62. // 3. props bindings (analyzed)
  63. expect(content).toMatch(`_useCssVars(_ctx => ({
  64. "${mockId}-color": (color),
  65. "${mockId}-size": (size.value),
  66. "${mockId}-foo": (__props.foo)
  67. })`)
  68. expect(content).toMatch(
  69. `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`
  70. )
  71. assertCode(content)
  72. })
  73. test('should rewrite CSS vars in compileStyle', () => {
  74. const { code } = compileStyle({
  75. source: `.foo {
  76. color: v-bind(color);
  77. font-size: v-bind('font.size');
  78. }`,
  79. filename: 'test.css',
  80. id: 'data-v-test'
  81. })
  82. expect(code).toMatchInlineSnapshot(`
  83. ".foo {
  84. color: var(--test-color);
  85. font-size: var(--test-font_size);
  86. }"
  87. `)
  88. })
  89. test('prod mode', () => {
  90. const { content } = compileSFCScript(
  91. `<script>const a = 1</script>\n` +
  92. `<style>div{
  93. color: v-bind(color);
  94. font-size: v-bind('font.size');
  95. }</style>`,
  96. { isProd: true }
  97. )
  98. expect(content).toMatch(`_useCssVars(_ctx => ({
  99. "4003f1a6": (_ctx.color),
  100. "41b6490a": (_ctx.font.size)
  101. }))}`)
  102. const { code } = compileStyle({
  103. source: `.foo {
  104. color: v-bind(color);
  105. font-size: v-bind('font.size');
  106. }`,
  107. filename: 'test.css',
  108. id: mockId,
  109. isProd: true
  110. })
  111. expect(code).toMatchInlineSnapshot(`
  112. ".foo {
  113. color: var(--4003f1a6);
  114. font-size: var(--41b6490a);
  115. }"
  116. `)
  117. })
  118. describe('codegen', () => {
  119. test('<script> w/ no default export', () => {
  120. assertCode(
  121. compileSFCScript(
  122. `<script>const a = 1</script>\n` +
  123. `<style>div{ color: v-bind(color); }</style>`
  124. ).content
  125. )
  126. })
  127. test('<script> w/ default export', () => {
  128. assertCode(
  129. compileSFCScript(
  130. `<script>export default { setup() {} }</script>\n` +
  131. `<style>div{ color: v-bind(color); }</style>`
  132. ).content
  133. )
  134. })
  135. test('<script> w/ default export in strings/comments', () => {
  136. assertCode(
  137. compileSFCScript(
  138. `<script>
  139. // export default {}
  140. export default {}
  141. </script>\n` + `<style>div{ color: v-bind(color); }</style>`
  142. ).content
  143. )
  144. })
  145. test('w/ <script setup>', () => {
  146. assertCode(
  147. compileSFCScript(
  148. `<script setup>const color = 'red'</script>\n` +
  149. `<style>div{ color: v-bind(color); }</style>`
  150. ).content
  151. )
  152. })
  153. //#4185
  154. test('should ignore comments', () => {
  155. const { content } = compileSFCScript(
  156. `<script setup>const color = 'red';const width = 100</script>\n` +
  157. `<style>
  158. /* comment **/
  159. div{ /* color: v-bind(color); */ width:20; }
  160. div{ width: v-bind(width); }
  161. /* comment */
  162. </style>`
  163. )
  164. expect(content).not.toMatch(`"${mockId}-color": (color)`)
  165. expect(content).toMatch(`"${mockId}-width": (width)`)
  166. assertCode(content)
  167. })
  168. test('w/ <script setup> using the same var multiple times', () => {
  169. const { content } = compileSFCScript(
  170. `<script setup>
  171. const color = 'red'
  172. </script>\n` +
  173. `<style>
  174. div {
  175. color: v-bind(color);
  176. }
  177. p {
  178. color: v-bind(color);
  179. }
  180. </style>`
  181. )
  182. // color should only be injected once, even if it is twice in style
  183. expect(content).toMatch(`_useCssVars(_ctx => ({
  184. "${mockId}-color": (color)
  185. })`)
  186. assertCode(content)
  187. })
  188. test('should work with w/ complex expression', () => {
  189. const { content } = compileSFCScript(
  190. `<script setup>
  191. let a = 100
  192. let b = 200
  193. let foo = 300
  194. </script>\n` +
  195. `<style>
  196. p{
  197. width: calc(v-bind(foo) - 3px);
  198. height: calc(v-bind('foo') - 3px);
  199. top: calc(v-bind(foo + 'px') - 3px);
  200. }
  201. div {
  202. color: v-bind((a + b) / 2 + 'px' );
  203. }
  204. div {
  205. color: v-bind ((a + b) / 2 + 'px' );
  206. }
  207. p {
  208. color: v-bind(((a + b)) / (2 * a));
  209. }
  210. </style>`
  211. )
  212. expect(content).toMatch(`_useCssVars(_ctx => ({
  213. "${mockId}-foo": (_unref(foo)),
  214. "${mockId}-foo____px_": (_unref(foo) + 'px'),
  215. "${mockId}-_a___b____2____px_": ((_unref(a) + _unref(b)) / 2 + 'px'),
  216. "${mockId}-__a___b______2___a_": (((_unref(a) + _unref(b))) / (2 * _unref(a)))
  217. })`)
  218. assertCode(content)
  219. })
  220. // #6022
  221. test('should be able to parse incomplete expressions', () => {
  222. const {
  223. descriptor: { cssVars }
  224. } = parse(
  225. `<script setup>let xxx = 1</script>
  226. <style scoped>
  227. label {
  228. font-weight: v-bind("count.toString(");
  229. font-weight: v-bind(xxx);
  230. }
  231. </style>`
  232. )
  233. expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
  234. })
  235. })
  236. })