codegen.spec.ts 19 KB

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