compileScript.spec.ts 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424
  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. })
  616. describe('with TypeScript', () => {
  617. test('hoist type declarations', () => {
  618. const { content } = compile(`
  619. <script setup lang="ts">
  620. export interface Foo {}
  621. type Bar = {}
  622. </script>`)
  623. assertCode(content)
  624. })
  625. test('runtime Enum', () => {
  626. const { content, bindings } = compile(
  627. `<script setup lang="ts">
  628. enum Foo { A = 123 }
  629. </script>`,
  630. )
  631. assertCode(content)
  632. expect(bindings).toStrictEqual({
  633. Foo: BindingTypes.LITERAL_CONST,
  634. })
  635. })
  636. test('runtime Enum in normal script', () => {
  637. const { content, bindings } = compile(
  638. `<script lang="ts">
  639. export enum D { D = "D" }
  640. const enum C { C = "C" }
  641. enum B { B = "B" }
  642. </script>
  643. <script setup lang="ts">
  644. enum Foo { A = 123 }
  645. </script>`,
  646. )
  647. assertCode(content)
  648. expect(bindings).toStrictEqual({
  649. D: BindingTypes.LITERAL_CONST,
  650. C: BindingTypes.LITERAL_CONST,
  651. B: BindingTypes.LITERAL_CONST,
  652. Foo: BindingTypes.LITERAL_CONST,
  653. })
  654. })
  655. test('const Enum', () => {
  656. const { content, bindings } = compile(
  657. `<script setup lang="ts">
  658. const enum Foo { A = 123 }
  659. </script>`,
  660. { hoistStatic: true },
  661. )
  662. assertCode(content)
  663. expect(bindings).toStrictEqual({
  664. Foo: BindingTypes.LITERAL_CONST,
  665. })
  666. })
  667. test('import type', () => {
  668. const { content } = compile(
  669. `<script setup lang="ts">
  670. import type { Foo } from './main.ts'
  671. import { type Bar, Baz } from './main.ts'
  672. </script>`,
  673. )
  674. expect(content).toMatch(`return { get Baz() { return Baz } }`)
  675. assertCode(content)
  676. })
  677. test('with generic attribute', () => {
  678. const { content } = compile(`
  679. <script setup lang="ts" generic="T extends Record<string,string>">
  680. type Bar = {}
  681. </script>`)
  682. assertCode(content)
  683. })
  684. })
  685. describe('async/await detection', () => {
  686. function assertAwaitDetection(code: string, shouldAsync = true) {
  687. const { content } = compile(`<script setup>${code}</script>`)
  688. if (shouldAsync) {
  689. expect(content).toMatch(`let __temp, __restore`)
  690. }
  691. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  692. assertCode(content)
  693. return content
  694. }
  695. test('expression statement', () => {
  696. assertAwaitDetection(`await foo`)
  697. })
  698. test('variable', () => {
  699. assertAwaitDetection(`const a = 1 + (await foo)`)
  700. })
  701. test('ref', () => {
  702. assertAwaitDetection(`let a = ref(1 + (await foo))`)
  703. })
  704. // #4448
  705. test('nested await', () => {
  706. assertAwaitDetection(`await (await foo)`)
  707. assertAwaitDetection(`await ((await foo))`)
  708. assertAwaitDetection(`await (await (await foo))`)
  709. })
  710. // should prepend semicolon
  711. test('nested leading await in expression statement', () => {
  712. const code = assertAwaitDetection(`foo()\nawait 1 + await 2`)
  713. expect(code).toMatch(`foo()\n;(`)
  714. })
  715. // #4596 should NOT prepend semicolon
  716. test('single line conditions', () => {
  717. const code = assertAwaitDetection(`if (false) await foo()`)
  718. expect(code).not.toMatch(`if (false) ;(`)
  719. })
  720. test('nested statements', () => {
  721. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  722. })
  723. test('multiple `if` nested statements', () => {
  724. assertAwaitDetection(`if (ok) {
  725. let a = 'foo'
  726. await 0 + await 1
  727. await 2
  728. } else if (a) {
  729. await 10
  730. if (b) {
  731. await 0 + await 1
  732. } else {
  733. let a = 'foo'
  734. await 2
  735. }
  736. if (b) {
  737. await 3
  738. await 4
  739. }
  740. } else {
  741. await 5
  742. }`)
  743. })
  744. test('multiple `if while` nested statements', () => {
  745. assertAwaitDetection(`if (ok) {
  746. while (d) {
  747. await 5
  748. }
  749. while (d) {
  750. await 5
  751. await 6
  752. if (c) {
  753. let f = 10
  754. 10 + await 7
  755. } else {
  756. await 8
  757. await 9
  758. }
  759. }
  760. }`)
  761. })
  762. test('multiple `if for` nested statements', () => {
  763. assertAwaitDetection(`if (ok) {
  764. for (let a of [1,2,3]) {
  765. await a
  766. }
  767. for (let a of [1,2,3]) {
  768. await a
  769. await a
  770. }
  771. }`)
  772. })
  773. test('should ignore await inside functions', () => {
  774. // function declaration
  775. assertAwaitDetection(`async function foo() { await bar }`, false)
  776. // function expression
  777. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  778. // object method
  779. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  780. // class method
  781. assertAwaitDetection(
  782. `const cls = class Foo { async method() { await bar }}`,
  783. false,
  784. )
  785. })
  786. })
  787. describe('errors', () => {
  788. test('<script> and <script setup> must have same lang', () => {
  789. expect(() =>
  790. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
  791. ).toThrow(`<script> and <script setup> must have the same language type`)
  792. })
  793. const moduleErrorMsg = `cannot contain ES module exports`
  794. test('non-type named exports', () => {
  795. expect(() =>
  796. compile(`<script setup>
  797. export const a = 1
  798. </script>`),
  799. ).toThrow(moduleErrorMsg)
  800. expect(() =>
  801. compile(`<script setup>
  802. export * from './foo'
  803. </script>`),
  804. ).toThrow(moduleErrorMsg)
  805. expect(() =>
  806. compile(`<script setup>
  807. const bar = 1
  808. export { bar as default }
  809. </script>`),
  810. ).toThrow(moduleErrorMsg)
  811. })
  812. test('defineProps/Emit() referencing local var', () => {
  813. expect(() =>
  814. compile(`<script setup>
  815. let bar = 1
  816. defineProps({
  817. foo: {
  818. default: () => bar
  819. }
  820. })
  821. </script>`),
  822. ).toThrow(`cannot reference locally declared variables`)
  823. expect(() =>
  824. compile(`<script setup>
  825. let bar = 'hello'
  826. defineEmits([bar])
  827. </script>`),
  828. ).toThrow(`cannot reference locally declared variables`)
  829. // #4644
  830. expect(() =>
  831. compile(`
  832. <script>const bar = 1</script>
  833. <script setup>
  834. defineProps({
  835. foo: {
  836. default: () => bar
  837. }
  838. })
  839. </script>`),
  840. ).not.toThrow(`cannot reference locally declared variables`)
  841. })
  842. test('should allow defineProps/Emit() referencing scope var', () => {
  843. assertCode(
  844. compile(`<script setup>
  845. const bar = 1
  846. defineProps({
  847. foo: {
  848. default: bar => bar + 1
  849. }
  850. })
  851. defineEmits({
  852. foo: bar => bar > 1
  853. })
  854. </script>`).content,
  855. )
  856. })
  857. test('should allow defineProps/Emit() referencing imported binding', () => {
  858. assertCode(
  859. compile(`<script setup>
  860. import { bar } from './bar'
  861. defineProps({
  862. foo: {
  863. default: () => bar
  864. }
  865. })
  866. defineEmits({
  867. foo: () => bar > 1
  868. })
  869. </script>`).content,
  870. )
  871. })
  872. })
  873. })
  874. describe('SFC analyze <script> bindings', () => {
  875. it('can parse decorators syntax in typescript block', () => {
  876. const { scriptAst } = compile(`
  877. <script lang="ts">
  878. import { Options, Vue } from 'vue-class-component';
  879. @Options({
  880. components: {
  881. HelloWorld,
  882. },
  883. props: ['foo', 'bar']
  884. })
  885. export default class Home extends Vue {}
  886. </script>
  887. `)
  888. expect(scriptAst).toBeDefined()
  889. })
  890. it('recognizes props array declaration', () => {
  891. const { bindings } = compile(`
  892. <script>
  893. export default {
  894. props: ['foo', 'bar']
  895. }
  896. </script>
  897. `)
  898. expect(bindings).toStrictEqual({
  899. foo: BindingTypes.PROPS,
  900. bar: BindingTypes.PROPS,
  901. })
  902. expect(bindings!.__isScriptSetup).toBe(false)
  903. })
  904. it('recognizes props object declaration', () => {
  905. const { bindings } = compile(`
  906. <script>
  907. export default {
  908. props: {
  909. foo: String,
  910. bar: {
  911. type: String,
  912. },
  913. baz: null,
  914. qux: [String, Number]
  915. }
  916. }
  917. </script>
  918. `)
  919. expect(bindings).toStrictEqual({
  920. foo: BindingTypes.PROPS,
  921. bar: BindingTypes.PROPS,
  922. baz: BindingTypes.PROPS,
  923. qux: BindingTypes.PROPS,
  924. })
  925. expect(bindings!.__isScriptSetup).toBe(false)
  926. })
  927. it('recognizes setup return', () => {
  928. const { bindings } = compile(`
  929. <script>
  930. const bar = 2
  931. export default {
  932. setup() {
  933. return {
  934. foo: 1,
  935. bar
  936. }
  937. }
  938. }
  939. </script>
  940. `)
  941. expect(bindings).toStrictEqual({
  942. foo: BindingTypes.SETUP_MAYBE_REF,
  943. bar: BindingTypes.SETUP_MAYBE_REF,
  944. })
  945. expect(bindings!.__isScriptSetup).toBe(false)
  946. })
  947. it('recognizes exported vars', () => {
  948. const { bindings } = compile(`
  949. <script>
  950. export const foo = 2
  951. </script>
  952. <script setup>
  953. console.log(foo)
  954. </script>
  955. `)
  956. expect(bindings).toStrictEqual({
  957. foo: BindingTypes.LITERAL_CONST,
  958. })
  959. })
  960. it('recognizes async setup return', () => {
  961. const { bindings } = compile(`
  962. <script>
  963. const bar = 2
  964. export default {
  965. async setup() {
  966. return {
  967. foo: 1,
  968. bar
  969. }
  970. }
  971. }
  972. </script>
  973. `)
  974. expect(bindings).toStrictEqual({
  975. foo: BindingTypes.SETUP_MAYBE_REF,
  976. bar: BindingTypes.SETUP_MAYBE_REF,
  977. })
  978. expect(bindings!.__isScriptSetup).toBe(false)
  979. })
  980. it('recognizes data return', () => {
  981. const { bindings } = compile(`
  982. <script>
  983. const bar = 2
  984. export default {
  985. data() {
  986. return {
  987. foo: null,
  988. bar
  989. }
  990. }
  991. }
  992. </script>
  993. `)
  994. expect(bindings).toStrictEqual({
  995. foo: BindingTypes.DATA,
  996. bar: BindingTypes.DATA,
  997. })
  998. })
  999. it('recognizes methods', () => {
  1000. const { bindings } = compile(`
  1001. <script>
  1002. export default {
  1003. methods: {
  1004. foo() {}
  1005. }
  1006. }
  1007. </script>
  1008. `)
  1009. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  1010. })
  1011. it('recognizes computeds', () => {
  1012. const { bindings } = compile(`
  1013. <script>
  1014. export default {
  1015. computed: {
  1016. foo() {},
  1017. bar: {
  1018. get() {},
  1019. set() {},
  1020. }
  1021. }
  1022. }
  1023. </script>
  1024. `)
  1025. expect(bindings).toStrictEqual({
  1026. foo: BindingTypes.OPTIONS,
  1027. bar: BindingTypes.OPTIONS,
  1028. })
  1029. })
  1030. it('recognizes injections array declaration', () => {
  1031. const { bindings } = compile(`
  1032. <script>
  1033. export default {
  1034. inject: ['foo', 'bar']
  1035. }
  1036. </script>
  1037. `)
  1038. expect(bindings).toStrictEqual({
  1039. foo: BindingTypes.OPTIONS,
  1040. bar: BindingTypes.OPTIONS,
  1041. })
  1042. })
  1043. it('recognizes injections object declaration', () => {
  1044. const { bindings } = compile(`
  1045. <script>
  1046. export default {
  1047. inject: {
  1048. foo: {},
  1049. bar: {},
  1050. }
  1051. }
  1052. </script>
  1053. `)
  1054. expect(bindings).toStrictEqual({
  1055. foo: BindingTypes.OPTIONS,
  1056. bar: BindingTypes.OPTIONS,
  1057. })
  1058. })
  1059. it('works for mixed bindings', () => {
  1060. const { bindings } = compile(`
  1061. <script>
  1062. export default {
  1063. inject: ['foo'],
  1064. props: {
  1065. bar: String,
  1066. },
  1067. setup() {
  1068. return {
  1069. baz: null,
  1070. }
  1071. },
  1072. data() {
  1073. return {
  1074. qux: null
  1075. }
  1076. },
  1077. methods: {
  1078. quux() {}
  1079. },
  1080. computed: {
  1081. quuz() {}
  1082. }
  1083. }
  1084. </script>
  1085. `)
  1086. expect(bindings).toStrictEqual({
  1087. foo: BindingTypes.OPTIONS,
  1088. bar: BindingTypes.PROPS,
  1089. baz: BindingTypes.SETUP_MAYBE_REF,
  1090. qux: BindingTypes.DATA,
  1091. quux: BindingTypes.OPTIONS,
  1092. quuz: BindingTypes.OPTIONS,
  1093. })
  1094. })
  1095. it('works for script setup', () => {
  1096. const { bindings } = compile(`
  1097. <script setup>
  1098. import { ref as r } from 'vue'
  1099. defineProps({
  1100. foo: String
  1101. })
  1102. const a = r(1)
  1103. let b = 2
  1104. const c = 3
  1105. const { d } = someFoo()
  1106. let { e } = someBar()
  1107. </script>
  1108. `)
  1109. expect(bindings).toStrictEqual({
  1110. r: BindingTypes.SETUP_CONST,
  1111. a: BindingTypes.SETUP_REF,
  1112. b: BindingTypes.SETUP_LET,
  1113. c: BindingTypes.LITERAL_CONST,
  1114. d: BindingTypes.SETUP_MAYBE_REF,
  1115. e: BindingTypes.SETUP_LET,
  1116. foo: BindingTypes.PROPS,
  1117. })
  1118. })
  1119. describe('auto name inference', () => {
  1120. test('basic', () => {
  1121. const { content } = compile(
  1122. `<script setup>const a = 1</script>
  1123. <template>{{ a }}</template>`,
  1124. undefined,
  1125. {
  1126. filename: 'FooBar.vue',
  1127. },
  1128. )
  1129. expect(content).toMatch(`export default {
  1130. __name: 'FooBar'`)
  1131. assertCode(content)
  1132. })
  1133. test('do not overwrite manual name (object)', () => {
  1134. const { content } = compile(
  1135. `<script>
  1136. export default {
  1137. name: 'Baz'
  1138. }
  1139. </script>
  1140. <script setup>const a = 1</script>
  1141. <template>{{ a }}</template>`,
  1142. undefined,
  1143. {
  1144. filename: 'FooBar.vue',
  1145. },
  1146. )
  1147. expect(content).not.toMatch(`name: 'FooBar'`)
  1148. expect(content).toMatch(`name: 'Baz'`)
  1149. assertCode(content)
  1150. })
  1151. test('do not overwrite manual name (call)', () => {
  1152. const { content } = compile(
  1153. `<script>
  1154. import { defineComponent } from 'vue'
  1155. export default defineComponent({
  1156. name: 'Baz'
  1157. })
  1158. </script>
  1159. <script setup>const a = 1</script>
  1160. <template>{{ a }}</template>`,
  1161. undefined,
  1162. {
  1163. filename: 'FooBar.vue',
  1164. },
  1165. )
  1166. expect(content).not.toMatch(`name: 'FooBar'`)
  1167. expect(content).toMatch(`name: 'Baz'`)
  1168. assertCode(content)
  1169. })
  1170. })
  1171. })
  1172. describe('SFC genDefaultAs', () => {
  1173. test('normal <script> only', () => {
  1174. const { content } = compile(
  1175. `<script>
  1176. export default {}
  1177. </script>`,
  1178. {
  1179. genDefaultAs: '_sfc_',
  1180. },
  1181. )
  1182. expect(content).not.toMatch('export default')
  1183. expect(content).toMatch(`const _sfc_ = {}`)
  1184. assertCode(content)
  1185. })
  1186. test('normal <script> w/ cssVars', () => {
  1187. const { content } = compile(
  1188. `<script>
  1189. export default {}
  1190. </script>
  1191. <style>
  1192. .foo { color: v-bind(x) }
  1193. </style>`,
  1194. {
  1195. genDefaultAs: '_sfc_',
  1196. },
  1197. )
  1198. expect(content).not.toMatch('export default')
  1199. expect(content).not.toMatch('__default__')
  1200. expect(content).toMatch(`const _sfc_ = {}`)
  1201. assertCode(content)
  1202. })
  1203. test('<script> + <script setup>', () => {
  1204. const { content } = compile(
  1205. `<script>
  1206. export default {}
  1207. </script>
  1208. <script setup>
  1209. const a = 1
  1210. </script>`,
  1211. {
  1212. genDefaultAs: '_sfc_',
  1213. },
  1214. )
  1215. expect(content).not.toMatch('export default')
  1216. expect(content).toMatch(
  1217. `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
  1218. )
  1219. assertCode(content)
  1220. })
  1221. test('<script> + <script setup>', () => {
  1222. const { content } = compile(
  1223. `<script>
  1224. export default {}
  1225. </script>
  1226. <script setup>
  1227. const a = 1
  1228. </script>`,
  1229. {
  1230. genDefaultAs: '_sfc_',
  1231. },
  1232. )
  1233. expect(content).not.toMatch('export default')
  1234. expect(content).toMatch(
  1235. `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
  1236. )
  1237. assertCode(content)
  1238. })
  1239. test('<script setup> only', () => {
  1240. const { content } = compile(
  1241. `<script setup>
  1242. const a = 1
  1243. </script>`,
  1244. {
  1245. genDefaultAs: '_sfc_',
  1246. },
  1247. )
  1248. expect(content).not.toMatch('export default')
  1249. expect(content).toMatch(`const _sfc_ = {\n setup`)
  1250. assertCode(content)
  1251. })
  1252. test('<script setup> only w/ ts', () => {
  1253. const { content } = compile(
  1254. `<script setup lang="ts">
  1255. const a = 1
  1256. </script>`,
  1257. {
  1258. genDefaultAs: '_sfc_',
  1259. },
  1260. )
  1261. expect(content).not.toMatch('export default')
  1262. expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`)
  1263. assertCode(content)
  1264. })
  1265. test('<script> + <script setup> w/ ts', () => {
  1266. const { content } = compile(
  1267. `<script lang="ts">
  1268. export default {}
  1269. </script>
  1270. <script setup lang="ts">
  1271. const a = 1
  1272. </script>`,
  1273. {
  1274. genDefaultAs: '_sfc_',
  1275. },
  1276. )
  1277. expect(content).not.toMatch('export default')
  1278. expect(content).toMatch(
  1279. `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__`,
  1280. )
  1281. assertCode(content)
  1282. })
  1283. test('binding type for edge cases', () => {
  1284. const { bindings } = compile(
  1285. `<script setup lang="ts">
  1286. import { toRef } from 'vue'
  1287. const props = defineProps<{foo: string}>()
  1288. const foo = toRef(() => props.foo)
  1289. </script>`,
  1290. )
  1291. expect(bindings).toStrictEqual({
  1292. toRef: BindingTypes.SETUP_CONST,
  1293. props: BindingTypes.SETUP_REACTIVE_CONST,
  1294. foo: BindingTypes.SETUP_REF,
  1295. })
  1296. })
  1297. describe('parser plugins', () => {
  1298. test('import attributes', () => {
  1299. const { content } = compile(`
  1300. <script setup>
  1301. import { foo } from './foo.js' with { type: 'foobar' }
  1302. </script>
  1303. `)
  1304. assertCode(content)
  1305. expect(() =>
  1306. compile(`
  1307. <script setup>
  1308. import { foo } from './foo.js' assert { type: 'foobar' }
  1309. </script>`),
  1310. ).toThrow()
  1311. })
  1312. test('import attributes (user override for deprecated syntax)', () => {
  1313. const { content } = compile(
  1314. `
  1315. <script setup>
  1316. import { foo } from './foo.js' assert { type: 'foobar' }
  1317. </script>
  1318. `,
  1319. {
  1320. babelParserPlugins: [
  1321. ['importAttributes', { deprecatedAssertSyntax: true }],
  1322. ],
  1323. },
  1324. )
  1325. assertCode(content)
  1326. })
  1327. })
  1328. })