compileScript.spec.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import { parse, compileScriptSetup, SFCScriptCompileOptions } from '../src'
  2. import { parse as babelParse } from '@babel/parser'
  3. import { babelParserDefautPlugins } from '@vue/shared'
  4. function compile(src: string, options?: SFCScriptCompileOptions) {
  5. const { descriptor } = parse(src)
  6. return compileScriptSetup(descriptor, options)
  7. }
  8. function assertCode(code: string) {
  9. // parse the generated code to make sure it is valid
  10. try {
  11. babelParse(code, {
  12. sourceType: 'module',
  13. plugins: [...babelParserDefautPlugins, 'typescript']
  14. })
  15. } catch (e) {
  16. console.log(code)
  17. throw e
  18. }
  19. expect(code).toMatchSnapshot()
  20. }
  21. describe('SFC compile <script setup>', () => {
  22. test('should hoist imports', () => {
  23. assertCode(compile(`<script setup>import { ref } from 'vue'</script>`).code)
  24. })
  25. test('explicit setup signature', () => {
  26. assertCode(
  27. compile(`<script setup="props, { emit }">emit('foo')</script>`).code
  28. )
  29. })
  30. test('import dedupe between <script> and <script setup>', () => {
  31. const code = compile(`
  32. <script>
  33. import { x } from './x'
  34. </script>
  35. <script setup>
  36. import { x } from './x'
  37. x()
  38. </script>
  39. `).code
  40. assertCode(code)
  41. expect(code.indexOf(`import { x }`)).toEqual(
  42. code.lastIndexOf(`import { x }`)
  43. )
  44. })
  45. describe('exports', () => {
  46. test('export const x = ...', () => {
  47. const { code, bindings } = compile(
  48. `<script setup>export const x = 1</script>`
  49. )
  50. assertCode(code)
  51. expect(bindings).toStrictEqual({
  52. x: 'setup'
  53. })
  54. })
  55. test('export const { x } = ... (destructuring)', () => {
  56. const { code, bindings } = compile(`<script setup>
  57. export const [a = 1, { b } = { b: 123 }, ...c] = useFoo()
  58. export const { d = 2, _: [e], ...f } = useBar()
  59. </script>`)
  60. assertCode(code)
  61. expect(bindings).toStrictEqual({
  62. a: 'setup',
  63. b: 'setup',
  64. c: 'setup',
  65. d: 'setup',
  66. e: 'setup',
  67. f: 'setup'
  68. })
  69. })
  70. test('export function x() {}', () => {
  71. const { code, bindings } = compile(
  72. `<script setup>export function x(){}</script>`
  73. )
  74. assertCode(code)
  75. expect(bindings).toStrictEqual({
  76. x: 'setup'
  77. })
  78. })
  79. test('export class X() {}', () => {
  80. const { code, bindings } = compile(
  81. `<script setup>export class X {}</script>`
  82. )
  83. assertCode(code)
  84. expect(bindings).toStrictEqual({
  85. X: 'setup'
  86. })
  87. })
  88. test('export { x }', () => {
  89. const { code, bindings } = compile(
  90. `<script setup>
  91. const x = 1
  92. const y = 2
  93. export { x, y }
  94. </script>`
  95. )
  96. assertCode(code)
  97. expect(bindings).toStrictEqual({
  98. x: 'setup',
  99. y: 'setup'
  100. })
  101. })
  102. test(`export { x } from './x'`, () => {
  103. const { code, bindings } = compile(
  104. `<script setup>
  105. export { x, y } from './x'
  106. </script>`
  107. )
  108. assertCode(code)
  109. expect(bindings).toStrictEqual({
  110. x: 'setup',
  111. y: 'setup'
  112. })
  113. })
  114. test(`export default from './x'`, () => {
  115. const { code, bindings } = compile(
  116. `<script setup>
  117. export default from './x'
  118. </script>`,
  119. {
  120. parserPlugins: ['exportDefaultFrom']
  121. }
  122. )
  123. assertCode(code)
  124. expect(bindings).toStrictEqual({})
  125. })
  126. test(`export { x as default }`, () => {
  127. const { code, bindings } = compile(
  128. `<script setup>
  129. import x from './x'
  130. const y = 1
  131. export { x as default, y }
  132. </script>`
  133. )
  134. assertCode(code)
  135. expect(bindings).toStrictEqual({
  136. y: 'setup'
  137. })
  138. })
  139. test(`export { x as default } from './x'`, () => {
  140. const { code, bindings } = compile(
  141. `<script setup>
  142. export { x as default, y } from './x'
  143. </script>`
  144. )
  145. assertCode(code)
  146. expect(bindings).toStrictEqual({
  147. y: 'setup'
  148. })
  149. })
  150. test(`export * from './x'`, () => {
  151. const { code, bindings } = compile(
  152. `<script setup>
  153. export * from './x'
  154. export const y = 1
  155. </script>`
  156. )
  157. assertCode(code)
  158. expect(bindings).toStrictEqual({
  159. y: 'setup'
  160. // in this case we cannot extract bindings from ./x so it falls back
  161. // to runtime proxy dispatching
  162. })
  163. })
  164. test('export default in <script setup>', () => {
  165. const { code, bindings } = compile(
  166. `<script setup>
  167. export default {
  168. props: ['foo']
  169. }
  170. export const y = 1
  171. </script>`
  172. )
  173. assertCode(code)
  174. expect(bindings).toStrictEqual({
  175. y: 'setup'
  176. })
  177. })
  178. })
  179. describe('<script setup lang="ts">', () => {
  180. test('hoist type declarations', () => {
  181. const { code, bindings } = compile(`
  182. <script setup lang="ts">
  183. export interface Foo {}
  184. type Bar = {}
  185. export const a = 1
  186. </script>`)
  187. assertCode(code)
  188. expect(bindings).toStrictEqual({ a: 'setup' })
  189. })
  190. test('extract props', () => {})
  191. test('extract emits', () => {})
  192. })
  193. describe('errors', () => {
  194. test('must have <script setup>', () => {
  195. expect(() => compile(`<script>foo()</script>`)).toThrow(
  196. `SFC has no <script setup>`
  197. )
  198. })
  199. test('<script> and <script setup> must have same lang', () => {
  200. expect(() =>
  201. compile(`<script>foo()</script><script setup lang="ts">bar()</script>`)
  202. ).toThrow(`<script> and <script setup> must have the same language type`)
  203. })
  204. test('export local as default', () => {
  205. expect(() =>
  206. compile(`<script setup>
  207. const bar = 1
  208. export { bar as default }
  209. </script>`)
  210. ).toThrow(`Cannot export locally defined variable as default`)
  211. })
  212. test('export default referencing local var', () => {
  213. expect(() =>
  214. compile(`<script setup>
  215. const bar = 1
  216. export default {
  217. props: {
  218. foo: {
  219. default: () => bar
  220. }
  221. }
  222. }
  223. </script>`)
  224. ).toThrow(`cannot reference locally declared variables`)
  225. })
  226. test('export default referencing exports', () => {
  227. expect(() =>
  228. compile(`<script setup>
  229. export const bar = 1
  230. export default {
  231. props: bar
  232. }
  233. </script>`)
  234. ).toThrow(`cannot reference locally declared variables`)
  235. })
  236. test('should allow export default referencing scope var', () => {
  237. assertCode(
  238. compile(`<script setup>
  239. const bar = 1
  240. export default {
  241. props: {
  242. foo: {
  243. default: bar => bar + 1
  244. }
  245. }
  246. }
  247. </script>`).code
  248. )
  249. })
  250. test('should allow export default referencing imported binding', () => {
  251. assertCode(
  252. compile(`<script setup>
  253. import { bar } from './bar'
  254. export { bar }
  255. export default {
  256. props: {
  257. foo: {
  258. default: () => bar
  259. }
  260. }
  261. }
  262. </script>`).code
  263. )
  264. })
  265. test('should allow export default referencing re-exported binding', () => {
  266. assertCode(
  267. compile(`<script setup>
  268. export { bar } from './bar'
  269. export default {
  270. props: {
  271. foo: {
  272. default: () => bar
  273. }
  274. }
  275. }
  276. </script>`).code
  277. )
  278. })
  279. test('error on duplicated defalut export', () => {
  280. expect(() =>
  281. compile(`
  282. <script>
  283. export default {}
  284. </script>
  285. <script setup>
  286. export default {}
  287. </script>
  288. `)
  289. ).toThrow(`Default export is already declared`)
  290. expect(() =>
  291. compile(`
  292. <script>
  293. export default {}
  294. </script>
  295. <script setup>
  296. const x = {}
  297. export { x as default }
  298. </script>
  299. `)
  300. ).toThrow(`Default export is already declared`)
  301. expect(() =>
  302. compile(`
  303. <script>
  304. export default {}
  305. </script>
  306. <script setup>
  307. export { x as default } from './y'
  308. </script>
  309. `)
  310. ).toThrow(`Default export is already declared`)
  311. expect(() =>
  312. compile(`
  313. <script>
  314. export { x as default } from './y'
  315. </script>
  316. <script setup>
  317. export default {}
  318. </script>
  319. `)
  320. ).toThrow(`Default export is already declared`)
  321. expect(() =>
  322. compile(`
  323. <script>
  324. const x = {}
  325. export { x as default }
  326. </script>
  327. <script setup>
  328. export default {}
  329. </script>
  330. `)
  331. ).toThrow(`Default export is already declared`)
  332. })
  333. })
  334. })