compileScript.spec.ts 39 KB

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