codegen.spec.ts 14 KB

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