compileScript.spec.ts 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  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()', () => {
  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('defineEmit')
  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('<template inherit-attrs="false">', () => {
  69. const { content } = compile(`
  70. <script>
  71. export default {}
  72. </script>
  73. <template inherit-attrs="false">
  74. {{ a }}
  75. </template>
  76. `)
  77. assertCode(content)
  78. const { content: content2 } = compile(`
  79. <script setup>
  80. const a = 1
  81. </script>
  82. <template inherit-attrs="false">
  83. {{ a }}
  84. </template>
  85. `)
  86. assertCode(content2)
  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, defineEmit, ref } from 'vue'
  138. defineProps(['foo'])
  139. defineEmit(['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, defineEmit } from 'vue'
  430. const props = defineProps({ foo: String })
  431. const emit = defineEmit(['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('defineEmit w/ type', () => {
  524. const { content } = compile(`
  525. <script setup lang="ts">
  526. import { defineEmit } from 'vue'
  527. const emit = defineEmit<(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('defineEmit 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 { defineEmit } from 'vue'
  540. const emit = defineEmit<${type}>()
  541. </script>
  542. `)
  543. ).toThrow()
  544. })
  545. test('defineEmit 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 { defineEmit } from 'vue'
  550. const emit = defineEmit<${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 { defineEmit } from 'vue'
  854. defineEmit<{}>({})
  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 { defineEmit } from 'vue'
  873. const bar = 'hello'
  874. defineEmit([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 { defineEmit } from 'vue'
  891. ref: bar = 1
  892. defineEmit({
  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, defineEmit } from 'vue'
  902. const bar = 1
  903. defineProps({
  904. foo: {
  905. default: bar => bar + 1
  906. }
  907. })
  908. defineEmit({
  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, defineEmit } from 'vue'
  918. import { bar } from './bar'
  919. defineProps({
  920. foo: {
  921. default: () => bar
  922. }
  923. })
  924. defineEmit({
  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. })