codegen.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. import {
  2. generate,
  3. NodeTypes,
  4. RootNode,
  5. SourceLocation,
  6. createSimpleExpression,
  7. Namespaces,
  8. ElementTypes,
  9. CallExpression,
  10. createObjectExpression,
  11. createObjectProperty,
  12. createArrayExpression,
  13. ElementNode,
  14. createCompoundExpression,
  15. createInterpolation
  16. } from '../src'
  17. import {
  18. CREATE_VNODE,
  19. COMMENT,
  20. TO_STRING,
  21. RENDER_LIST
  22. } from '../src/runtimeConstants'
  23. const mockLoc: SourceLocation = {
  24. source: ``,
  25. start: {
  26. offset: 0,
  27. line: 1,
  28. column: 1
  29. },
  30. end: {
  31. offset: 3,
  32. line: 1,
  33. column: 4
  34. }
  35. }
  36. function createRoot(options: Partial<RootNode> = {}): RootNode {
  37. return {
  38. type: NodeTypes.ROOT,
  39. children: [],
  40. imports: [],
  41. statements: [],
  42. hoists: [],
  43. loc: mockLoc,
  44. ...options
  45. }
  46. }
  47. describe('compiler: codegen', () => {
  48. test('module mode preamble', () => {
  49. const root = createRoot({
  50. imports: [`helperOne`, `helperTwo`]
  51. })
  52. const { code } = generate(root, { mode: 'module' })
  53. expect(code).toMatch(`import { helperOne, helperTwo } from "vue"`)
  54. expect(code).toMatchSnapshot()
  55. })
  56. test('function mode preamble', () => {
  57. const root = createRoot({
  58. imports: [`helperOne`, `helperTwo`]
  59. })
  60. const { code } = generate(root, { mode: 'function' })
  61. expect(code).toMatch(`const _Vue = Vue`)
  62. expect(code).toMatch(
  63. `const { helperOne: _helperOne, helperTwo: _helperTwo } = _Vue`
  64. )
  65. expect(code).toMatchSnapshot()
  66. })
  67. test('function mode preamble w/ prefixIdentifiers: true', () => {
  68. const root = createRoot({
  69. imports: [`helperOne`, `helperTwo`]
  70. })
  71. const { code } = generate(root, {
  72. mode: 'function',
  73. prefixIdentifiers: true
  74. })
  75. expect(code).not.toMatch(`const _Vue = Vue`)
  76. expect(code).toMatch(`const { helperOne, helperTwo } = Vue`)
  77. expect(code).toMatchSnapshot()
  78. })
  79. test('statements', () => {
  80. const root = createRoot({
  81. statements: [`const a = 1`, `const b = 2`]
  82. })
  83. const { code } = generate(root, { mode: 'function' })
  84. expect(code).toMatch(`const a = 1\n`)
  85. expect(code).toMatch(`const b = 2\n`)
  86. expect(code).toMatchSnapshot()
  87. })
  88. test('hoists', () => {
  89. const root = createRoot({
  90. hoists: [
  91. createSimpleExpression(`hello`, false, mockLoc),
  92. createObjectExpression(
  93. [
  94. createObjectProperty(
  95. createSimpleExpression(`id`, true, mockLoc),
  96. createSimpleExpression(`foo`, true, mockLoc),
  97. mockLoc
  98. )
  99. ],
  100. mockLoc
  101. )
  102. ]
  103. })
  104. const { code } = generate(root)
  105. expect(code).toMatch(`const _hoisted_1 = hello`)
  106. expect(code).toMatch(`const _hoisted_2 = { id: "foo" }`)
  107. expect(code).toMatchSnapshot()
  108. })
  109. test('prefixIdentifiers: true should inject _ctx statement', () => {
  110. const { code } = generate(createRoot(), { prefixIdentifiers: true })
  111. expect(code).toMatch(`const _ctx = this\n`)
  112. expect(code).toMatchSnapshot()
  113. })
  114. test('static text', () => {
  115. const { code } = generate(
  116. createRoot({
  117. children: [
  118. {
  119. type: NodeTypes.TEXT,
  120. content: 'hello',
  121. isEmpty: false,
  122. loc: mockLoc
  123. }
  124. ]
  125. })
  126. )
  127. expect(code).toMatch(`return "hello"`)
  128. expect(code).toMatchSnapshot()
  129. })
  130. test('interpolation', () => {
  131. const { code } = generate(
  132. createRoot({
  133. children: [createInterpolation(`hello`, mockLoc)]
  134. })
  135. )
  136. expect(code).toMatch(`return _${TO_STRING}(hello)`)
  137. expect(code).toMatchSnapshot()
  138. })
  139. test('comment', () => {
  140. const { code } = generate(
  141. createRoot({
  142. children: [
  143. {
  144. type: NodeTypes.COMMENT,
  145. content: 'foo',
  146. loc: mockLoc
  147. }
  148. ]
  149. })
  150. )
  151. expect(code).toMatch(`return _${CREATE_VNODE}(_${COMMENT}, 0, "foo")`)
  152. expect(code).toMatchSnapshot()
  153. })
  154. test('text + comment + interpolation', () => {
  155. const { code } = generate(
  156. createRoot({
  157. children: [
  158. {
  159. type: NodeTypes.TEXT,
  160. content: 'foo',
  161. isEmpty: false,
  162. loc: mockLoc
  163. },
  164. createInterpolation(`hello`, mockLoc),
  165. {
  166. type: NodeTypes.COMMENT,
  167. content: 'foo',
  168. loc: mockLoc
  169. }
  170. ]
  171. })
  172. )
  173. expect(code).toMatch(`
  174. return [
  175. "foo",
  176. _${TO_STRING}(hello),
  177. _${CREATE_VNODE}(_${COMMENT}, 0, "foo")
  178. ]`)
  179. expect(code).toMatchSnapshot()
  180. })
  181. test('text + comment + interpolation w/ prefixIdentifiers: true', () => {
  182. const { code } = generate(
  183. createRoot({
  184. children: [
  185. {
  186. type: NodeTypes.TEXT,
  187. content: 'foo',
  188. isEmpty: false,
  189. loc: mockLoc
  190. },
  191. createInterpolation(`hello`, mockLoc),
  192. {
  193. type: NodeTypes.COMMENT,
  194. content: 'foo',
  195. loc: mockLoc
  196. }
  197. ]
  198. }),
  199. {
  200. prefixIdentifiers: true
  201. }
  202. )
  203. expect(code).toMatch(`
  204. return [
  205. "foo",
  206. ${TO_STRING}(hello),
  207. ${CREATE_VNODE}(${COMMENT}, 0, "foo")
  208. ]`)
  209. expect(code).toMatchSnapshot()
  210. })
  211. test('compound expression', () => {
  212. const { code } = generate(
  213. createRoot({
  214. children: [
  215. createInterpolation(
  216. createCompoundExpression(
  217. [`_ctx.`, createSimpleExpression(`foo`, false, mockLoc)],
  218. mockLoc
  219. ),
  220. mockLoc
  221. )
  222. ]
  223. })
  224. )
  225. expect(code).toMatch(`return _${TO_STRING}(_ctx.foo)`)
  226. expect(code).toMatchSnapshot()
  227. })
  228. test('ifNode', () => {
  229. const { code } = generate(
  230. createRoot({
  231. children: [
  232. {
  233. type: NodeTypes.IF,
  234. loc: mockLoc,
  235. branches: [
  236. {
  237. type: NodeTypes.IF_BRANCH,
  238. condition: createSimpleExpression('foo', false, mockLoc),
  239. loc: mockLoc,
  240. children: [
  241. {
  242. type: NodeTypes.TEXT,
  243. content: 'foo',
  244. isEmpty: false,
  245. loc: mockLoc
  246. }
  247. ]
  248. },
  249. {
  250. type: NodeTypes.IF_BRANCH,
  251. condition: createSimpleExpression('a + b', false, mockLoc),
  252. loc: mockLoc,
  253. children: [createInterpolation(`bye`, mockLoc)]
  254. },
  255. {
  256. type: NodeTypes.IF_BRANCH,
  257. condition: undefined,
  258. loc: mockLoc,
  259. children: [
  260. {
  261. type: NodeTypes.COMMENT,
  262. content: 'foo',
  263. loc: mockLoc
  264. }
  265. ]
  266. }
  267. ]
  268. }
  269. ]
  270. })
  271. )
  272. expect(code).toMatch(`
  273. return foo
  274. ? "foo"
  275. : (a + b)
  276. ? _${TO_STRING}(bye)
  277. : _${CREATE_VNODE}(_${COMMENT}, 0, "foo")`)
  278. expect(code).toMatchSnapshot()
  279. })
  280. test('ifNode with no v-else', () => {
  281. const { code } = generate(
  282. createRoot({
  283. children: [
  284. {
  285. type: NodeTypes.IF,
  286. loc: mockLoc,
  287. branches: [
  288. {
  289. type: NodeTypes.IF_BRANCH,
  290. condition: createSimpleExpression('foo', false, mockLoc),
  291. loc: mockLoc,
  292. children: [
  293. {
  294. type: NodeTypes.TEXT,
  295. content: 'foo',
  296. isEmpty: false,
  297. loc: mockLoc
  298. }
  299. ]
  300. },
  301. {
  302. type: NodeTypes.IF_BRANCH,
  303. condition: createSimpleExpression('a + b', false, mockLoc),
  304. loc: mockLoc,
  305. children: [createInterpolation(`bye`, mockLoc)]
  306. }
  307. ]
  308. }
  309. ]
  310. })
  311. )
  312. expect(code).toMatch(`
  313. return foo
  314. ? "foo"
  315. : (a + b)
  316. ? _${TO_STRING}(bye)
  317. : null`)
  318. expect(code).toMatchSnapshot()
  319. })
  320. test('forNode', () => {
  321. const { code } = generate(
  322. createRoot({
  323. children: [
  324. {
  325. type: NodeTypes.FOR,
  326. loc: mockLoc,
  327. source: createSimpleExpression(`list`, false, mockLoc),
  328. valueAlias: createSimpleExpression(`v`, false, mockLoc),
  329. keyAlias: createSimpleExpression(`k`, false, mockLoc),
  330. objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
  331. children: [createInterpolation(`v`, mockLoc)]
  332. }
  333. ]
  334. })
  335. )
  336. expect(code).toMatch(
  337. `return _${RENDER_LIST}(list, (v, k, i) => {
  338. return _${TO_STRING}(v)
  339. })`
  340. )
  341. expect(code).toMatchSnapshot()
  342. })
  343. test('forNode w/ prefixIdentifiers: true', () => {
  344. const { code } = generate(
  345. createRoot({
  346. children: [
  347. {
  348. type: NodeTypes.FOR,
  349. loc: mockLoc,
  350. source: createSimpleExpression(`list`, false, mockLoc),
  351. valueAlias: createSimpleExpression(`v`, false, mockLoc),
  352. keyAlias: createSimpleExpression(`k`, false, mockLoc),
  353. objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
  354. children: [createInterpolation(`v`, mockLoc)]
  355. }
  356. ]
  357. }),
  358. {
  359. prefixIdentifiers: true
  360. }
  361. )
  362. expect(code).toMatch(
  363. `return ${RENDER_LIST}(list, (v, k, i) => {
  364. return ${TO_STRING}(v)
  365. })`
  366. )
  367. expect(code).toMatchSnapshot()
  368. })
  369. test('forNode w/ skipped value alias', () => {
  370. const { code } = generate(
  371. createRoot({
  372. children: [
  373. {
  374. type: NodeTypes.FOR,
  375. loc: mockLoc,
  376. source: createSimpleExpression(`list`, false, mockLoc),
  377. valueAlias: undefined,
  378. keyAlias: createSimpleExpression(`k`, false, mockLoc),
  379. objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
  380. children: [createInterpolation(`v`, mockLoc)]
  381. }
  382. ]
  383. })
  384. )
  385. expect(code).toMatch(
  386. `return _${RENDER_LIST}(list, (__value, k, i) => {
  387. return _${TO_STRING}(v)
  388. })`
  389. )
  390. expect(code).toMatchSnapshot()
  391. })
  392. test('forNode w/ skipped key alias', () => {
  393. const { code } = generate(
  394. createRoot({
  395. children: [
  396. {
  397. type: NodeTypes.FOR,
  398. loc: mockLoc,
  399. source: createSimpleExpression(`list`, false, mockLoc),
  400. valueAlias: createSimpleExpression(`v`, false, mockLoc),
  401. keyAlias: undefined,
  402. objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
  403. children: [createInterpolation(`v`, mockLoc)]
  404. }
  405. ]
  406. })
  407. )
  408. expect(code).toMatch(
  409. `return _${RENDER_LIST}(list, (v, __key, i) => {
  410. return _${TO_STRING}(v)
  411. })`
  412. )
  413. expect(code).toMatchSnapshot()
  414. })
  415. test('forNode w/ skipped value and key aliases', () => {
  416. const { code } = generate(
  417. createRoot({
  418. children: [
  419. {
  420. type: NodeTypes.FOR,
  421. loc: mockLoc,
  422. source: createSimpleExpression(`list`, false, mockLoc),
  423. valueAlias: undefined,
  424. keyAlias: undefined,
  425. objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
  426. children: [createInterpolation(`v`, mockLoc)]
  427. }
  428. ]
  429. })
  430. )
  431. expect(code).toMatch(
  432. `return _${RENDER_LIST}(list, (__value, __key, i) => {
  433. return _${TO_STRING}(v)
  434. })`
  435. )
  436. expect(code).toMatchSnapshot()
  437. })
  438. test('callExpression + objectExpression + arrayExpression', () => {
  439. function createElementWithCodegen(
  440. args: CallExpression['arguments']
  441. ): ElementNode {
  442. return {
  443. type: NodeTypes.ELEMENT,
  444. loc: mockLoc,
  445. ns: Namespaces.HTML,
  446. tag: 'div',
  447. tagType: ElementTypes.ELEMENT,
  448. isSelfClosing: false,
  449. props: [],
  450. children: [],
  451. codegenNode: {
  452. type: NodeTypes.JS_CALL_EXPRESSION,
  453. loc: mockLoc,
  454. callee: CREATE_VNODE,
  455. arguments: args
  456. }
  457. }
  458. }
  459. const { code } = generate(
  460. createRoot({
  461. children: [
  462. createElementWithCodegen([
  463. // string
  464. `"div"`,
  465. // ObjectExpression
  466. createObjectExpression(
  467. [
  468. createObjectProperty(
  469. createSimpleExpression(`id`, true, mockLoc),
  470. createSimpleExpression(`foo`, true, mockLoc),
  471. mockLoc
  472. ),
  473. createObjectProperty(
  474. createSimpleExpression(`prop`, false, mockLoc),
  475. createSimpleExpression(`bar`, false, mockLoc),
  476. mockLoc
  477. ),
  478. // compound expression as computed key
  479. createObjectProperty(
  480. {
  481. type: NodeTypes.COMPOUND_EXPRESSION,
  482. loc: mockLoc,
  483. children: [
  484. `foo + `,
  485. createSimpleExpression(`bar`, false, mockLoc)
  486. ]
  487. },
  488. createSimpleExpression(`bar`, false, mockLoc),
  489. mockLoc
  490. )
  491. ],
  492. mockLoc
  493. ),
  494. // ChildNode[]
  495. [
  496. createElementWithCodegen([
  497. `"p"`,
  498. createObjectExpression(
  499. [
  500. createObjectProperty(
  501. // should quote the key!
  502. createSimpleExpression(`some-key`, true, mockLoc),
  503. createSimpleExpression(`foo`, true, mockLoc),
  504. mockLoc
  505. )
  506. ],
  507. mockLoc
  508. )
  509. ])
  510. ],
  511. // ArrayExpression
  512. createArrayExpression(
  513. [
  514. 'foo',
  515. {
  516. type: NodeTypes.JS_CALL_EXPRESSION,
  517. loc: mockLoc,
  518. callee: CREATE_VNODE,
  519. arguments: [`"p"`]
  520. }
  521. ],
  522. mockLoc
  523. )
  524. ])
  525. ]
  526. })
  527. )
  528. expect(code).toMatch(`
  529. return ${CREATE_VNODE}("div", {
  530. id: "foo",
  531. [prop]: bar,
  532. [foo + bar]: bar
  533. }, [
  534. ${CREATE_VNODE}("p", { "some-key": "foo" })
  535. ], [
  536. foo,
  537. ${CREATE_VNODE}("p")
  538. ])`)
  539. expect(code).toMatchSnapshot()
  540. })
  541. })