codegen.spec.ts 15 KB

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