compileScript.spec.ts 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { assertCode, compileSFCScript as compile, mockId } from './utils'
  3. describe('SFC compile <script setup>', () => {
  4. test('should compile JS syntax', () => {
  5. const { content } = compile(`
  6. <script setup lang='js'>
  7. const a = 1
  8. const b = 2
  9. </script>
  10. `)
  11. expect(content).toMatch(`return { a, b }`)
  12. assertCode(content)
  13. })
  14. test('should expose top level declarations', () => {
  15. const { content, bindings } = compile(`
  16. <script setup>
  17. import { x } from './x'
  18. let a = 1
  19. const b = 2
  20. function c() {}
  21. class d {}
  22. </script>
  23. <script>
  24. import { xx } from './x'
  25. let aa = 1
  26. const bb = 2
  27. function cc() {}
  28. class dd {}
  29. </script>
  30. `)
  31. expect(content).toMatch(
  32. `return { get aa() { return aa }, set aa(v) { aa = v }, ` +
  33. `bb, cc, dd, get a() { return a }, set a(v) { a = v }, b, c, d, ` +
  34. `get xx() { return xx }, get x() { return x } }`,
  35. )
  36. expect(bindings).toStrictEqual({
  37. x: BindingTypes.SETUP_MAYBE_REF,
  38. a: BindingTypes.SETUP_LET,
  39. b: BindingTypes.SETUP_CONST,
  40. c: BindingTypes.SETUP_CONST,
  41. d: BindingTypes.SETUP_CONST,
  42. xx: BindingTypes.SETUP_MAYBE_REF,
  43. aa: BindingTypes.SETUP_LET,
  44. bb: BindingTypes.LITERAL_CONST,
  45. cc: BindingTypes.SETUP_CONST,
  46. dd: BindingTypes.SETUP_CONST,
  47. })
  48. assertCode(content)
  49. })
  50. test('binding analysis for destructure', () => {
  51. const { content, bindings } = compile(`
  52. <script setup>
  53. const { foo, b: bar, ['x' + 'y']: baz, x: { y, zz: { z }}} = {}
  54. </script>
  55. `)
  56. expect(content).toMatch('return { foo, bar, baz, y, z }')
  57. expect(bindings).toStrictEqual({
  58. foo: BindingTypes.SETUP_MAYBE_REF,
  59. bar: BindingTypes.SETUP_MAYBE_REF,
  60. baz: BindingTypes.SETUP_MAYBE_REF,
  61. y: BindingTypes.SETUP_MAYBE_REF,
  62. z: BindingTypes.SETUP_MAYBE_REF,
  63. })
  64. assertCode(content)
  65. })
  66. describe('<script> and <script setup> co-usage', () => {
  67. test('script first', () => {
  68. const { content } = compile(`
  69. <script>
  70. export const n = 1
  71. export default {}
  72. </script>
  73. <script setup>
  74. import { x } from './x'
  75. x()
  76. </script>
  77. `)
  78. assertCode(content)
  79. })
  80. test('script setup first', () => {
  81. const { content } = compile(`
  82. <script setup>
  83. import { x } from './x'
  84. x()
  85. </script>
  86. <script>
  87. export const n = 1
  88. export default {}
  89. </script>
  90. `)
  91. assertCode(content)
  92. })
  93. // #7805
  94. test('keep original semi style', () => {
  95. const { content } = compile(`
  96. <script setup>
  97. console.log('test')
  98. const props = defineProps(['item']);
  99. const emit = defineEmits(['change']);
  100. (function () {})()
  101. </script>
  102. `)
  103. assertCode(content)
  104. expect(content).toMatch(`console.log('test')`)
  105. expect(content).toMatch(`const props = __props;`)
  106. expect(content).toMatch(`const emit = __emit;`)
  107. expect(content).toMatch(`(function () {})()`)
  108. })
  109. test('script setup first, named default export', () => {
  110. const { content } = compile(`
  111. <script setup>
  112. import { x } from './x'
  113. x()
  114. </script>
  115. <script>
  116. export const n = 1
  117. const def = {}
  118. export { def as default }
  119. </script>
  120. `)
  121. assertCode(content)
  122. })
  123. // #4395
  124. test('script setup first, lang="ts", script block content export default', () => {
  125. const { content } = compile(`
  126. <script setup lang="ts">
  127. import { x } from './x'
  128. x()
  129. </script>
  130. <script lang="ts">
  131. export default {
  132. name: "test"
  133. }
  134. </script>
  135. `)
  136. // ensure __default__ is declared before used
  137. expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m)
  138. assertCode(content)
  139. })
  140. describe('spaces in ExportDefaultDeclaration node', () => {
  141. // #4371
  142. test('with many spaces and newline', () => {
  143. // #4371
  144. const { content } = compile(`
  145. <script>
  146. export const n = 1
  147. export default
  148. {
  149. some:'option'
  150. }
  151. </script>
  152. <script setup>
  153. import { x } from './x'
  154. x()
  155. </script>
  156. `)
  157. assertCode(content)
  158. })
  159. test('with minimal spaces', () => {
  160. const { content } = compile(`
  161. <script>
  162. export const n = 1
  163. export default{
  164. some:'option'
  165. }
  166. </script>
  167. <script setup>
  168. import { x } from './x'
  169. x()
  170. </script>
  171. `)
  172. assertCode(content)
  173. })
  174. })
  175. test('export call expression as default', () => {
  176. const { content } = compile(`
  177. <script>
  178. function fn() {
  179. return "hello, world";
  180. }
  181. export default fn();
  182. </script>
  183. <script setup>
  184. console.log('foo')
  185. </script>
  186. `)
  187. assertCode(content)
  188. })
  189. })
  190. describe('imports', () => {
  191. test('should hoist and expose imports', () => {
  192. assertCode(
  193. compile(`<script setup>
  194. import { ref } from 'vue'
  195. import 'foo/css'
  196. </script>`).content,
  197. )
  198. })
  199. test('should extract comment for import or type declarations', () => {
  200. assertCode(
  201. compile(`
  202. <script setup>
  203. import a from 'a' // comment
  204. import b from 'b'
  205. </script>
  206. `).content,
  207. )
  208. })
  209. // #2740
  210. test('should allow defineProps/Emit at the start of imports', () => {
  211. assertCode(
  212. compile(`<script setup>
  213. import { ref } from 'vue'
  214. defineProps(['foo'])
  215. defineEmits(['bar'])
  216. const r = ref(0)
  217. </script>`).content,
  218. )
  219. })
  220. test('dedupe between user & helper', () => {
  221. const { content } = compile(
  222. `
  223. <script setup>
  224. import { useCssVars, ref } from 'vue'
  225. const msg = ref()
  226. </script>
  227. <style>
  228. .foo {
  229. color: v-bind(msg)
  230. }
  231. </style>
  232. `,
  233. )
  234. assertCode(content)
  235. expect(content).toMatch(
  236. `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
  237. )
  238. expect(content).toMatch(`import { useCssVars, ref } from 'vue'`)
  239. })
  240. test('import dedupe between <script> and <script setup>', () => {
  241. const { content } = compile(`
  242. <script>
  243. import { x } from './x'
  244. </script>
  245. <script setup>
  246. import { x } from './x'
  247. x()
  248. </script>
  249. `)
  250. assertCode(content)
  251. expect(content.indexOf(`import { x }`)).toEqual(
  252. content.lastIndexOf(`import { x }`),
  253. )
  254. })
  255. describe('import ref/reactive function from other place', () => {
  256. test('import directly', () => {
  257. const { bindings } = compile(`
  258. <script setup>
  259. import { ref, reactive } from './foo'
  260. const foo = ref(1)
  261. const bar = reactive(1)
  262. </script>
  263. `)
  264. expect(bindings).toStrictEqual({
  265. ref: BindingTypes.SETUP_MAYBE_REF,
  266. reactive: BindingTypes.SETUP_MAYBE_REF,
  267. foo: BindingTypes.SETUP_MAYBE_REF,
  268. bar: BindingTypes.SETUP_MAYBE_REF,
  269. })
  270. })
  271. test('import w/ alias', () => {
  272. const { bindings } = compile(`
  273. <script setup>
  274. import { ref as _ref, reactive as _reactive } from './foo'
  275. const foo = ref(1)
  276. const bar = reactive(1)
  277. </script>
  278. `)
  279. expect(bindings).toStrictEqual({
  280. _reactive: BindingTypes.SETUP_MAYBE_REF,
  281. _ref: BindingTypes.SETUP_MAYBE_REF,
  282. foo: BindingTypes.SETUP_MAYBE_REF,
  283. bar: BindingTypes.SETUP_MAYBE_REF,
  284. })
  285. })
  286. test('aliased usage before import site', () => {
  287. const { bindings } = compile(`
  288. <script setup>
  289. const bar = x(1)
  290. import { reactive as x } from 'vue'
  291. </script>
  292. `)
  293. expect(bindings).toStrictEqual({
  294. bar: BindingTypes.SETUP_REACTIVE_CONST,
  295. x: BindingTypes.SETUP_CONST,
  296. })
  297. })
  298. })
  299. test('should support module string names syntax', () => {
  300. const { content, bindings } = compile(`
  301. <script>
  302. import { "😏" as foo } from './foo'
  303. </script>
  304. <script setup>
  305. import { "😏" as foo } from './foo'
  306. </script>
  307. `)
  308. assertCode(content)
  309. expect(bindings).toStrictEqual({
  310. foo: BindingTypes.SETUP_MAYBE_REF,
  311. })
  312. })
  313. })
  314. describe('inlineTemplate mode', () => {
  315. test('should work', () => {
  316. const { content } = compile(
  317. `
  318. <script setup>
  319. import { ref } from 'vue'
  320. const count = ref(0)
  321. </script>
  322. <template>
  323. <div>{{ count }}</div>
  324. <div>static</div>
  325. </template>
  326. `,
  327. { inlineTemplate: true },
  328. )
  329. // check snapshot and make sure helper imports and
  330. // hoists are placed correctly.
  331. assertCode(content)
  332. // in inline mode, no need to call expose() since nothing is exposed
  333. // anyway!
  334. expect(content).not.toMatch(`expose()`)
  335. })
  336. test('with defineExpose()', () => {
  337. const { content } = compile(
  338. `
  339. <script setup>
  340. const count = ref(0)
  341. defineExpose({ count })
  342. </script>
  343. `,
  344. { inlineTemplate: true },
  345. )
  346. assertCode(content)
  347. expect(content).toMatch(`setup(__props, { expose: __expose })`)
  348. expect(content).toMatch(`expose({ count })`)
  349. })
  350. test('referencing scope components and directives', () => {
  351. const { content } = compile(
  352. `
  353. <script setup>
  354. import ChildComp from './Child.vue'
  355. import SomeOtherComp from './Other.vue'
  356. import vMyDir from './my-dir'
  357. </script>
  358. <template>
  359. <div v-my-dir></div>
  360. <ChildComp/>
  361. <some-other-comp/>
  362. </template>
  363. `,
  364. { inlineTemplate: true },
  365. )
  366. expect(content).toMatch('[_unref(vMyDir)]')
  367. expect(content).toMatch('_createVNode(ChildComp)')
  368. // kebab-case component support
  369. expect(content).toMatch('_createVNode(SomeOtherComp)')
  370. assertCode(content)
  371. })
  372. test('avoid unref() when necessary', () => {
  373. // function, const, component import
  374. const { content } = compile(
  375. `<script setup>
  376. import { ref } from 'vue'
  377. import Foo, { bar } from './Foo.vue'
  378. import other from './util'
  379. import * as tree from './tree'
  380. const count = ref(0)
  381. const constant = {}
  382. const maybe = foo()
  383. let lett = 1
  384. function fn() {}
  385. </script>
  386. <template>
  387. <Foo>{{ bar }}</Foo>
  388. <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
  389. {{ tree.foo() }}
  390. </template>
  391. `,
  392. { inlineTemplate: true },
  393. )
  394. // no need to unref vue component import
  395. expect(content).toMatch(`createVNode(Foo,`)
  396. // #2699 should unref named imports from .vue
  397. expect(content).toMatch(`unref(bar)`)
  398. // should unref other imports
  399. expect(content).toMatch(`unref(other)`)
  400. // no need to unref constant literals
  401. expect(content).not.toMatch(`unref(constant)`)
  402. // should directly use .value for known refs
  403. expect(content).toMatch(`count.value`)
  404. // should unref() on const bindings that may be refs
  405. expect(content).toMatch(`unref(maybe)`)
  406. // should unref() on let bindings
  407. expect(content).toMatch(`unref(lett)`)
  408. // no need to unref namespace import (this also preserves tree-shaking)
  409. expect(content).toMatch(`tree.foo()`)
  410. // no need to unref function declarations
  411. expect(content).toMatch(`{ onClick: fn }`)
  412. // no need to mark constant fns in patch flag
  413. expect(content).not.toMatch(`PROPS`)
  414. assertCode(content)
  415. })
  416. test('v-model codegen', () => {
  417. const { content } = compile(
  418. `<script setup>
  419. import { ref } from 'vue'
  420. const count = ref(0)
  421. const maybe = foo()
  422. let lett = 1
  423. </script>
  424. <template>
  425. <input v-model="count">
  426. <input v-model="maybe">
  427. <input v-model="lett">
  428. </template>
  429. `,
  430. { inlineTemplate: true },
  431. )
  432. // known const ref: set value
  433. expect(content).toMatch(`(count).value = $event`)
  434. // const but maybe ref: assign if ref, otherwise do nothing
  435. expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`)
  436. // let: handle both cases
  437. expect(content).toMatch(
  438. `_isRef(lett) ? (lett).value = $event : lett = $event`,
  439. )
  440. assertCode(content)
  441. })
  442. test('v-model should not generate ref assignment code for non-setup bindings', () => {
  443. const { content } = compile(
  444. `<script setup>
  445. import { ref } from 'vue'
  446. const count = ref(0)
  447. </script>
  448. <script>
  449. export default {
  450. data() { return { foo: 123 } }
  451. }
  452. </script>
  453. <template>
  454. <input v-model="foo">
  455. </template>
  456. `,
  457. { inlineTemplate: true },
  458. )
  459. expect(content).not.toMatch(`_isRef(foo)`)
  460. })
  461. test('template assignment expression codegen', () => {
  462. const { content } = compile(
  463. `<script setup>
  464. import { ref } from 'vue'
  465. const count = ref(0)
  466. const maybe = foo()
  467. let lett = 1
  468. let v = ref(1)
  469. </script>
  470. <template>
  471. <div @click="count = 1"/>
  472. <div @click="maybe = count"/>
  473. <div @click="lett = count"/>
  474. <div @click="v += 1"/>
  475. <div @click="v -= 1"/>
  476. <div @click="() => {
  477. let a = '' + lett
  478. v = a
  479. }"/>
  480. <div @click="() => {
  481. // nested scopes
  482. (()=>{
  483. let x = a
  484. (()=>{
  485. let z = x
  486. let z2 = z
  487. })
  488. let lz = z
  489. })
  490. v = a
  491. }"/>
  492. </template>
  493. `,
  494. { inlineTemplate: true },
  495. )
  496. // known const ref: set value
  497. expect(content).toMatch(`count.value = 1`)
  498. // const but maybe ref: only assign after check
  499. expect(content).toMatch(`maybe.value = count.value`)
  500. // let: handle both cases
  501. expect(content).toMatch(
  502. `_isRef(lett) ? lett.value = count.value : lett = count.value`,
  503. )
  504. expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
  505. expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
  506. expect(content).toMatch(`_isRef(v) ? v.value = a : v = a`)
  507. expect(content).toMatch(`_isRef(v) ? v.value = _ctx.a : v = _ctx.a`)
  508. assertCode(content)
  509. })
  510. test('template update expression codegen', () => {
  511. const { content } = compile(
  512. `<script setup>
  513. import { ref } from 'vue'
  514. const count = ref(0)
  515. const maybe = foo()
  516. let lett = 1
  517. </script>
  518. <template>
  519. <div @click="count++"/>
  520. <div @click="--count"/>
  521. <div @click="maybe++"/>
  522. <div @click="--maybe"/>
  523. <div @click="lett++"/>
  524. <div @click="--lett"/>
  525. </template>
  526. `,
  527. { inlineTemplate: true },
  528. )
  529. // known const ref: set value
  530. expect(content).toMatch(`count.value++`)
  531. expect(content).toMatch(`--count.value`)
  532. // const but maybe ref (non-ref case ignored)
  533. expect(content).toMatch(`maybe.value++`)
  534. expect(content).toMatch(`--maybe.value`)
  535. // let: handle both cases
  536. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  537. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  538. assertCode(content)
  539. })
  540. test('template destructure assignment codegen', () => {
  541. const { content } = compile(
  542. `<script setup>
  543. import { ref } from 'vue'
  544. const val = {}
  545. const count = ref(0)
  546. const maybe = foo()
  547. let lett = 1
  548. </script>
  549. <template>
  550. <div @click="({ count } = val)"/>
  551. <div @click="[maybe] = val"/>
  552. <div @click="({ lett } = val)"/>
  553. </template>
  554. `,
  555. { inlineTemplate: true },
  556. )
  557. // known const ref: set value
  558. expect(content).toMatch(`({ count: count.value } = val)`)
  559. // const but maybe ref (non-ref case ignored)
  560. expect(content).toMatch(`[maybe.value] = val`)
  561. // let: assumes non-ref
  562. expect(content).toMatch(`{ lett: lett } = val`)
  563. assertCode(content)
  564. })
  565. test('ssr codegen', () => {
  566. const { content } = compile(
  567. `
  568. <script setup>
  569. import { ref } from 'vue'
  570. const count = ref(0)
  571. const style = { color: 'red' }
  572. </script>
  573. <template>
  574. <div>{{ count }}</div>
  575. <div>static</div>
  576. </template>
  577. <style>
  578. div { color: v-bind(count) }
  579. span { color: v-bind(style.color) }
  580. </style>
  581. `,
  582. {
  583. inlineTemplate: true,
  584. templateOptions: {
  585. ssr: true,
  586. },
  587. },
  588. )
  589. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  590. expect(content).toMatch(`return (_ctx, _push`)
  591. expect(content).toMatch(`ssrInterpolate`)
  592. expect(content).not.toMatch(`useCssVars`)
  593. expect(content).toMatch(`"--${mockId}-count": (count.value)`)
  594. expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
  595. assertCode(content)
  596. })
  597. test('the v-for wrapped in parentheses can be correctly parsed & inline is false', () => {
  598. expect(() =>
  599. compile(
  600. `
  601. <script setup lang="ts">
  602. import { ref } from 'vue'
  603. const stacks = ref([])
  604. </script>
  605. <template>
  606. <div v-for="({ file: efile }) of stacks"></div>
  607. </template>
  608. `,
  609. {
  610. inlineTemplate: false,
  611. },
  612. ),
  613. ).not.toThrowError()
  614. })
  615. test('unref + new expression', () => {
  616. const { content } = compile(
  617. `
  618. <script setup>
  619. import Foo from './foo'
  620. </script>
  621. <template>
  622. <div>{{ new Foo() }}</div>
  623. <div>{{ new Foo.Bar() }}</div>
  624. </template>
  625. `,
  626. { inlineTemplate: true },
  627. )
  628. expect(content).toMatch(`new (_unref(Foo))()`)
  629. expect(content).toMatch(`new (_unref(Foo)).Bar()`)
  630. assertCode(content)
  631. })
  632. })
  633. describe('with TypeScript', () => {
  634. test('hoist type declarations', () => {
  635. const { content } = compile(`
  636. <script setup lang="ts">
  637. export interface Foo {}
  638. type Bar = {}
  639. </script>`)
  640. assertCode(content)
  641. })
  642. test('runtime Enum', () => {
  643. const { content, bindings } = compile(
  644. `<script setup lang="ts">
  645. enum Foo { A = 123 }
  646. </script>`,
  647. )
  648. assertCode(content)
  649. expect(bindings).toStrictEqual({
  650. Foo: BindingTypes.LITERAL_CONST,
  651. })
  652. })
  653. test('runtime Enum in normal script', () => {
  654. const { content, bindings } = compile(
  655. `<script lang="ts">
  656. export enum D { D = "D" }
  657. const enum C { C = "C" }
  658. enum B { B = "B" }
  659. </script>
  660. <script setup lang="ts">
  661. enum Foo { A = 123 }
  662. </script>`,
  663. )
  664. assertCode(content)
  665. expect(bindings).toStrictEqual({
  666. D: BindingTypes.LITERAL_CONST,
  667. C: BindingTypes.LITERAL_CONST,
  668. B: BindingTypes.LITERAL_CONST,
  669. Foo: BindingTypes.LITERAL_CONST,
  670. })
  671. })
  672. test('const Enum', () => {
  673. const { content, bindings } = compile(
  674. `<script setup lang="ts">
  675. const enum Foo { A = 123 }
  676. </script>`,
  677. { hoistStatic: true },
  678. )
  679. assertCode(content)
  680. expect(bindings).toStrictEqual({
  681. Foo: BindingTypes.LITERAL_CONST,
  682. })
  683. })
  684. test('import type', () => {
  685. const { content } = compile(
  686. `<script setup lang="ts">
  687. import type { Foo } from './main.ts'
  688. import { type Bar, Baz } from './main.ts'
  689. </script>`,
  690. )
  691. expect(content).toMatch(`return { get Baz() { return Baz } }`)
  692. assertCode(content)
  693. })
  694. test('with generic attribute', () => {
  695. const { content } = compile(`
  696. <script setup lang="ts" generic="T extends Record<string,string>">
  697. type Bar = {}
  698. </script>`)
  699. assertCode(content)
  700. })
  701. })
  702. describe('async/await detection', () => {
  703. function assertAwaitDetection(code: string, shouldAsync = true) {
  704. const { content } = compile(`<script setup>${code}</script>`)
  705. if (shouldAsync) {
  706. expect(content).toMatch(`let __temp, __restore`)
  707. }
  708. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  709. assertCode(content)
  710. return content
  711. }
  712. test('expression statement', () => {
  713. assertAwaitDetection(`await foo`)
  714. })
  715. test('variable', () => {
  716. assertAwaitDetection(`const a = 1 + (await foo)`)
  717. })
  718. test('ref', () => {
  719. assertAwaitDetection(`let a = ref(1 + (await foo))`)
  720. })
  721. // #4448
  722. test('nested await', () => {
  723. assertAwaitDetection(`await (await foo)`)
  724. assertAwaitDetection(`await ((await foo))`)
  725. assertAwaitDetection(`await (await (await foo))`)
  726. })
  727. // should prepend semicolon
  728. test('nested leading await in expression statement', () => {
  729. const code = assertAwaitDetection(`foo()\nawait 1 + await 2`)
  730. expect(code).toMatch(`foo()\n;(`)
  731. })
  732. // #4596 should NOT prepend semicolon
  733. test('single line conditions', () => {
  734. const code = assertAwaitDetection(`if (false) await foo()`)
  735. expect(code).not.toMatch(`if (false) ;(`)
  736. })
  737. test('nested statements', () => {
  738. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  739. })
  740. test('multiple `if` nested statements', () => {
  741. assertAwaitDetection(`if (ok) {
  742. let a = 'foo'
  743. await 0 + await 1
  744. await 2
  745. } else if (a) {
  746. await 10
  747. if (b) {
  748. await 0 + await 1
  749. } else {
  750. let a = 'foo'
  751. await 2
  752. }
  753. if (b) {
  754. await 3
  755. await 4
  756. }
  757. } else {
  758. await 5
  759. }`)
  760. })
  761. test('multiple `if while` nested statements', () => {
  762. assertAwaitDetection(`if (ok) {
  763. while (d) {
  764. await 5
  765. }
  766. while (d) {
  767. await 5
  768. await 6
  769. if (c) {
  770. let f = 10
  771. 10 + await 7
  772. } else {
  773. await 8
  774. await 9
  775. }
  776. }
  777. }`)
  778. })
  779. test('multiple `if for` nested statements', () => {
  780. assertAwaitDetection(`if (ok) {
  781. for (let a of [1,2,3]) {
  782. await a
  783. }
  784. for (let a of [1,2,3]) {
  785. await a
  786. await a
  787. }
  788. }`)
  789. })
  790. test('should ignore await inside functions', () => {
  791. // function declaration
  792. assertAwaitDetection(`async function foo() { await bar }`, false)
  793. // function expression
  794. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  795. // object method
  796. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  797. // class method
  798. assertAwaitDetection(
  799. `const cls = class Foo { async method() { await bar }}`,
  800. false,
  801. )
  802. })
  803. })
  804. describe('errors', () => {
  805. test('<script> and <script setup> must have same lang', () => {
  806. expect(() =>
  807. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
  808. ).toThrow(`<script> and <script setup> must have the same language type`)
  809. })
  810. const moduleErrorMsg = `cannot contain ES module exports`
  811. test('non-type named exports', () => {
  812. expect(() =>
  813. compile(`<script setup>
  814. export const a = 1
  815. </script>`),
  816. ).toThrow(moduleErrorMsg)
  817. expect(() =>
  818. compile(`<script setup>
  819. export * from './foo'
  820. </script>`),
  821. ).toThrow(moduleErrorMsg)
  822. expect(() =>
  823. compile(`<script setup>
  824. const bar = 1
  825. export { bar as default }
  826. </script>`),
  827. ).toThrow(moduleErrorMsg)
  828. })
  829. test('defineProps/Emit() referencing local var', () => {
  830. expect(() =>
  831. compile(`<script setup>
  832. let bar = 1
  833. defineProps({
  834. foo: {
  835. default: () => bar
  836. }
  837. })
  838. </script>`),
  839. ).toThrow(`cannot reference locally declared variables`)
  840. expect(() =>
  841. compile(`<script setup>
  842. let bar = 'hello'
  843. defineEmits([bar])
  844. </script>`),
  845. ).toThrow(`cannot reference locally declared variables`)
  846. // #4644
  847. expect(() =>
  848. compile(`
  849. <script>const bar = 1</script>
  850. <script setup>
  851. defineProps({
  852. foo: {
  853. default: () => bar
  854. }
  855. })
  856. </script>`),
  857. ).not.toThrow(`cannot reference locally declared variables`)
  858. })
  859. test('should allow defineProps/Emit() referencing scope var', () => {
  860. assertCode(
  861. compile(`<script setup>
  862. const bar = 1
  863. defineProps({
  864. foo: {
  865. default: bar => bar + 1
  866. }
  867. })
  868. defineEmits({
  869. foo: bar => bar > 1
  870. })
  871. </script>`).content,
  872. )
  873. })
  874. test('should allow defineProps/Emit() referencing imported binding', () => {
  875. assertCode(
  876. compile(`<script setup>
  877. import { bar } from './bar'
  878. defineProps({
  879. foo: {
  880. default: () => bar
  881. }
  882. })
  883. defineEmits({
  884. foo: () => bar > 1
  885. })
  886. </script>`).content,
  887. )
  888. })
  889. })
  890. })
  891. describe('SFC analyze <script> bindings', () => {
  892. it('can parse decorators syntax in typescript block', () => {
  893. const { scriptAst } = compile(`
  894. <script lang="ts">
  895. import { Options, Vue } from 'vue-class-component';
  896. @Options({
  897. components: {
  898. HelloWorld,
  899. },
  900. props: ['foo', 'bar']
  901. })
  902. export default class Home extends Vue {}
  903. </script>
  904. `)
  905. expect(scriptAst).toBeDefined()
  906. })
  907. it('recognizes props array declaration', () => {
  908. const { bindings } = compile(`
  909. <script>
  910. export default {
  911. props: ['foo', 'bar']
  912. }
  913. </script>
  914. `)
  915. expect(bindings).toStrictEqual({
  916. foo: BindingTypes.PROPS,
  917. bar: BindingTypes.PROPS,
  918. })
  919. expect(bindings!.__isScriptSetup).toBe(false)
  920. })
  921. it('recognizes props object declaration', () => {
  922. const { bindings } = compile(`
  923. <script>
  924. export default {
  925. props: {
  926. foo: String,
  927. bar: {
  928. type: String,
  929. },
  930. baz: null,
  931. qux: [String, Number]
  932. }
  933. }
  934. </script>
  935. `)
  936. expect(bindings).toStrictEqual({
  937. foo: BindingTypes.PROPS,
  938. bar: BindingTypes.PROPS,
  939. baz: BindingTypes.PROPS,
  940. qux: BindingTypes.PROPS,
  941. })
  942. expect(bindings!.__isScriptSetup).toBe(false)
  943. })
  944. it('recognizes setup return', () => {
  945. const { bindings } = compile(`
  946. <script>
  947. const bar = 2
  948. export default {
  949. setup() {
  950. return {
  951. foo: 1,
  952. bar
  953. }
  954. }
  955. }
  956. </script>
  957. `)
  958. expect(bindings).toStrictEqual({
  959. foo: BindingTypes.SETUP_MAYBE_REF,
  960. bar: BindingTypes.SETUP_MAYBE_REF,
  961. })
  962. expect(bindings!.__isScriptSetup).toBe(false)
  963. })
  964. it('recognizes exported vars', () => {
  965. const { bindings } = compile(`
  966. <script>
  967. export const foo = 2
  968. </script>
  969. <script setup>
  970. console.log(foo)
  971. </script>
  972. `)
  973. expect(bindings).toStrictEqual({
  974. foo: BindingTypes.LITERAL_CONST,
  975. })
  976. })
  977. it('recognizes async setup return', () => {
  978. const { bindings } = compile(`
  979. <script>
  980. const bar = 2
  981. export default {
  982. async setup() {
  983. return {
  984. foo: 1,
  985. bar
  986. }
  987. }
  988. }
  989. </script>
  990. `)
  991. expect(bindings).toStrictEqual({
  992. foo: BindingTypes.SETUP_MAYBE_REF,
  993. bar: BindingTypes.SETUP_MAYBE_REF,
  994. })
  995. expect(bindings!.__isScriptSetup).toBe(false)
  996. })
  997. it('recognizes data return', () => {
  998. const { bindings } = compile(`
  999. <script>
  1000. const bar = 2
  1001. export default {
  1002. data() {
  1003. return {
  1004. foo: null,
  1005. bar
  1006. }
  1007. }
  1008. }
  1009. </script>
  1010. `)
  1011. expect(bindings).toStrictEqual({
  1012. foo: BindingTypes.DATA,
  1013. bar: BindingTypes.DATA,
  1014. })
  1015. })
  1016. it('recognizes methods', () => {
  1017. const { bindings } = compile(`
  1018. <script>
  1019. export default {
  1020. methods: {
  1021. foo() {}
  1022. }
  1023. }
  1024. </script>
  1025. `)
  1026. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  1027. })
  1028. it('recognizes computeds', () => {
  1029. const { bindings } = compile(`
  1030. <script>
  1031. export default {
  1032. computed: {
  1033. foo() {},
  1034. bar: {
  1035. get() {},
  1036. set() {},
  1037. }
  1038. }
  1039. }
  1040. </script>
  1041. `)
  1042. expect(bindings).toStrictEqual({
  1043. foo: BindingTypes.OPTIONS,
  1044. bar: BindingTypes.OPTIONS,
  1045. })
  1046. })
  1047. it('recognizes injections array declaration', () => {
  1048. const { bindings } = compile(`
  1049. <script>
  1050. export default {
  1051. inject: ['foo', 'bar']
  1052. }
  1053. </script>
  1054. `)
  1055. expect(bindings).toStrictEqual({
  1056. foo: BindingTypes.OPTIONS,
  1057. bar: BindingTypes.OPTIONS,
  1058. })
  1059. })
  1060. it('recognizes injections object declaration', () => {
  1061. const { bindings } = compile(`
  1062. <script>
  1063. export default {
  1064. inject: {
  1065. foo: {},
  1066. bar: {},
  1067. }
  1068. }
  1069. </script>
  1070. `)
  1071. expect(bindings).toStrictEqual({
  1072. foo: BindingTypes.OPTIONS,
  1073. bar: BindingTypes.OPTIONS,
  1074. })
  1075. })
  1076. it('works for mixed bindings', () => {
  1077. const { bindings } = compile(`
  1078. <script>
  1079. export default {
  1080. inject: ['foo'],
  1081. props: {
  1082. bar: String,
  1083. },
  1084. setup() {
  1085. return {
  1086. baz: null,
  1087. }
  1088. },
  1089. data() {
  1090. return {
  1091. qux: null
  1092. }
  1093. },
  1094. methods: {
  1095. quux() {}
  1096. },
  1097. computed: {
  1098. quuz() {}
  1099. }
  1100. }
  1101. </script>
  1102. `)
  1103. expect(bindings).toStrictEqual({
  1104. foo: BindingTypes.OPTIONS,
  1105. bar: BindingTypes.PROPS,
  1106. baz: BindingTypes.SETUP_MAYBE_REF,
  1107. qux: BindingTypes.DATA,
  1108. quux: BindingTypes.OPTIONS,
  1109. quuz: BindingTypes.OPTIONS,
  1110. })
  1111. })
  1112. it('works for script setup', () => {
  1113. const { bindings } = compile(`
  1114. <script setup>
  1115. import { ref as r } from 'vue'
  1116. defineProps({
  1117. foo: String
  1118. })
  1119. const a = r(1)
  1120. let b = 2
  1121. const c = 3
  1122. const { d } = someFoo()
  1123. let { e } = someBar()
  1124. </script>
  1125. `)
  1126. expect(bindings).toStrictEqual({
  1127. r: BindingTypes.SETUP_CONST,
  1128. a: BindingTypes.SETUP_REF,
  1129. b: BindingTypes.SETUP_LET,
  1130. c: BindingTypes.LITERAL_CONST,
  1131. d: BindingTypes.SETUP_MAYBE_REF,
  1132. e: BindingTypes.SETUP_LET,
  1133. foo: BindingTypes.PROPS,
  1134. })
  1135. })
  1136. describe('auto name inference', () => {
  1137. test('basic', () => {
  1138. const { content } = compile(
  1139. `<script setup>const a = 1</script>
  1140. <template>{{ a }}</template>`,
  1141. undefined,
  1142. {
  1143. filename: 'FooBar.vue',
  1144. },
  1145. )
  1146. expect(content).toMatch(`export default {
  1147. __name: 'FooBar'`)
  1148. assertCode(content)
  1149. })
  1150. test('do not overwrite manual name (object)', () => {
  1151. const { content } = compile(
  1152. `<script>
  1153. export default {
  1154. name: 'Baz'
  1155. }
  1156. </script>
  1157. <script setup>const a = 1</script>
  1158. <template>{{ a }}</template>`,
  1159. undefined,
  1160. {
  1161. filename: 'FooBar.vue',
  1162. },
  1163. )
  1164. expect(content).not.toMatch(`name: 'FooBar'`)
  1165. expect(content).toMatch(`name: 'Baz'`)
  1166. assertCode(content)
  1167. })
  1168. test('do not overwrite manual name (call)', () => {
  1169. const { content } = compile(
  1170. `<script>
  1171. import { defineComponent } from 'vue'
  1172. export default defineComponent({
  1173. name: 'Baz'
  1174. })
  1175. </script>
  1176. <script setup>const a = 1</script>
  1177. <template>{{ a }}</template>`,
  1178. undefined,
  1179. {
  1180. filename: 'FooBar.vue',
  1181. },
  1182. )
  1183. expect(content).not.toMatch(`name: 'FooBar'`)
  1184. expect(content).toMatch(`name: 'Baz'`)
  1185. assertCode(content)
  1186. })
  1187. })
  1188. })
  1189. describe('SFC genDefaultAs', () => {
  1190. test('normal <script> only', () => {
  1191. const { content } = compile(
  1192. `<script>
  1193. export default {}
  1194. </script>`,
  1195. {
  1196. genDefaultAs: '_sfc_',
  1197. },
  1198. )
  1199. expect(content).not.toMatch('export default')
  1200. expect(content).toMatch(`const _sfc_ = {}`)
  1201. assertCode(content)
  1202. })
  1203. test('normal <script> w/ cssVars', () => {
  1204. const { content } = compile(
  1205. `<script>
  1206. export default {}
  1207. </script>
  1208. <style>
  1209. .foo { color: v-bind(x) }
  1210. </style>`,
  1211. {
  1212. genDefaultAs: '_sfc_',
  1213. },
  1214. )
  1215. expect(content).not.toMatch('export default')
  1216. expect(content).not.toMatch('__default__')
  1217. expect(content).toMatch(`const _sfc_ = {}`)
  1218. assertCode(content)
  1219. })
  1220. test('<script> + <script setup>', () => {
  1221. const { content } = compile(
  1222. `<script>
  1223. export default {}
  1224. </script>
  1225. <script setup>
  1226. const a = 1
  1227. </script>`,
  1228. {
  1229. genDefaultAs: '_sfc_',
  1230. },
  1231. )
  1232. expect(content).not.toMatch('export default')
  1233. expect(content).toMatch(
  1234. `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
  1235. )
  1236. assertCode(content)
  1237. })
  1238. test('<script> + <script setup>', () => {
  1239. const { content } = compile(
  1240. `<script>
  1241. export default {}
  1242. </script>
  1243. <script setup>
  1244. const a = 1
  1245. </script>`,
  1246. {
  1247. genDefaultAs: '_sfc_',
  1248. },
  1249. )
  1250. expect(content).not.toMatch('export default')
  1251. expect(content).toMatch(
  1252. `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
  1253. )
  1254. assertCode(content)
  1255. })
  1256. test('<script setup> only', () => {
  1257. const { content } = compile(
  1258. `<script setup>
  1259. const a = 1
  1260. </script>`,
  1261. {
  1262. genDefaultAs: '_sfc_',
  1263. },
  1264. )
  1265. expect(content).not.toMatch('export default')
  1266. expect(content).toMatch(`const _sfc_ = {\n setup`)
  1267. assertCode(content)
  1268. })
  1269. test('<script setup> only w/ ts', () => {
  1270. const { content } = compile(
  1271. `<script setup lang="ts">
  1272. const a = 1
  1273. </script>`,
  1274. {
  1275. genDefaultAs: '_sfc_',
  1276. },
  1277. )
  1278. expect(content).not.toMatch('export default')
  1279. expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`)
  1280. assertCode(content)
  1281. })
  1282. test('<script> + <script setup> w/ ts', () => {
  1283. const { content } = compile(
  1284. `<script lang="ts">
  1285. export default {}
  1286. </script>
  1287. <script setup lang="ts">
  1288. const a = 1
  1289. </script>`,
  1290. {
  1291. genDefaultAs: '_sfc_',
  1292. },
  1293. )
  1294. expect(content).not.toMatch('export default')
  1295. expect(content).toMatch(
  1296. `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__`,
  1297. )
  1298. assertCode(content)
  1299. })
  1300. test('binding type for edge cases', () => {
  1301. const { bindings } = compile(
  1302. `<script setup lang="ts">
  1303. import { toRef } from 'vue'
  1304. const props = defineProps<{foo: string}>()
  1305. const foo = toRef(() => props.foo)
  1306. </script>`,
  1307. )
  1308. expect(bindings).toStrictEqual({
  1309. toRef: BindingTypes.SETUP_CONST,
  1310. props: BindingTypes.SETUP_REACTIVE_CONST,
  1311. foo: BindingTypes.SETUP_REF,
  1312. })
  1313. })
  1314. describe('parser plugins', () => {
  1315. test('import attributes', () => {
  1316. const { content } = compile(`
  1317. <script setup>
  1318. import { foo } from './foo.js' with { type: 'foobar' }
  1319. </script>
  1320. `)
  1321. assertCode(content)
  1322. expect(() =>
  1323. compile(`
  1324. <script setup>
  1325. import { foo } from './foo.js' assert { type: 'foobar' }
  1326. </script>`),
  1327. ).toThrow()
  1328. })
  1329. test('import attributes (user override for deprecated syntax)', () => {
  1330. const { content } = compile(
  1331. `
  1332. <script setup>
  1333. import { foo } from './foo.js' assert { type: 'foobar' }
  1334. </script>
  1335. `,
  1336. {
  1337. babelParserPlugins: [
  1338. ['importAttributes', { deprecatedAssertSyntax: true }],
  1339. ],
  1340. },
  1341. )
  1342. assertCode(content)
  1343. })
  1344. })
  1345. })