cssVars.spec.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import { compileStyle, parse } from '../src'
  2. import { assertCode, compileSFCScript, mockId } 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. font-weight: v-bind(_φ);
  79. font-size: v-bind(1-字号);
  80. font-family: v-bind(フォント);
  81. }`,
  82. filename: 'test.css',
  83. id: 'data-v-test',
  84. })
  85. expect(code).toMatchInlineSnapshot(`
  86. ".foo {
  87. color: var(--test-color);
  88. font-size: var(--test-font\\.size);
  89. font-weight: var(--test-_φ);
  90. font-size: var(--test-1-字号);
  91. font-family: var(--test-フォント);
  92. }"
  93. `)
  94. })
  95. test('prod mode', () => {
  96. const { content } = compileSFCScript(
  97. `<script>const a = 1</script>\n` +
  98. `<style>div{
  99. color: v-bind(color);
  100. font-size: v-bind('font.size');
  101. }</style>`,
  102. { isProd: true },
  103. )
  104. expect(content).toMatch(`_useCssVars(_ctx => ({
  105. "v4003f1a6": (_ctx.color),
  106. "v41b6490a": (_ctx.font.size)
  107. }))}`)
  108. const { code } = compileStyle({
  109. source: `.foo {
  110. color: v-bind(color);
  111. font-size: v-bind('font.size');
  112. }`,
  113. filename: 'test.css',
  114. id: mockId,
  115. isProd: true,
  116. })
  117. expect(code).toMatchInlineSnapshot(`
  118. ".foo {
  119. color: var(--v4003f1a6);
  120. font-size: var(--v41b6490a);
  121. }"
  122. `)
  123. })
  124. describe('codegen', () => {
  125. test('<script> w/ no default export', () => {
  126. assertCode(
  127. compileSFCScript(
  128. `<script>const a = 1</script>\n` +
  129. `<style>div{ color: v-bind(color); }</style>`,
  130. ).content,
  131. )
  132. })
  133. test('<script> w/ default export', () => {
  134. assertCode(
  135. compileSFCScript(
  136. `<script>export default { setup() {} }</script>\n` +
  137. `<style>div{ color: v-bind(color); }</style>`,
  138. ).content,
  139. )
  140. })
  141. test('<script> w/ default export in strings/comments', () => {
  142. assertCode(
  143. compileSFCScript(
  144. `<script>
  145. // export default {}
  146. export default {}
  147. </script>\n` + `<style>div{ color: v-bind(color); }</style>`,
  148. ).content,
  149. )
  150. })
  151. test('w/ <script setup>', () => {
  152. assertCode(
  153. compileSFCScript(
  154. `<script setup>const color = 'red'</script>\n` +
  155. `<style>div{ color: v-bind(color); }</style>`,
  156. ).content,
  157. )
  158. })
  159. //#4185
  160. test('should ignore comments', () => {
  161. const { content } = compileSFCScript(
  162. `<script setup>const color = 'red';const width = 100</script>\n` +
  163. `<style>
  164. /* comment **/
  165. div{ /* color: v-bind(color); */ width:20; }
  166. div{ width: v-bind(width); }
  167. /* comment */
  168. </style>`,
  169. )
  170. expect(content).not.toMatch(`"${mockId}-color": (color)`)
  171. expect(content).toMatch(`"${mockId}-width": (width)`)
  172. assertCode(content)
  173. })
  174. test('w/ <script setup> using the same var multiple times', () => {
  175. const { content } = compileSFCScript(
  176. `<script setup>
  177. const color = 'red'
  178. </script>\n` +
  179. `<style>
  180. div {
  181. color: v-bind(color);
  182. }
  183. p {
  184. color: v-bind(color);
  185. }
  186. </style>`,
  187. )
  188. // color should only be injected once, even if it is twice in style
  189. expect(content).toMatch(`_useCssVars(_ctx => ({
  190. "${mockId}-color": (color)
  191. })`)
  192. assertCode(content)
  193. })
  194. test('should work with w/ complex expression', () => {
  195. const { content } = compileSFCScript(
  196. `<script setup>
  197. let a = 100
  198. let b = 200
  199. let foo = 300
  200. </script>\n` +
  201. `<style>
  202. p{
  203. width: calc(v-bind(foo) - 3px);
  204. height: calc(v-bind('foo') - 3px);
  205. top: calc(v-bind(foo + 'px') - 3px);
  206. }
  207. div {
  208. color: v-bind((a + b) / 2 + 'px' );
  209. }
  210. div {
  211. color: v-bind ((a + b) / 2 + 'px' );
  212. }
  213. p {
  214. color: v-bind(((a + b)) / (2 * a));
  215. }
  216. </style>`,
  217. )
  218. expect(content).toMatch(`_useCssVars(_ctx => ({
  219. "${mockId}-foo": (_unref(foo)),
  220. "${mockId}-foo\\ \\+\\ \\'px\\'": (_unref(foo) + 'px'),
  221. "${mockId}-\\(a\\ \\+\\ b\\)\\ \\/\\ 2\\ \\+\\ \\'px\\'": ((_unref(a) + _unref(b)) / 2 + 'px'),
  222. "${mockId}-\\(\\(a\\ \\+\\ b\\)\\)\\ \\/\\ \\(2\\ \\*\\ a\\)": (((_unref(a) + _unref(b))) / (2 * _unref(a)))
  223. }))`)
  224. assertCode(content)
  225. })
  226. // #6022
  227. test('should be able to parse incomplete expressions', () => {
  228. const {
  229. descriptor: { cssVars },
  230. } = parse(
  231. `<script setup>let xxx = 1</script>
  232. <style scoped>
  233. label {
  234. font-weight: v-bind("count.toString(");
  235. font-weight: v-bind(xxx);
  236. }
  237. </style>`,
  238. )
  239. expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
  240. })
  241. // #7759
  242. test('It should correctly parse the case where there is no space after the script tag', () => {
  243. const { content } = compileSFCScript(
  244. `<script setup>import { ref as _ref } from 'vue';
  245. let background = _ref('red')
  246. </script>
  247. <style>
  248. label {
  249. background: v-bind(background);
  250. }
  251. </style>`,
  252. )
  253. expect(content).toMatch(
  254. `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`,
  255. )
  256. })
  257. describe('skip codegen in SSR', () => {
  258. test('script setup, inline', () => {
  259. const { content } = compileSFCScript(
  260. `<script setup>
  261. let size = 1
  262. </script>\n` +
  263. `<style>
  264. div {
  265. font-size: v-bind(size);
  266. }
  267. </style>`,
  268. {
  269. inlineTemplate: true,
  270. templateOptions: {
  271. ssr: true,
  272. },
  273. },
  274. )
  275. expect(content).not.toMatch(`_useCssVars`)
  276. })
  277. // #6926
  278. test('script, non-inline', () => {
  279. const { content } = compileSFCScript(
  280. `<script setup>
  281. let size = 1
  282. </script>\n` +
  283. `<style>
  284. div {
  285. font-size: v-bind(size);
  286. }
  287. </style>`,
  288. {
  289. inlineTemplate: false,
  290. templateOptions: {
  291. ssr: true,
  292. },
  293. },
  294. )
  295. expect(content).not.toMatch(`_useCssVars`)
  296. })
  297. test('normal script', () => {
  298. const { content } = compileSFCScript(
  299. `<script>
  300. export default {
  301. setup() {
  302. return {
  303. size: ref('100px')
  304. }
  305. }
  306. }
  307. </script>\n` +
  308. `<style>
  309. div {
  310. font-size: v-bind(size);
  311. }
  312. </style>`,
  313. {
  314. templateOptions: {
  315. ssr: true,
  316. },
  317. },
  318. )
  319. expect(content).not.toMatch(`_useCssVars`)
  320. })
  321. })
  322. })
  323. })