compileScript.spec.ts 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519
  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 w/ newlines codegen', () => {
  443. const { content } = compile(
  444. `<script setup>
  445. const count = ref(0)
  446. </script>
  447. <template>
  448. <input v-model="
  449. count
  450. ">
  451. </template>
  452. `,
  453. { inlineTemplate: true },
  454. )
  455. expect(content).toMatch(`_isRef(count) ? (count).value = $event : null`)
  456. assertCode(content)
  457. })
  458. test('v-model should not generate ref assignment code for non-setup bindings', () => {
  459. const { content } = compile(
  460. `<script setup>
  461. import { ref } from 'vue'
  462. const count = ref(0)
  463. </script>
  464. <script>
  465. export default {
  466. data() { return { foo: 123 } }
  467. }
  468. </script>
  469. <template>
  470. <input v-model="foo">
  471. </template>
  472. `,
  473. { inlineTemplate: true },
  474. )
  475. expect(content).not.toMatch(`_isRef(foo)`)
  476. })
  477. test('template assignment expression codegen', () => {
  478. const { content } = compile(
  479. `<script setup>
  480. import { ref } from 'vue'
  481. const count = ref(0)
  482. const maybe = foo()
  483. let lett = 1
  484. let v = ref(1)
  485. </script>
  486. <template>
  487. <div @click="count = 1"/>
  488. <div @click="maybe = count"/>
  489. <div @click="lett = count"/>
  490. <div @click="v += 1"/>
  491. <div @click="v -= 1"/>
  492. <div @click="() => {
  493. let a = '' + lett
  494. v = a
  495. }"/>
  496. <div @click="() => {
  497. // nested scopes
  498. (()=>{
  499. let x = a
  500. (()=>{
  501. let z = x
  502. let z2 = z
  503. })
  504. let lz = z
  505. })
  506. v = a
  507. }"/>
  508. </template>
  509. `,
  510. { inlineTemplate: true },
  511. )
  512. // known const ref: set value
  513. expect(content).toMatch(`count.value = 1`)
  514. // const but maybe ref: only assign after check
  515. expect(content).toMatch(`maybe.value = count.value`)
  516. // let: handle both cases
  517. expect(content).toMatch(
  518. `_isRef(lett) ? lett.value = count.value : lett = count.value`,
  519. )
  520. expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
  521. expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
  522. expect(content).toMatch(`_isRef(v) ? v.value = a : v = a`)
  523. expect(content).toMatch(`_isRef(v) ? v.value = _ctx.a : v = _ctx.a`)
  524. assertCode(content)
  525. })
  526. test('template update expression codegen', () => {
  527. const { content } = compile(
  528. `<script setup>
  529. import { ref } from 'vue'
  530. const count = ref(0)
  531. const maybe = foo()
  532. let lett = 1
  533. </script>
  534. <template>
  535. <div @click="count++"/>
  536. <div @click="--count"/>
  537. <div @click="maybe++"/>
  538. <div @click="--maybe"/>
  539. <div @click="lett++"/>
  540. <div @click="--lett"/>
  541. </template>
  542. `,
  543. { inlineTemplate: true },
  544. )
  545. // known const ref: set value
  546. expect(content).toMatch(`count.value++`)
  547. expect(content).toMatch(`--count.value`)
  548. // const but maybe ref (non-ref case ignored)
  549. expect(content).toMatch(`maybe.value++`)
  550. expect(content).toMatch(`--maybe.value`)
  551. // let: handle both cases
  552. expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
  553. expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
  554. assertCode(content)
  555. })
  556. test('template destructure assignment codegen', () => {
  557. const { content } = compile(
  558. `<script setup>
  559. import { ref } from 'vue'
  560. const val = {}
  561. const count = ref(0)
  562. const maybe = foo()
  563. let lett = 1
  564. </script>
  565. <template>
  566. <div @click="({ count } = val)"/>
  567. <div @click="[maybe] = val"/>
  568. <div @click="({ lett } = val)"/>
  569. </template>
  570. `,
  571. { inlineTemplate: true },
  572. )
  573. // known const ref: set value
  574. expect(content).toMatch(`({ count: count.value } = val)`)
  575. // const but maybe ref (non-ref case ignored)
  576. expect(content).toMatch(`[maybe.value] = val`)
  577. // let: assumes non-ref
  578. expect(content).toMatch(`{ lett: lett } = val`)
  579. assertCode(content)
  580. })
  581. test('ssr codegen', () => {
  582. const { content } = compile(
  583. `
  584. <script setup>
  585. import { ref } from 'vue'
  586. const count = ref(0)
  587. const style = { color: 'red' }
  588. const height = ref(0)
  589. </script>
  590. <template>
  591. <div>{{ count }}</div>
  592. <div>static</div>
  593. </template>
  594. <style>
  595. div { color: v-bind(count) }
  596. span { color: v-bind(style.color) }
  597. span { color: v-bind(height + "px") }
  598. </style>
  599. `,
  600. {
  601. inlineTemplate: true,
  602. templateOptions: {
  603. ssr: true,
  604. },
  605. },
  606. )
  607. expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
  608. expect(content).toMatch(`return (_ctx, _push`)
  609. expect(content).toMatch(`ssrInterpolate`)
  610. expect(content).not.toMatch(`useCssVars`)
  611. expect(content).toMatch(`"--${mockId}-count": (count.value)`)
  612. expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
  613. expect(content).toMatch(
  614. `"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
  615. )
  616. assertCode(content)
  617. })
  618. test('the v-for wrapped in parentheses can be correctly parsed & inline is false', () => {
  619. expect(() =>
  620. compile(
  621. `
  622. <script setup lang="ts">
  623. import { ref } from 'vue'
  624. const stacks = ref([])
  625. </script>
  626. <template>
  627. <div v-for="({ file: efile }) of stacks"></div>
  628. </template>
  629. `,
  630. {
  631. inlineTemplate: false,
  632. },
  633. ),
  634. ).not.toThrowError()
  635. })
  636. test('unref + new expression', () => {
  637. const { content } = compile(
  638. `
  639. <script setup>
  640. import Foo from './foo'
  641. </script>
  642. <template>
  643. <div>{{ new Foo() }}</div>
  644. <div>{{ new Foo.Bar() }}</div>
  645. </template>
  646. `,
  647. { inlineTemplate: true },
  648. )
  649. expect(content).toMatch(`new (_unref(Foo))()`)
  650. expect(content).toMatch(`new (_unref(Foo)).Bar()`)
  651. assertCode(content)
  652. })
  653. })
  654. describe('with TypeScript', () => {
  655. test('hoist type declarations', () => {
  656. const { content } = compile(`
  657. <script setup lang="ts">
  658. export interface Foo {}
  659. type Bar = {}
  660. </script>`)
  661. assertCode(content)
  662. })
  663. test('runtime Enum', () => {
  664. const { content, bindings } = compile(
  665. `<script setup lang="ts">
  666. enum Foo { A = 123 }
  667. </script>`,
  668. )
  669. assertCode(content)
  670. expect(bindings).toStrictEqual({
  671. Foo: BindingTypes.LITERAL_CONST,
  672. })
  673. })
  674. test('runtime Enum in normal script', () => {
  675. const { content, bindings } = compile(
  676. `<script lang="ts">
  677. export enum D { D = "D" }
  678. const enum C { C = "C" }
  679. enum B { B = "B" }
  680. </script>
  681. <script setup lang="ts">
  682. enum Foo { A = 123 }
  683. </script>`,
  684. )
  685. assertCode(content)
  686. expect(bindings).toStrictEqual({
  687. D: BindingTypes.LITERAL_CONST,
  688. C: BindingTypes.LITERAL_CONST,
  689. B: BindingTypes.LITERAL_CONST,
  690. Foo: BindingTypes.LITERAL_CONST,
  691. })
  692. })
  693. test('const Enum', () => {
  694. const { content, bindings } = compile(
  695. `<script setup lang="ts">
  696. const enum Foo { A = 123 }
  697. </script>`,
  698. { hoistStatic: true },
  699. )
  700. assertCode(content)
  701. expect(bindings).toStrictEqual({
  702. Foo: BindingTypes.LITERAL_CONST,
  703. })
  704. })
  705. test('import type', () => {
  706. const { content } = compile(
  707. `<script setup lang="ts">
  708. import type { Foo } from './main.ts'
  709. import { type Bar, Baz } from './main.ts'
  710. </script>`,
  711. )
  712. expect(content).toMatch(`return { get Baz() { return Baz } }`)
  713. assertCode(content)
  714. })
  715. test('with generic attribute', () => {
  716. const { content } = compile(`
  717. <script setup lang="ts" generic="T extends Record<string,string>">
  718. type Bar = {}
  719. </script>`)
  720. assertCode(content)
  721. })
  722. })
  723. describe('async/await detection', () => {
  724. function assertAwaitDetection(code: string, shouldAsync = true) {
  725. const { content } = compile(`<script setup>${code}</script>`)
  726. if (shouldAsync) {
  727. expect(content).toMatch(`let __temp, __restore`)
  728. }
  729. expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
  730. assertCode(content)
  731. return content
  732. }
  733. test('expression statement', () => {
  734. assertAwaitDetection(`await foo`)
  735. })
  736. test('variable', () => {
  737. assertAwaitDetection(`const a = 1 + (await foo)`)
  738. })
  739. test('ref', () => {
  740. assertAwaitDetection(`let a = ref(1 + (await foo))`)
  741. })
  742. // #4448
  743. test('nested await', () => {
  744. assertAwaitDetection(`await (await foo)`)
  745. assertAwaitDetection(`await ((await foo))`)
  746. assertAwaitDetection(`await (await (await foo))`)
  747. })
  748. // should prepend semicolon
  749. test('nested leading await in expression statement', () => {
  750. const code = assertAwaitDetection(`foo()\nawait 1 + await 2`)
  751. expect(code).toMatch(`foo()\n;(`)
  752. })
  753. // #4596 should NOT prepend semicolon
  754. test('single line conditions', () => {
  755. const code = assertAwaitDetection(`if (false) await foo()`)
  756. expect(code).not.toMatch(`if (false) ;(`)
  757. })
  758. test('nested statements', () => {
  759. assertAwaitDetection(`if (ok) { await foo } else { await bar }`)
  760. })
  761. test('multiple `if` nested statements', () => {
  762. assertAwaitDetection(`if (ok) {
  763. let a = 'foo'
  764. await 0 + await 1
  765. await 2
  766. } else if (a) {
  767. await 10
  768. if (b) {
  769. await 0 + await 1
  770. } else {
  771. let a = 'foo'
  772. await 2
  773. }
  774. if (b) {
  775. await 3
  776. await 4
  777. }
  778. } else {
  779. await 5
  780. }`)
  781. })
  782. test('multiple `if while` nested statements', () => {
  783. assertAwaitDetection(`if (ok) {
  784. while (d) {
  785. await 5
  786. }
  787. while (d) {
  788. await 5
  789. await 6
  790. if (c) {
  791. let f = 10
  792. 10 + await 7
  793. } else {
  794. await 8
  795. await 9
  796. }
  797. }
  798. }`)
  799. })
  800. test('multiple `if for` nested statements', () => {
  801. assertAwaitDetection(`if (ok) {
  802. for (let a of [1,2,3]) {
  803. await a
  804. }
  805. for (let a of [1,2,3]) {
  806. await a
  807. await a
  808. }
  809. }`)
  810. })
  811. test('should ignore await inside functions', () => {
  812. // function declaration
  813. assertAwaitDetection(`async function foo() { await bar }`, false)
  814. // function expression
  815. assertAwaitDetection(`const foo = async () => { await bar }`, false)
  816. // object method
  817. assertAwaitDetection(`const obj = { async method() { await bar }}`, false)
  818. // class method
  819. assertAwaitDetection(
  820. `const cls = class Foo { async method() { await bar }}`,
  821. false,
  822. )
  823. })
  824. })
  825. describe('errors', () => {
  826. test('<script> and <script setup> must have same lang', () => {
  827. expect(() =>
  828. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
  829. ).toThrow(`<script> and <script setup> must have the same language type`)
  830. })
  831. const moduleErrorMsg = `cannot contain ES module exports`
  832. test('non-type named exports', () => {
  833. expect(() =>
  834. compile(`<script setup>
  835. export const a = 1
  836. </script>`),
  837. ).toThrow(moduleErrorMsg)
  838. expect(() =>
  839. compile(`<script setup>
  840. export * from './foo'
  841. </script>`),
  842. ).toThrow(moduleErrorMsg)
  843. expect(() =>
  844. compile(`<script setup>
  845. const bar = 1
  846. export { bar as default }
  847. </script>`),
  848. ).toThrow(moduleErrorMsg)
  849. })
  850. test('defineProps/Emit() referencing local var', () => {
  851. expect(() =>
  852. compile(`<script setup>
  853. let bar = 1
  854. defineProps({
  855. foo: {
  856. default: () => bar
  857. }
  858. })
  859. </script>`),
  860. ).toThrow(`cannot reference locally declared variables`)
  861. expect(() =>
  862. compile(`<script setup>
  863. let bar = 'hello'
  864. defineEmits([bar])
  865. </script>`),
  866. ).toThrow(`cannot reference locally declared variables`)
  867. // #4644
  868. expect(() =>
  869. compile(`
  870. <script>const bar = 1</script>
  871. <script setup>
  872. defineProps({
  873. foo: {
  874. default: () => bar
  875. }
  876. })
  877. </script>`),
  878. ).not.toThrow(`cannot reference locally declared variables`)
  879. })
  880. test('should allow defineProps/Emit() referencing scope var', () => {
  881. assertCode(
  882. compile(`<script setup>
  883. const bar = 1
  884. defineProps({
  885. foo: {
  886. default: bar => bar + 1
  887. }
  888. })
  889. defineEmits({
  890. foo: bar => bar > 1
  891. })
  892. </script>`).content,
  893. )
  894. })
  895. test('should allow defineProps/Emit() referencing imported binding', () => {
  896. assertCode(
  897. compile(`<script setup>
  898. import { bar } from './bar'
  899. defineProps({
  900. foo: {
  901. default: () => bar
  902. }
  903. })
  904. defineEmits({
  905. foo: () => bar > 1
  906. })
  907. </script>`).content,
  908. )
  909. })
  910. test('defineModel() referencing local var', () => {
  911. expect(() =>
  912. compile(`<script setup>
  913. let bar = 1
  914. defineModel({
  915. default: () => bar
  916. })
  917. </script>`),
  918. ).toThrow(`cannot reference locally declared variables`)
  919. // allow const
  920. expect(() =>
  921. compile(`<script setup>
  922. const bar = 1
  923. defineModel({
  924. default: () => bar
  925. })
  926. </script>`),
  927. ).not.toThrow(`cannot reference locally declared variables`)
  928. // allow in get/set
  929. expect(() =>
  930. compile(`<script setup>
  931. let bar = 1
  932. defineModel({
  933. get: () => bar,
  934. set: () => bar
  935. })
  936. </script>`),
  937. ).not.toThrow(`cannot reference locally declared variables`)
  938. })
  939. })
  940. })
  941. describe('SFC analyze <script> bindings', () => {
  942. it('can parse decorators syntax in typescript block', () => {
  943. const { scriptAst } = compile(`
  944. <script lang="ts">
  945. import { Options, Vue } from 'vue-class-component';
  946. @Options({
  947. components: {
  948. HelloWorld,
  949. },
  950. props: ['foo', 'bar']
  951. })
  952. export default class Home extends Vue {}
  953. </script>
  954. `)
  955. expect(scriptAst).toBeDefined()
  956. })
  957. it('recognizes props array declaration', () => {
  958. const { bindings } = compile(`
  959. <script>
  960. export default {
  961. props: ['foo', 'bar']
  962. }
  963. </script>
  964. `)
  965. expect(bindings).toStrictEqual({
  966. foo: BindingTypes.PROPS,
  967. bar: BindingTypes.PROPS,
  968. })
  969. expect(bindings!.__isScriptSetup).toBe(false)
  970. })
  971. it('recognizes props object declaration', () => {
  972. const { bindings } = compile(`
  973. <script>
  974. export default {
  975. props: {
  976. foo: String,
  977. bar: {
  978. type: String,
  979. },
  980. baz: null,
  981. qux: [String, Number]
  982. }
  983. }
  984. </script>
  985. `)
  986. expect(bindings).toStrictEqual({
  987. foo: BindingTypes.PROPS,
  988. bar: BindingTypes.PROPS,
  989. baz: BindingTypes.PROPS,
  990. qux: BindingTypes.PROPS,
  991. })
  992. expect(bindings!.__isScriptSetup).toBe(false)
  993. })
  994. it('recognizes setup return', () => {
  995. const { bindings } = compile(`
  996. <script>
  997. const bar = 2
  998. export default {
  999. setup() {
  1000. return {
  1001. foo: 1,
  1002. bar
  1003. }
  1004. }
  1005. }
  1006. </script>
  1007. `)
  1008. expect(bindings).toStrictEqual({
  1009. foo: BindingTypes.SETUP_MAYBE_REF,
  1010. bar: BindingTypes.SETUP_MAYBE_REF,
  1011. })
  1012. expect(bindings!.__isScriptSetup).toBe(false)
  1013. })
  1014. it('recognizes exported vars', () => {
  1015. const { bindings } = compile(`
  1016. <script>
  1017. export const foo = 2
  1018. </script>
  1019. <script setup>
  1020. console.log(foo)
  1021. </script>
  1022. `)
  1023. expect(bindings).toStrictEqual({
  1024. foo: BindingTypes.LITERAL_CONST,
  1025. })
  1026. })
  1027. it('recognizes async setup return', () => {
  1028. const { bindings } = compile(`
  1029. <script>
  1030. const bar = 2
  1031. export default {
  1032. async setup() {
  1033. return {
  1034. foo: 1,
  1035. bar
  1036. }
  1037. }
  1038. }
  1039. </script>
  1040. `)
  1041. expect(bindings).toStrictEqual({
  1042. foo: BindingTypes.SETUP_MAYBE_REF,
  1043. bar: BindingTypes.SETUP_MAYBE_REF,
  1044. })
  1045. expect(bindings!.__isScriptSetup).toBe(false)
  1046. })
  1047. it('recognizes data return', () => {
  1048. const { bindings } = compile(`
  1049. <script>
  1050. const bar = 2
  1051. export default {
  1052. data() {
  1053. return {
  1054. foo: null,
  1055. bar
  1056. }
  1057. }
  1058. }
  1059. </script>
  1060. `)
  1061. expect(bindings).toStrictEqual({
  1062. foo: BindingTypes.DATA,
  1063. bar: BindingTypes.DATA,
  1064. })
  1065. })
  1066. it('recognizes methods', () => {
  1067. const { bindings } = compile(`
  1068. <script>
  1069. export default {
  1070. methods: {
  1071. foo() {}
  1072. }
  1073. }
  1074. </script>
  1075. `)
  1076. expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
  1077. })
  1078. it('recognizes computeds', () => {
  1079. const { bindings } = compile(`
  1080. <script>
  1081. export default {
  1082. computed: {
  1083. foo() {},
  1084. bar: {
  1085. get() {},
  1086. set() {},
  1087. }
  1088. }
  1089. }
  1090. </script>
  1091. `)
  1092. expect(bindings).toStrictEqual({
  1093. foo: BindingTypes.OPTIONS,
  1094. bar: BindingTypes.OPTIONS,
  1095. })
  1096. })
  1097. it('recognizes injections array declaration', () => {
  1098. const { bindings } = compile(`
  1099. <script>
  1100. export default {
  1101. inject: ['foo', 'bar']
  1102. }
  1103. </script>
  1104. `)
  1105. expect(bindings).toStrictEqual({
  1106. foo: BindingTypes.OPTIONS,
  1107. bar: BindingTypes.OPTIONS,
  1108. })
  1109. })
  1110. it('recognizes injections object declaration', () => {
  1111. const { bindings } = compile(`
  1112. <script>
  1113. export default {
  1114. inject: {
  1115. foo: {},
  1116. bar: {},
  1117. }
  1118. }
  1119. </script>
  1120. `)
  1121. expect(bindings).toStrictEqual({
  1122. foo: BindingTypes.OPTIONS,
  1123. bar: BindingTypes.OPTIONS,
  1124. })
  1125. })
  1126. it('works for mixed bindings', () => {
  1127. const { bindings } = compile(`
  1128. <script>
  1129. export default {
  1130. inject: ['foo'],
  1131. props: {
  1132. bar: String,
  1133. },
  1134. setup() {
  1135. return {
  1136. baz: null,
  1137. }
  1138. },
  1139. data() {
  1140. return {
  1141. qux: null
  1142. }
  1143. },
  1144. methods: {
  1145. quux() {}
  1146. },
  1147. computed: {
  1148. quuz() {}
  1149. }
  1150. }
  1151. </script>
  1152. `)
  1153. expect(bindings).toStrictEqual({
  1154. foo: BindingTypes.OPTIONS,
  1155. bar: BindingTypes.PROPS,
  1156. baz: BindingTypes.SETUP_MAYBE_REF,
  1157. qux: BindingTypes.DATA,
  1158. quux: BindingTypes.OPTIONS,
  1159. quuz: BindingTypes.OPTIONS,
  1160. })
  1161. })
  1162. it('works for script setup', () => {
  1163. const { bindings } = compile(`
  1164. <script setup>
  1165. import { ref as r } from 'vue'
  1166. defineProps({
  1167. foo: String
  1168. })
  1169. const a = r(1)
  1170. let b = 2
  1171. const c = 3
  1172. const { d } = someFoo()
  1173. let { e } = someBar()
  1174. </script>
  1175. `)
  1176. expect(bindings).toStrictEqual({
  1177. r: BindingTypes.SETUP_CONST,
  1178. a: BindingTypes.SETUP_REF,
  1179. b: BindingTypes.SETUP_LET,
  1180. c: BindingTypes.LITERAL_CONST,
  1181. d: BindingTypes.SETUP_MAYBE_REF,
  1182. e: BindingTypes.SETUP_LET,
  1183. foo: BindingTypes.PROPS,
  1184. })
  1185. })
  1186. describe('auto name inference', () => {
  1187. test('basic', () => {
  1188. const { content } = compile(
  1189. `<script setup>const a = 1</script>
  1190. <template>{{ a }}</template>`,
  1191. undefined,
  1192. {
  1193. filename: 'FooBar.vue',
  1194. },
  1195. )
  1196. expect(content).toMatch(`export default {
  1197. __name: 'FooBar'`)
  1198. assertCode(content)
  1199. })
  1200. test('do not overwrite manual name (object)', () => {
  1201. const { content } = compile(
  1202. `<script>
  1203. export default {
  1204. name: 'Baz'
  1205. }
  1206. </script>
  1207. <script setup>const a = 1</script>
  1208. <template>{{ a }}</template>`,
  1209. undefined,
  1210. {
  1211. filename: 'FooBar.vue',
  1212. },
  1213. )
  1214. expect(content).not.toMatch(`name: 'FooBar'`)
  1215. expect(content).toMatch(`name: 'Baz'`)
  1216. assertCode(content)
  1217. })
  1218. test('do not overwrite manual name (call)', () => {
  1219. const { content } = compile(
  1220. `<script>
  1221. import { defineComponent } from 'vue'
  1222. export default defineComponent({
  1223. name: 'Baz'
  1224. })
  1225. </script>
  1226. <script setup>const a = 1</script>
  1227. <template>{{ a }}</template>`,
  1228. undefined,
  1229. {
  1230. filename: 'FooBar.vue',
  1231. },
  1232. )
  1233. expect(content).not.toMatch(`name: 'FooBar'`)
  1234. expect(content).toMatch(`name: 'Baz'`)
  1235. assertCode(content)
  1236. })
  1237. })
  1238. })
  1239. describe('SFC genDefaultAs', () => {
  1240. test('normal <script> only', () => {
  1241. const { content } = compile(
  1242. `<script>
  1243. export default {}
  1244. </script>`,
  1245. {
  1246. genDefaultAs: '_sfc_',
  1247. },
  1248. )
  1249. expect(content).not.toMatch('export default')
  1250. expect(content).toMatch(`const _sfc_ = {}`)
  1251. assertCode(content)
  1252. })
  1253. test('normal <script> w/ cssVars', () => {
  1254. const { content } = compile(
  1255. `<script>
  1256. export default {}
  1257. </script>
  1258. <style>
  1259. .foo { color: v-bind(x) }
  1260. </style>`,
  1261. {
  1262. genDefaultAs: '_sfc_',
  1263. },
  1264. )
  1265. expect(content).not.toMatch('export default')
  1266. expect(content).not.toMatch('__default__')
  1267. expect(content).toMatch(`const _sfc_ = {}`)
  1268. assertCode(content)
  1269. })
  1270. test('<script> + <script setup>', () => {
  1271. const { content } = compile(
  1272. `<script>
  1273. export default {}
  1274. </script>
  1275. <script setup>
  1276. const a = 1
  1277. </script>`,
  1278. {
  1279. genDefaultAs: '_sfc_',
  1280. },
  1281. )
  1282. expect(content).not.toMatch('export default')
  1283. expect(content).toMatch(
  1284. `const _sfc_ = /*@__PURE__*/Object.assign(__default__`,
  1285. )
  1286. assertCode(content)
  1287. })
  1288. test('<script> + <script setup>', () => {
  1289. const { content } = compile(
  1290. `<script>
  1291. export default {}
  1292. </script>
  1293. <script setup>
  1294. const a = 1
  1295. </script>`,
  1296. {
  1297. genDefaultAs: '_sfc_',
  1298. },
  1299. )
  1300. expect(content).not.toMatch('export default')
  1301. expect(content).toMatch(
  1302. `const _sfc_ = /*@__PURE__*/Object.assign(__default__`,
  1303. )
  1304. assertCode(content)
  1305. })
  1306. test('<script setup> only', () => {
  1307. const { content } = compile(
  1308. `<script setup>
  1309. const a = 1
  1310. </script>`,
  1311. {
  1312. genDefaultAs: '_sfc_',
  1313. },
  1314. )
  1315. expect(content).not.toMatch('export default')
  1316. expect(content).toMatch(`const _sfc_ = {\n setup`)
  1317. assertCode(content)
  1318. })
  1319. test('<script setup> only w/ ts', () => {
  1320. const { content } = compile(
  1321. `<script setup lang="ts">
  1322. const a = 1
  1323. </script>`,
  1324. {
  1325. genDefaultAs: '_sfc_',
  1326. },
  1327. )
  1328. expect(content).not.toMatch('export default')
  1329. expect(content).toMatch(`const _sfc_ = /*@__PURE__*/_defineComponent(`)
  1330. assertCode(content)
  1331. })
  1332. test('<script> + <script setup> w/ ts', () => {
  1333. const { content } = compile(
  1334. `<script lang="ts">
  1335. export default {}
  1336. </script>
  1337. <script setup lang="ts">
  1338. const a = 1
  1339. </script>`,
  1340. {
  1341. genDefaultAs: '_sfc_',
  1342. },
  1343. )
  1344. expect(content).not.toMatch('export default')
  1345. expect(content).toMatch(
  1346. `const _sfc_ = /*@__PURE__*/_defineComponent({\n ...__default__`,
  1347. )
  1348. assertCode(content)
  1349. })
  1350. test('binding type for edge cases', () => {
  1351. const { bindings } = compile(
  1352. `<script setup lang="ts">
  1353. import { toRef } from 'vue'
  1354. const props = defineProps<{foo: string}>()
  1355. const foo = toRef(() => props.foo)
  1356. </script>`,
  1357. )
  1358. expect(bindings).toStrictEqual({
  1359. toRef: BindingTypes.SETUP_CONST,
  1360. props: BindingTypes.SETUP_REACTIVE_CONST,
  1361. foo: BindingTypes.SETUP_REF,
  1362. })
  1363. })
  1364. describe('parser plugins', () => {
  1365. test('import attributes', () => {
  1366. const { content } = compile(`
  1367. <script setup>
  1368. import { foo } from './foo.js' with { type: 'foobar' }
  1369. </script>
  1370. `)
  1371. assertCode(content)
  1372. expect(() =>
  1373. compile(`
  1374. <script setup>
  1375. import { foo } from './foo.js' assert { type: 'foobar' }
  1376. </script>`),
  1377. ).toThrow()
  1378. })
  1379. test('import attributes (user override for deprecated syntax)', () => {
  1380. const { content } = compile(
  1381. `
  1382. <script setup>
  1383. import { foo } from './foo.js' assert { type: 'foobar' }
  1384. </script>
  1385. `,
  1386. {
  1387. babelParserPlugins: [
  1388. ['importAttributes', { deprecatedAssertSyntax: true }],
  1389. ],
  1390. },
  1391. )
  1392. assertCode(content)
  1393. })
  1394. })
  1395. })
  1396. describe('compileScript', () => {
  1397. test('should care about runtimeModuleName', () => {
  1398. const { content } = compile(
  1399. `
  1400. <script setup>
  1401. await Promise.resolve(1)
  1402. </script>
  1403. `,
  1404. {
  1405. templateOptions: {
  1406. compilerOptions: {
  1407. runtimeModuleName: 'npm:vue',
  1408. },
  1409. },
  1410. },
  1411. )
  1412. expect(content).toMatch(
  1413. `import { withAsyncContext as _withAsyncContext } from "npm:vue"\n`,
  1414. )
  1415. assertCode(content)
  1416. })
  1417. })