compileScript.spec.ts 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. import { BindingTypes } from '@vue/compiler-dom'
  2. import { compileSFCScript as compile, assertCode } from './utils'
  3. describe('SFC compile <script setup>', () => {
  4. test('should expose top level declarations', () => {
  5. const { content } = compile(`
  6. <script setup>
  7. import { x } from './x'
  8. let a = 1
  9. const b = 2
  10. function c() {}
  11. class d {}
  12. </script>
  13. `)
  14. assertCode(content)
  15. expect(content).toMatch('return { a, b, c, d, x }')
  16. })
  17. test('defineProps()', () => {
  18. const { content, bindings } = compile(`
  19. <script setup>
  20. import { defineProps } from 'vue'
  21. const props = defineProps({
  22. foo: String
  23. })
  24. const bar = 1
  25. </script>
  26. `)
  27. // should generate working code
  28. assertCode(content)
  29. // should anayze bindings
  30. expect(bindings).toStrictEqual({
  31. foo: BindingTypes.PROPS,
  32. bar: BindingTypes.SETUP_CONST,
  33. props: BindingTypes.SETUP_CONST
  34. })
  35. // should remove defineOptions import and call
  36. expect(content).not.toMatch('defineProps')
  37. // should generate correct setup signature
  38. expect(content).toMatch(`setup(__props) {`)
  39. // should assign user identifier to it
  40. expect(content).toMatch(`const props = __props`)
  41. // should include context options in default export
  42. expect(content).toMatch(`export default {
  43. expose: [],
  44. props: {
  45. foo: String
  46. },`)
  47. })
  48. test('defineEmit() (deprecated)', () => {
  49. const { content, bindings } = compile(`
  50. <script setup>
  51. import { defineEmit } from 'vue'
  52. const myEmit = defineEmit(['foo', 'bar'])
  53. </script>
  54. `)
  55. assertCode(content)
  56. expect(bindings).toStrictEqual({
  57. myEmit: BindingTypes.SETUP_CONST
  58. })
  59. // should remove defineOptions import and call
  60. expect(content).not.toMatch(/defineEmits?/)
  61. // should generate correct setup signature
  62. expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
  63. // should include context options in default export
  64. expect(content).toMatch(`export default {
  65. expose: [],
  66. emits: ['foo', 'bar'],`)
  67. })
  68. test('defineEmits()', () => {
  69. const { content, bindings } = compile(`
  70. <script setup>
  71. import { defineEmits } from 'vue'
  72. const myEmit = defineEmits(['foo', 'bar'])
  73. </script>
  74. `)
  75. assertCode(content)
  76. expect(bindings).toStrictEqual({
  77. myEmit: BindingTypes.SETUP_CONST
  78. })
  79. // should remove defineOptions import and call
  80. expect(content).not.toMatch('defineEmits')
  81. // should generate correct setup signature
  82. expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
  83. // should include context options in default export
  84. expect(content).toMatch(`export default {
  85. expose: [],
  86. emits: ['foo', 'bar'],`)
  87. })
  88. describe('<script> and <script setup> co-usage', () => {
  89. test('script first', () => {
  90. const { content } = compile(`
  91. <script>
  92. export const n = 1
  93. </script>
  94. <script setup>
  95. import { x } from './x'
  96. x()
  97. </script>
  98. `)
  99. assertCode(content)
  100. })
  101. test('script setup first', () => {
  102. const { content } = compile(`
  103. <script setup>
  104. import { x } from './x'
  105. x()
  106. </script>
  107. <script>
  108. export const n = 1
  109. </script>
  110. `)
  111. assertCode(content)
  112. })
  113. })
  114. describe('imports', () => {
  115. test('should hoist and expose imports', () => {
  116. assertCode(
  117. compile(`<script setup>
  118. import { ref } from 'vue'
  119. import 'foo/css'
  120. </script>`).content
  121. )
  122. })
  123. test('should extract comment for import or type declarations', () => {
  124. assertCode(
  125. compile(`
  126. <script setup>
  127. import a from 'a' // comment
  128. import b from 'b'
  129. </script>
  130. `).content
  131. )
  132. })
  133. // #2740
  134. test('should allow defineProps/Emit at the start of imports', () => {
  135. assertCode(
  136. compile(`<script setup>
  137. import { defineProps, defineEmits, ref } from 'vue'
  138. defineProps(['foo'])
  139. defineEmits(['bar'])
  140. const r = ref(0)
  141. </script>`).content
  142. )
  143. })
  144. test('dedupe between user & helper', () => {
  145. const { content } = compile(`
  146. <script setup>
  147. import { ref } from 'vue'
  148. ref: foo = 1
  149. </script>
  150. `)
  151. assertCode(content)
  152. expect(content).toMatch(`import { ref } from 'vue'`)
  153. })
  154. test('import dedupe between <script> and <script setup>', () => {
  155. const { content } = compile(`
  156. <script>
  157. import { x } from './x'
  158. </script>
  159. <script setup>
  160. import { x } from './x'
  161. x()
  162. </script>
  163. `)
  164. assertCode(content)
  165. expect(content.indexOf(`import { x }`)).toEqual(
  166. content.lastIndexOf(`import { x }`)
  167. )
  168. })
  169. })
  170. describe('inlineTemplate mode', () => {
  171. test('should work', () => {
  172. const { content } = compile(
  173. `
  174. <script setup>
  175. import { ref } from 'vue'
  176. const count = ref(0)
  177. </script>
  178. <template>
  179. <div>{{ count }}</div>
  180. <div>static</div>
  181. </template>
  182. `,
  183. { inlineTemplate: true }
  184. )
  185. // check snapshot and make sure helper imports and
  186. // hoists are placed correctly.
  187. assertCode(content)
  188. })
  189. test('referencing scope components and directives', () => {
  190. const { content } = compile(
  191. `
  192. <script setup>
  193. import ChildComp from './Child.vue'
  194. import SomeOtherComp from './Other.vue'
  195. import myDir from './my-dir'
  196. </script>
  197. <template>
  198. <div v-my-dir></div>
  199. <ChildComp/>
  200. <some-other-comp/>
  201. </template>
  202. `,
  203. { inlineTemplate: true }
  204. )
  205. expect(content).toMatch('[_unref(myDir)]')
  206. expect(content).toMatch('_createVNode(ChildComp)')
  207. // kebab-case component support
  208. expect(content).toMatch('_createVNode(SomeOtherComp)')
  209. assertCode(content)
  210. })
  211. test('avoid unref() when necessary', () => {
  212. // function, const, component import
  213. const { content } = compile(
  214. `<script setup>
  215. import { ref } from 'vue'
  216. import Foo, { bar } from './Foo.vue'
  217. import other from './util'
  218. const count = ref(0)
  219. const constant = {}
  220. const maybe = foo()
  221. let lett = 1
  222. function fn() {}
  223. </script>
  224. <template>
  225. <Foo>{{ bar }}</Foo>
  226. <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
  227. </template>
  228. `,
  229. { inlineTemplate: true }
  230. )
  231. // no need to unref vue component import
  232. expect(content).toMatch(`createVNode(Foo,`)
  233. // #2699 should unref named imports from .vue
  234. expect(content).toMatch(`unref(bar)`)
  235. // should unref other imports
  236. expect(content).toMatch(`unref(other)`)
  237. // no need to unref constant literals
  238. expect(content).not.toMatch(`unref(constant)`)
  239. // should directly use .value for known refs
  240. expect(content).toMatch(`count.value`)
  241. // should unref() on const bindings that may be refs
  242. expect(content).toMatch(`unref(maybe)`)
  243. // should unref() on let bindings
  244. expect(content).toMatch(`unref(lett)`)
  245. // no need to unref function declarations
  246. expect(content).toMatch(`{ onClick: fn }`)
  247. // no need to mark constant fns in patch flag
  248. expect(content).not.toMatch(`PROPS`)
  249. assertCode(content)
  250. })
  251. test('v-model codegen', () => {
  252. const { content } = compile(
  253. `<script setup>
  254. import { ref } from 'vue'
  255. const count = ref(0)
  256. const maybe = foo()
  257. let lett = 1
  258. </script>
  259. <template>
  260. <input v-model="count">
  261. <input v-model="maybe">
  262. <input v-model="lett">
  263. </template>
  264. `,
  265. { inlineTemplate: true }
  266. )
  267. // known const ref: set value
  268. expect(content).toMatch(`count.value = $event`)
  269. // const but maybe ref: also assign .value directly since non-ref
  270. // won't work
  271. expect(content).toMatch(`maybe.value = $event`)
  272. // let: handle both cases
  273. expect(content).toMatch(
  274. `_isRef(lett) ? lett.value = $event : lett = $event`
  275. )
  276. assertCode(content)
  277. })
  278. test('template assignment expression codegen', () => {
  279. const { content } = compile(
  280. `<script setup>
  281. import { ref } from 'vue'
  282. const count = ref(0)
  283. const maybe = foo()
  284. let lett = 1
  285. let v = ref(1)
  286. </script>
  287. <template>
  288. <div @click="count = 1"/>
  289. <div @click="maybe = count"/>
  290. <div @click="lett = count"/>
  291. <div @click="v += 1"/>
  292. <div @click="v -= 1"/>
  293. </template>
  294. `,
  295. { inlineTemplate: true }
  296. )
  297. // known const ref: set value
  298. expect(content).toMatch(`count.value = 1`)
  299. // const but maybe ref: only assign after check
  300. expect(content).toMatch(`maybe.value = count.value`)
  301. // let: handle both cases
  302. expect(content).toMatch(
  303. `_isRef(lett) ? lett.value = count.value : lett = count.value`
  304. )
  305. expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
  306. expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
  307. assertCode(content)
  308. })
  309. test('template update expression codegen', () => {
  310. const { content } = compile(
  311. `<script setup>
  312. import { ref } from 'vue'
  313. const count = ref(0)
  314. const maybe = foo()
  315. let lett = 1
  316. </script>
  317. <template>
  318. <div @click="count++"/>
  319. <div @click="--count"/>
  320. <div @click="maybe++"/>
  321. <div @click="--maybe"/>
  322. <div @click="lett++"/>
  323. <div @click="--lett"/>
  324. </template>
  325. `,
  326. { inlineTemplate: true }
  327. )
  328. // known const ref: set value
  329. expect(content).toMatch(`count.value++`)
  330. expect(content).toMatch(`--count.value`)
  331. // const but maybe ref (non-ref case ignored)
  332. expect(content).toMatch(`maybe.value++`)
  333. expect(content).toMatch(`--maybe.value`)
  334. // let: handle both cases
  335. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  336. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  337. assertCode(content)
  338. })
  339. test('template destructure assignment codegen', () => {
  340. const { content } = compile(
  341. `<script setup>
  342. import { ref } from 'vue'
  343. const val = {}
  344. const count = ref(0)
  345. const maybe = foo()
  346. let lett = 1
  347. </script>
  348. <template>
  349. <div @click="({ count } = val)"/>
  350. <div @click="[maybe] = val"/>
  351. <div @click="({ lett } = val)"/>
  352. </template>
  353. `,
  354. { inlineTemplate: true }
  355. )
  356. // known const ref: set value
  357. expect(content).toMatch(`({ count: count.value } = val)`)
  358. // const but maybe ref (non-ref case ignored)
  359. expect(content).toMatch(`[maybe.value] = val`)
  360. // let: assumes non-ref
  361. expect(content).toMatch(`{ lett: lett } = val`)
  362. assertCode(content)
  363. })
  364. test('ssr codegen', () => {
  365. const { content } = compile(
  366. `
  367. <script setup>
  368. import { ref } from 'vue'
  369. const count = ref(0)
  370. </script>
  371. <template>
  372. <div>{{ count }}</div>
  373. <div>static</div>
  374. </template>
  375. <style>
  376. div { color: v-bind(count) }
  377. </style>
  378. `,
  379. {
  380. inlineTemplate: true,
  381. templateOptions: {
  382. ssr: true
  383. }
  384. }
  385. )
  386. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  387. expect(content).toMatch(`return (_ctx, _push`)
  388. expect(content).toMatch(`ssrInterpolate`)
  389. assertCode(content)
  390. })
  391. // _withId is only generated for backwards compat and is a noop when called
  392. // in module scope.
  393. // when inside setup(), currentInstance will be non-null and _withId will
  394. // no longer be noop and cause scopeId errors.
  395. // TODO: this test should no longer be necessary if we remove _withId
  396. // codegen in 3.1
  397. test('should not wrap render fn with withId when having scoped styles', async () => {
  398. const { content } = compile(
  399. `
  400. <script setup>
  401. const msg = 1
  402. </script>
  403. <template><h1>{{ msg }}</h1></template>
  404. <style scoped>
  405. h1 { color: red; }
  406. </style>
  407. `,
  408. {
  409. inlineTemplate: true
  410. }
  411. )
  412. expect(content).toMatch(`return (_ctx, _cache`)
  413. expect(content).not.toMatch(`_withId(`)
  414. assertCode(content)
  415. })
  416. })
  417. describe('with TypeScript', () => {
  418. test('hoist type declarations', () => {
  419. const { content } = compile(`
  420. <script setup lang="ts">
  421. export interface Foo {}
  422. type Bar = {}
  423. </script>`)
  424. assertCode(content)
  425. })
  426. test('defineProps/Emit w/ runtime options', () => {
  427. const { content } = compile(`
  428. <script setup lang="ts">
  429. import { defineProps, defineEmits } from 'vue'
  430. const props = defineProps({ foo: String })
  431. const emit = defineEmits(['a', 'b'])
  432. </script>
  433. `)
  434. assertCode(content)
  435. expect(content).toMatch(`export default _defineComponent({
  436. expose: [],
  437. props: { foo: String },
  438. emits: ['a', 'b'],
  439. setup(__props, { emit }) {`)
  440. })
  441. test('defineProps w/ type', () => {
  442. const { content, bindings } = compile(`
  443. <script setup lang="ts">
  444. import { defineProps } from 'vue'
  445. interface Test {}
  446. type Alias = number[]
  447. defineProps<{
  448. string: string
  449. number: number
  450. boolean: boolean
  451. object: object
  452. objectLiteral: { a: number }
  453. fn: (n: number) => void
  454. functionRef: Function
  455. objectRef: Object
  456. array: string[]
  457. arrayRef: Array<any>
  458. tuple: [number, number]
  459. set: Set<string>
  460. literal: 'foo'
  461. optional?: any
  462. recordRef: Record<string, null>
  463. interface: Test
  464. alias: Alias
  465. union: string | number
  466. literalUnion: 'foo' | 'bar'
  467. literalUnionMixed: 'foo' | 1 | boolean
  468. intersection: Test & {}
  469. }>()
  470. </script>`)
  471. assertCode(content)
  472. expect(content).toMatch(`string: { type: String, required: true }`)
  473. expect(content).toMatch(`number: { type: Number, required: true }`)
  474. expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
  475. expect(content).toMatch(`object: { type: Object, required: true }`)
  476. expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
  477. expect(content).toMatch(`fn: { type: Function, required: true }`)
  478. expect(content).toMatch(`functionRef: { type: Function, required: true }`)
  479. expect(content).toMatch(`objectRef: { type: Object, required: true }`)
  480. expect(content).toMatch(`array: { type: Array, required: true }`)
  481. expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
  482. expect(content).toMatch(`tuple: { type: Array, required: true }`)
  483. expect(content).toMatch(`set: { type: Set, required: true }`)
  484. expect(content).toMatch(`literal: { type: String, required: true }`)
  485. expect(content).toMatch(`optional: { type: null, required: false }`)
  486. expect(content).toMatch(`recordRef: { type: Object, required: true }`)
  487. expect(content).toMatch(`interface: { type: Object, required: true }`)
  488. expect(content).toMatch(`alias: { type: Array, required: true }`)
  489. expect(content).toMatch(
  490. `union: { type: [String, Number], required: true }`
  491. )
  492. expect(content).toMatch(
  493. `literalUnion: { type: [String, String], required: true }`
  494. )
  495. expect(content).toMatch(
  496. `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
  497. )
  498. expect(content).toMatch(`intersection: { type: Object, required: true }`)
  499. expect(bindings).toStrictEqual({
  500. string: BindingTypes.PROPS,
  501. number: BindingTypes.PROPS,
  502. boolean: BindingTypes.PROPS,
  503. object: BindingTypes.PROPS,
  504. objectLiteral: BindingTypes.PROPS,
  505. fn: BindingTypes.PROPS,
  506. functionRef: BindingTypes.PROPS,
  507. objectRef: BindingTypes.PROPS,
  508. array: BindingTypes.PROPS,
  509. arrayRef: BindingTypes.PROPS,
  510. tuple: BindingTypes.PROPS,
  511. set: BindingTypes.PROPS,
  512. literal: BindingTypes.PROPS,
  513. optional: BindingTypes.PROPS,
  514. recordRef: BindingTypes.PROPS,
  515. interface: BindingTypes.PROPS,
  516. alias: BindingTypes.PROPS,
  517. union: BindingTypes.PROPS,
  518. literalUnion: BindingTypes.PROPS,
  519. literalUnionMixed: BindingTypes.PROPS,
  520. intersection: BindingTypes.PROPS
  521. })
  522. })
  523. test('defineEmits w/ type', () => {
  524. const { content } = compile(`
  525. <script setup lang="ts">
  526. import { defineEmits } from 'vue'
  527. const emit = defineEmits<(e: 'foo' | 'bar') => void>()
  528. </script>
  529. `)
  530. assertCode(content)
  531. expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
  532. expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
  533. })
  534. test('defineEmits w/ type (union)', () => {
  535. const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
  536. expect(() =>
  537. compile(`
  538. <script setup lang="ts">
  539. import { defineEmits } from 'vue'
  540. const emit = defineEmits<${type}>()
  541. </script>
  542. `)
  543. ).toThrow()
  544. })
  545. test('defineEmits w/ type (type literal w/ call signatures)', () => {
  546. const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
  547. const { content } = compile(`
  548. <script setup lang="ts">
  549. import { defineEmits } from 'vue'
  550. const emit = defineEmits<${type}>()
  551. </script>
  552. `)
  553. assertCode(content)
  554. expect(content).toMatch(`emit: (${type}),`)
  555. expect(content).toMatch(
  556. `emits: ["foo", "bar", "baz"] as unknown as undefined`
  557. )
  558. })
  559. })
  560. describe('async/await detection', () => {
  561. function assertAwaitDetection(code: string, shouldAsync = true) {
  562. const { content } = compile(`<script setup>${code}</script>`)
  563. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  564. }
  565. test('expression statement', () => {
  566. assertAwaitDetection(`await foo`)
  567. })
  568. test('variable', () => {
  569. assertAwaitDetection(`const a = 1 + (await foo)`)
  570. })
  571. test('ref', () => {
  572. assertAwaitDetection(`ref: a = 1 + (await foo)`)
  573. })
  574. test('nested statements', () => {
  575. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  576. })
  577. test('should ignore await inside functions', () => {
  578. // function declaration
  579. assertAwaitDetection(`async function foo() { await bar }`, false)
  580. // function expression
  581. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  582. // object method
  583. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  584. // class method
  585. assertAwaitDetection(
  586. `const cls = class Foo { async method() { await bar }}`,
  587. false
  588. )
  589. })
  590. })
  591. describe('ref: syntax sugar', () => {
  592. test('convert ref declarations', () => {
  593. const { content, bindings } = compile(`<script setup>
  594. ref: foo
  595. ref: a = 1
  596. ref: b = {
  597. count: 0
  598. }
  599. let c = () => {}
  600. let d
  601. </script>`)
  602. expect(content).toMatch(`import { ref as _ref } from 'vue'`)
  603. expect(content).not.toMatch(`ref: foo`)
  604. expect(content).not.toMatch(`ref: a`)
  605. expect(content).not.toMatch(`ref: b`)
  606. expect(content).toMatch(`const foo = _ref()`)
  607. expect(content).toMatch(`const a = _ref(1)`)
  608. expect(content).toMatch(`
  609. const b = _ref({
  610. count: 0
  611. })
  612. `)
  613. // normal declarations left untouched
  614. expect(content).toMatch(`let c = () => {}`)
  615. expect(content).toMatch(`let d`)
  616. assertCode(content)
  617. expect(bindings).toStrictEqual({
  618. foo: BindingTypes.SETUP_REF,
  619. a: BindingTypes.SETUP_REF,
  620. b: BindingTypes.SETUP_REF,
  621. c: BindingTypes.SETUP_LET,
  622. d: BindingTypes.SETUP_LET
  623. })
  624. })
  625. test('multi ref declarations', () => {
  626. const { content, bindings } = compile(`<script setup>
  627. ref: a = 1, b = 2, c = {
  628. count: 0
  629. }
  630. </script>`)
  631. expect(content).toMatch(`
  632. const a = _ref(1), b = _ref(2), c = _ref({
  633. count: 0
  634. })
  635. `)
  636. expect(content).toMatch(`return { a, b, c }`)
  637. assertCode(content)
  638. expect(bindings).toStrictEqual({
  639. a: BindingTypes.SETUP_REF,
  640. b: BindingTypes.SETUP_REF,
  641. c: BindingTypes.SETUP_REF
  642. })
  643. })
  644. test('should not convert non ref labels', () => {
  645. const { content } = compile(`<script setup>
  646. foo: a = 1, b = 2, c = {
  647. count: 0
  648. }
  649. </script>`)
  650. expect(content).toMatch(`foo: a = 1, b = 2`)
  651. assertCode(content)
  652. })
  653. test('accessing ref binding', () => {
  654. const { content } = compile(`<script setup>
  655. ref: a = 1
  656. console.log(a)
  657. function get() {
  658. return a + 1
  659. }
  660. </script>`)
  661. expect(content).toMatch(`console.log(a.value)`)
  662. expect(content).toMatch(`return a.value + 1`)
  663. assertCode(content)
  664. })
  665. test('cases that should not append .value', () => {
  666. const { content } = compile(`<script setup>
  667. ref: a = 1
  668. console.log(b.a)
  669. function get(a) {
  670. return a + 1
  671. }
  672. </script>`)
  673. expect(content).not.toMatch(`a.value`)
  674. })
  675. test('mutating ref binding', () => {
  676. const { content } = compile(`<script setup>
  677. ref: a = 1
  678. ref: b = { count: 0 }
  679. function inc() {
  680. a++
  681. a = a + 1
  682. b.count++
  683. b.count = b.count + 1
  684. ;({ a } = { a: 2 })
  685. ;[a] = [1]
  686. }
  687. </script>`)
  688. expect(content).toMatch(`a.value++`)
  689. expect(content).toMatch(`a.value = a.value + 1`)
  690. expect(content).toMatch(`b.value.count++`)
  691. expect(content).toMatch(`b.value.count = b.value.count + 1`)
  692. expect(content).toMatch(`;({ a: a.value } = { a: 2 })`)
  693. expect(content).toMatch(`;[a.value] = [1]`)
  694. assertCode(content)
  695. })
  696. test('using ref binding in property shorthand', () => {
  697. const { content } = compile(`<script setup>
  698. ref: a = 1
  699. const b = { a }
  700. function test() {
  701. const { a } = b
  702. }
  703. </script>`)
  704. expect(content).toMatch(`const b = { a: a.value }`)
  705. // should not convert destructure
  706. expect(content).toMatch(`const { a } = b`)
  707. assertCode(content)
  708. })
  709. test('should not rewrite scope variable', () => {
  710. const { content } = compile(`
  711. <script setup>
  712. ref: a = 1
  713. ref: b = 1
  714. ref: d = 1
  715. const e = 1
  716. function test() {
  717. const a = 2
  718. console.log(a)
  719. console.log(b)
  720. let c = { c: 3 }
  721. console.log(c)
  722. let $d
  723. console.log($d)
  724. console.log(d)
  725. console.log(e)
  726. }
  727. </script>`)
  728. expect(content).toMatch('console.log(a)')
  729. expect(content).toMatch('console.log(b.value)')
  730. expect(content).toMatch('console.log(c)')
  731. expect(content).toMatch('console.log($d)')
  732. expect(content).toMatch('console.log(d.value)')
  733. expect(content).toMatch('console.log(e)')
  734. assertCode(content)
  735. })
  736. test('object destructure', () => {
  737. const { content, bindings } = compile(`<script setup>
  738. ref: n = 1, ({ a, b: c, d = 1, e: f = 2, ...g } = useFoo())
  739. ref: ({ foo } = useSomthing(() => 1));
  740. console.log(n, a, c, d, f, g, foo)
  741. </script>`)
  742. expect(content).toMatch(
  743. `const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
  744. )
  745. expect(content).toMatch(`const { foo: __foo } = useSomthing(() => 1)`)
  746. expect(content).toMatch(`\nconst a = _ref(__a);`)
  747. expect(content).not.toMatch(`\nconst b = _ref(__b);`)
  748. expect(content).toMatch(`\nconst c = _ref(__c);`)
  749. expect(content).toMatch(`\nconst d = _ref(__d);`)
  750. expect(content).not.toMatch(`\nconst e = _ref(__e);`)
  751. expect(content).toMatch(`\nconst f = _ref(__f);`)
  752. expect(content).toMatch(`\nconst g = _ref(__g);`)
  753. expect(content).toMatch(`\nconst foo = _ref(__foo);`)
  754. expect(content).toMatch(
  755. `console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)`
  756. )
  757. expect(content).toMatch(`return { n, a, c, d, f, g, foo }`)
  758. expect(bindings).toStrictEqual({
  759. n: BindingTypes.SETUP_REF,
  760. a: BindingTypes.SETUP_REF,
  761. c: BindingTypes.SETUP_REF,
  762. d: BindingTypes.SETUP_REF,
  763. f: BindingTypes.SETUP_REF,
  764. g: BindingTypes.SETUP_REF,
  765. foo: BindingTypes.SETUP_REF
  766. })
  767. assertCode(content)
  768. })
  769. test('array destructure', () => {
  770. const { content, bindings } = compile(`<script setup>
  771. ref: n = 1, [a, b = 1, ...c] = useFoo()
  772. console.log(n, a, b, c)
  773. </script>`)
  774. expect(content).toMatch(
  775. `const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()`
  776. )
  777. expect(content).toMatch(`\nconst a = _ref(__a);`)
  778. expect(content).toMatch(`\nconst b = _ref(__b);`)
  779. expect(content).toMatch(`\nconst c = _ref(__c);`)
  780. expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
  781. expect(content).toMatch(`return { n, a, b, c }`)
  782. expect(bindings).toStrictEqual({
  783. n: BindingTypes.SETUP_REF,
  784. a: BindingTypes.SETUP_REF,
  785. b: BindingTypes.SETUP_REF,
  786. c: BindingTypes.SETUP_REF
  787. })
  788. assertCode(content)
  789. })
  790. test('nested destructure', () => {
  791. const { content, bindings } = compile(`<script setup>
  792. ref: [{ a: { b }}] = useFoo()
  793. ref: ({ c: [d, e] } = useBar())
  794. console.log(b, d, e)
  795. </script>`)
  796. expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
  797. expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
  798. expect(content).not.toMatch(`\nconst a = _ref(__a);`)
  799. expect(content).not.toMatch(`\nconst c = _ref(__c);`)
  800. expect(content).toMatch(`\nconst b = _ref(__b);`)
  801. expect(content).toMatch(`\nconst d = _ref(__d);`)
  802. expect(content).toMatch(`\nconst e = _ref(__e);`)
  803. expect(content).toMatch(`return { b, d, e }`)
  804. expect(bindings).toStrictEqual({
  805. b: BindingTypes.SETUP_REF,
  806. d: BindingTypes.SETUP_REF,
  807. e: BindingTypes.SETUP_REF
  808. })
  809. assertCode(content)
  810. })
  811. })
  812. describe('errors', () => {
  813. test('<script> and <script setup> must have same lang', () => {
  814. expect(() =>
  815. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  816. ).toThrow(`<script> and <script setup> must have the same language type`)
  817. })
  818. const moduleErrorMsg = `cannot contain ES module exports`
  819. test('non-type named exports', () => {
  820. expect(() =>
  821. compile(`<script setup>
  822. export const a = 1
  823. </script>`)
  824. ).toThrow(moduleErrorMsg)
  825. expect(() =>
  826. compile(`<script setup>
  827. export * from './foo'
  828. </script>`)
  829. ).toThrow(moduleErrorMsg)
  830. expect(() =>
  831. compile(`<script setup>
  832. const bar = 1
  833. export { bar as default }
  834. </script>`)
  835. ).toThrow(moduleErrorMsg)
  836. })
  837. test('ref: non-assignment expressions', () => {
  838. expect(() =>
  839. compile(`<script setup>
  840. ref: a = 1, foo()
  841. </script>`)
  842. ).toThrow(`ref: statements can only contain assignment expressions`)
  843. })
  844. test('defineProps/Emit() w/ both type and non-type args', () => {
  845. expect(() => {
  846. compile(`<script setup lang="ts">
  847. import { defineProps } from 'vue'
  848. defineProps<{}>({})
  849. </script>`)
  850. }).toThrow(`cannot accept both type and non-type arguments`)
  851. expect(() => {
  852. compile(`<script setup lang="ts">
  853. import { defineEmits } from 'vue'
  854. defineEmits<{}>({})
  855. </script>`)
  856. }).toThrow(`cannot accept both type and non-type arguments`)
  857. })
  858. test('defineProps/Emit() referencing local var', () => {
  859. expect(() =>
  860. compile(`<script setup>
  861. import { defineProps } from 'vue'
  862. const bar = 1
  863. defineProps({
  864. foo: {
  865. default: () => bar
  866. }
  867. })
  868. </script>`)
  869. ).toThrow(`cannot reference locally declared variables`)
  870. expect(() =>
  871. compile(`<script setup>
  872. import { defineEmits } from 'vue'
  873. const bar = 'hello'
  874. defineEmits([bar])
  875. </script>`)
  876. ).toThrow(`cannot reference locally declared variables`)
  877. })
  878. test('defineProps/Emit() referencing ref declarations', () => {
  879. expect(() =>
  880. compile(`<script setup>
  881. import { defineProps } from 'vue'
  882. ref: bar = 1
  883. defineProps({
  884. bar
  885. })
  886. </script>`)
  887. ).toThrow(`cannot reference locally declared variables`)
  888. expect(() =>
  889. compile(`<script setup>
  890. import { defineEmits } from 'vue'
  891. ref: bar = 1
  892. defineEmits({
  893. bar
  894. })
  895. </script>`)
  896. ).toThrow(`cannot reference locally declared variables`)
  897. })
  898. test('should allow defineProps/Emit() referencing scope var', () => {
  899. assertCode(
  900. compile(`<script setup>
  901. import { defineProps, defineEmits } from 'vue'
  902. const bar = 1
  903. defineProps({
  904. foo: {
  905. default: bar => bar + 1
  906. }
  907. })
  908. defineEmits({
  909. foo: bar => bar > 1
  910. })
  911. </script>`).content
  912. )
  913. })
  914. test('should allow defineProps/Emit() referencing imported binding', () => {
  915. assertCode(
  916. compile(`<script setup>
  917. import { defineProps, defineEmits } from 'vue'
  918. import { bar } from './bar'
  919. defineProps({
  920. foo: {
  921. default: () => bar
  922. }
  923. })
  924. defineEmits({
  925. foo: () => bar > 1
  926. })
  927. </script>`).content
  928. )
  929. })
  930. })
  931. })
  932. describe('SFC analyze <script> bindings', () => {
  933. it('can parse decorators syntax in typescript block', () => {
  934. const { scriptAst } = compile(`
  935. <script lang="ts">
  936. import { Options, Vue } from 'vue-class-component';
  937. @Options({
  938. components: {
  939. HelloWorld,
  940. },
  941. props: ['foo', 'bar']
  942. })
  943. export default class Home extends Vue {}
  944. </script>
  945. `)
  946. expect(scriptAst).toBeDefined()
  947. })
  948. it('recognizes props array declaration', () => {
  949. const { bindings } = compile(`
  950. <script>
  951. export default {
  952. props: ['foo', 'bar']
  953. }
  954. </script>
  955. `)
  956. expect(bindings).toStrictEqual({
  957. foo: BindingTypes.PROPS,
  958. bar: BindingTypes.PROPS
  959. })
  960. expect(bindings!.__isScriptSetup).toBe(false)
  961. })
  962. it('recognizes props object declaration', () => {
  963. const { bindings } = compile(`
  964. <script>
  965. export default {
  966. props: {
  967. foo: String,
  968. bar: {
  969. type: String,
  970. },
  971. baz: null,
  972. qux: [String, Number]
  973. }
  974. }
  975. </script>
  976. `)
  977. expect(bindings).toStrictEqual({
  978. foo: BindingTypes.PROPS,
  979. bar: BindingTypes.PROPS,
  980. baz: BindingTypes.PROPS,
  981. qux: BindingTypes.PROPS
  982. })
  983. expect(bindings!.__isScriptSetup).toBe(false)
  984. })
  985. it('recognizes setup return', () => {
  986. const { bindings } = compile(`
  987. <script>
  988. const bar = 2
  989. export default {
  990. setup() {
  991. return {
  992. foo: 1,
  993. bar
  994. }
  995. }
  996. }
  997. </script>
  998. `)
  999. expect(bindings).toStrictEqual({
  1000. foo: BindingTypes.SETUP_MAYBE_REF,
  1001. bar: BindingTypes.SETUP_MAYBE_REF
  1002. })
  1003. expect(bindings!.__isScriptSetup).toBe(false)
  1004. })
  1005. it('recognizes async setup return', () => {
  1006. const { bindings } = compile(`
  1007. <script>
  1008. const bar = 2
  1009. export default {
  1010. async setup() {
  1011. return {
  1012. foo: 1,
  1013. bar
  1014. }
  1015. }
  1016. }
  1017. </script>
  1018. `)
  1019. expect(bindings).toStrictEqual({
  1020. foo: BindingTypes.SETUP_MAYBE_REF,
  1021. bar: BindingTypes.SETUP_MAYBE_REF
  1022. })
  1023. expect(bindings!.__isScriptSetup).toBe(false)
  1024. })
  1025. it('recognizes data return', () => {
  1026. const { bindings } = compile(`
  1027. <script>
  1028. const bar = 2
  1029. export default {
  1030. data() {
  1031. return {
  1032. foo: null,
  1033. bar
  1034. }
  1035. }
  1036. }
  1037. </script>
  1038. `)
  1039. expect(bindings).toStrictEqual({
  1040. foo: BindingTypes.DATA,
  1041. bar: BindingTypes.DATA
  1042. })
  1043. })
  1044. it('recognizes methods', () => {
  1045. const { bindings } = compile(`
  1046. <script>
  1047. export default {
  1048. methods: {
  1049. foo() {}
  1050. }
  1051. }
  1052. </script>
  1053. `)
  1054. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  1055. })
  1056. it('recognizes computeds', () => {
  1057. const { bindings } = compile(`
  1058. <script>
  1059. export default {
  1060. computed: {
  1061. foo() {},
  1062. bar: {
  1063. get() {},
  1064. set() {},
  1065. }
  1066. }
  1067. }
  1068. </script>
  1069. `)
  1070. expect(bindings).toStrictEqual({
  1071. foo: BindingTypes.OPTIONS,
  1072. bar: BindingTypes.OPTIONS
  1073. })
  1074. })
  1075. it('recognizes injections array declaration', () => {
  1076. const { bindings } = compile(`
  1077. <script>
  1078. export default {
  1079. inject: ['foo', 'bar']
  1080. }
  1081. </script>
  1082. `)
  1083. expect(bindings).toStrictEqual({
  1084. foo: BindingTypes.OPTIONS,
  1085. bar: BindingTypes.OPTIONS
  1086. })
  1087. })
  1088. it('recognizes injections object declaration', () => {
  1089. const { bindings } = compile(`
  1090. <script>
  1091. export default {
  1092. inject: {
  1093. foo: {},
  1094. bar: {},
  1095. }
  1096. }
  1097. </script>
  1098. `)
  1099. expect(bindings).toStrictEqual({
  1100. foo: BindingTypes.OPTIONS,
  1101. bar: BindingTypes.OPTIONS
  1102. })
  1103. })
  1104. it('works for mixed bindings', () => {
  1105. const { bindings } = compile(`
  1106. <script>
  1107. export default {
  1108. inject: ['foo'],
  1109. props: {
  1110. bar: String,
  1111. },
  1112. setup() {
  1113. return {
  1114. baz: null,
  1115. }
  1116. },
  1117. data() {
  1118. return {
  1119. qux: null
  1120. }
  1121. },
  1122. methods: {
  1123. quux() {}
  1124. },
  1125. computed: {
  1126. quuz() {}
  1127. }
  1128. }
  1129. </script>
  1130. `)
  1131. expect(bindings).toStrictEqual({
  1132. foo: BindingTypes.OPTIONS,
  1133. bar: BindingTypes.PROPS,
  1134. baz: BindingTypes.SETUP_MAYBE_REF,
  1135. qux: BindingTypes.DATA,
  1136. quux: BindingTypes.OPTIONS,
  1137. quuz: BindingTypes.OPTIONS
  1138. })
  1139. })
  1140. it('works for script setup', () => {
  1141. const { bindings } = compile(`
  1142. <script setup>
  1143. import { defineProps, ref as r } from 'vue'
  1144. defineProps({
  1145. foo: String
  1146. })
  1147. const a = r(1)
  1148. let b = 2
  1149. const c = 3
  1150. const { d } = someFoo()
  1151. let { e } = someBar()
  1152. </script>
  1153. `)
  1154. expect(bindings).toStrictEqual({
  1155. r: BindingTypes.SETUP_CONST,
  1156. a: BindingTypes.SETUP_REF,
  1157. b: BindingTypes.SETUP_LET,
  1158. c: BindingTypes.SETUP_CONST,
  1159. d: BindingTypes.SETUP_MAYBE_REF,
  1160. e: BindingTypes.SETUP_LET,
  1161. foo: BindingTypes.PROPS
  1162. })
  1163. })
  1164. })