codegen.spec.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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('SlotFunctionExpression', () => {
  439. const { code } = generate(
  440. createRoot({
  441. children: [
  442. {
  443. type: NodeTypes.ELEMENT,
  444. tagType: ElementTypes.COMPONENT,
  445. ns: Namespaces.HTML,
  446. isSelfClosing: false,
  447. tag: `Comp`,
  448. loc: mockLoc,
  449. props: [],
  450. children: [],
  451. codegenNode: {
  452. type: NodeTypes.JS_CALL_EXPRESSION,
  453. loc: mockLoc,
  454. callee: `_${CREATE_VNODE}`,
  455. arguments: [
  456. `Comp`,
  457. `0`,
  458. {
  459. type: NodeTypes.JS_OBJECT_EXPRESSION,
  460. loc: mockLoc,
  461. properties: [
  462. {
  463. type: NodeTypes.JS_PROPERTY,
  464. loc: mockLoc,
  465. key: {
  466. type: NodeTypes.SIMPLE_EXPRESSION,
  467. isStatic: true,
  468. content: `default`,
  469. loc: mockLoc
  470. },
  471. value: {
  472. type: NodeTypes.JS_SLOT_FUNCTION,
  473. loc: mockLoc,
  474. params: {
  475. type: NodeTypes.SIMPLE_EXPRESSION,
  476. isStatic: false,
  477. content: `{ foo }`,
  478. loc: mockLoc
  479. },
  480. returns: [
  481. {
  482. type: NodeTypes.INTERPOLATION,
  483. loc: mockLoc,
  484. content: {
  485. type: NodeTypes.SIMPLE_EXPRESSION,
  486. isStatic: false,
  487. content: `foo`,
  488. loc: mockLoc
  489. }
  490. }
  491. ]
  492. }
  493. }
  494. ]
  495. }
  496. ]
  497. }
  498. }
  499. ]
  500. })
  501. )
  502. expect(code).toMatch(
  503. `return _createVNode(Comp, 0, {
  504. default: ({ foo }) => [
  505. _toString(foo)
  506. ]
  507. })`
  508. )
  509. expect(code).toMatchSnapshot()
  510. })
  511. test('callExpression + objectExpression + arrayExpression', () => {
  512. function createElementWithCodegen(
  513. args: CallExpression['arguments']
  514. ): ElementNode {
  515. return {
  516. type: NodeTypes.ELEMENT,
  517. loc: mockLoc,
  518. ns: Namespaces.HTML,
  519. tag: 'div',
  520. tagType: ElementTypes.ELEMENT,
  521. isSelfClosing: false,
  522. props: [],
  523. children: [],
  524. codegenNode: {
  525. type: NodeTypes.JS_CALL_EXPRESSION,
  526. loc: mockLoc,
  527. callee: CREATE_VNODE,
  528. arguments: args
  529. }
  530. }
  531. }
  532. const { code } = generate(
  533. createRoot({
  534. children: [
  535. createElementWithCodegen([
  536. // string
  537. `"div"`,
  538. // ObjectExpression
  539. createObjectExpression(
  540. [
  541. createObjectProperty(
  542. createSimpleExpression(`id`, true, mockLoc),
  543. createSimpleExpression(`foo`, true, mockLoc),
  544. mockLoc
  545. ),
  546. createObjectProperty(
  547. createSimpleExpression(`prop`, false, mockLoc),
  548. createSimpleExpression(`bar`, false, mockLoc),
  549. mockLoc
  550. ),
  551. // compound expression as computed key
  552. createObjectProperty(
  553. {
  554. type: NodeTypes.COMPOUND_EXPRESSION,
  555. loc: mockLoc,
  556. children: [
  557. `foo + `,
  558. createSimpleExpression(`bar`, false, mockLoc)
  559. ]
  560. },
  561. createSimpleExpression(`bar`, false, mockLoc),
  562. mockLoc
  563. )
  564. ],
  565. mockLoc
  566. ),
  567. // ChildNode[]
  568. [
  569. createElementWithCodegen([
  570. `"p"`,
  571. createObjectExpression(
  572. [
  573. createObjectProperty(
  574. // should quote the key!
  575. createSimpleExpression(`some-key`, true, mockLoc),
  576. createSimpleExpression(`foo`, true, mockLoc),
  577. mockLoc
  578. )
  579. ],
  580. mockLoc
  581. )
  582. ])
  583. ],
  584. // ArrayExpression
  585. createArrayExpression(
  586. [
  587. 'foo',
  588. {
  589. type: NodeTypes.JS_CALL_EXPRESSION,
  590. loc: mockLoc,
  591. callee: CREATE_VNODE,
  592. arguments: [`"p"`]
  593. }
  594. ],
  595. mockLoc
  596. )
  597. ])
  598. ]
  599. })
  600. )
  601. expect(code).toMatch(`
  602. return ${CREATE_VNODE}("div", {
  603. id: "foo",
  604. [prop]: bar,
  605. [foo + bar]: bar
  606. }, [
  607. ${CREATE_VNODE}("p", { "some-key": "foo" })
  608. ], [
  609. foo,
  610. ${CREATE_VNODE}("p")
  611. ])`)
  612. expect(code).toMatchSnapshot()
  613. })
  614. })