codegen.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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. parseResult: {} as any,
  226. codegenNode: createSequenceExpression([
  227. createSimpleExpression('foo', false),
  228. createSimpleExpression('bar', false)
  229. ]) as ForCodegenNode
  230. }
  231. })
  232. )
  233. expect(code).toMatch(`return (foo, bar)`)
  234. expect(code).toMatchSnapshot()
  235. })
  236. test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
  237. const { code } = generate(
  238. createRoot({
  239. codegenNode: createElementWithCodegen([
  240. // string
  241. `"div"`,
  242. // ObjectExpression
  243. createObjectExpression(
  244. [
  245. createObjectProperty(
  246. createSimpleExpression(`id`, true, locStub),
  247. createSimpleExpression(`foo`, true, locStub)
  248. ),
  249. createObjectProperty(
  250. createSimpleExpression(`prop`, false, locStub),
  251. createSimpleExpression(`bar`, false, locStub)
  252. ),
  253. // compound expression as computed key
  254. createObjectProperty(
  255. {
  256. type: NodeTypes.COMPOUND_EXPRESSION,
  257. loc: locStub,
  258. children: [
  259. `foo + `,
  260. createSimpleExpression(`bar`, false, locStub)
  261. ]
  262. },
  263. createSimpleExpression(`bar`, false, locStub)
  264. )
  265. ],
  266. locStub
  267. ),
  268. // ChildNode[]
  269. [
  270. createElementWithCodegen([
  271. `"p"`,
  272. createObjectExpression(
  273. [
  274. createObjectProperty(
  275. // should quote the key!
  276. createSimpleExpression(`some-key`, true, locStub),
  277. createSimpleExpression(`foo`, true, locStub)
  278. )
  279. ],
  280. locStub
  281. )
  282. ])
  283. ],
  284. // flag
  285. PatchFlags.FULL_PROPS + ''
  286. ])
  287. })
  288. )
  289. expect(code).toMatch(`
  290. return _${helperNameMap[CREATE_VNODE]}("div", {
  291. id: "foo",
  292. [prop]: bar,
  293. [foo + bar]: bar
  294. }, [
  295. _${helperNameMap[CREATE_VNODE]}("p", { "some-key": "foo" })
  296. ], ${PatchFlags.FULL_PROPS})`)
  297. expect(code).toMatchSnapshot()
  298. })
  299. test('ArrayExpression', () => {
  300. const { code } = generate(
  301. createRoot({
  302. codegenNode: createArrayExpression([
  303. createSimpleExpression(`foo`, false),
  304. createCallExpression(`bar`, [`baz`])
  305. ])
  306. })
  307. )
  308. expect(code).toMatch(`return [
  309. foo,
  310. bar(baz)
  311. ]`)
  312. expect(code).toMatchSnapshot()
  313. })
  314. test('SequenceExpression', () => {
  315. const { code } = generate(
  316. createRoot({
  317. codegenNode: createSequenceExpression([
  318. createSimpleExpression(`foo`, false),
  319. createCallExpression(`bar`, [`baz`])
  320. ])
  321. })
  322. )
  323. expect(code).toMatch(`return (foo, bar(baz))`)
  324. expect(code).toMatchSnapshot()
  325. })
  326. test('ConditionalExpression', () => {
  327. const { code } = generate(
  328. createRoot({
  329. codegenNode: createConditionalExpression(
  330. createSimpleExpression(`ok`, false),
  331. createCallExpression(`foo`),
  332. createConditionalExpression(
  333. createSimpleExpression(`orNot`, false),
  334. createCallExpression(`bar`),
  335. createCallExpression(`baz`)
  336. )
  337. )
  338. })
  339. )
  340. expect(code).toMatch(
  341. `return ok
  342. ? foo()
  343. : orNot
  344. ? bar()
  345. : baz()`
  346. )
  347. expect(code).toMatchSnapshot()
  348. })
  349. test('CacheExpression', () => {
  350. const { code } = generate(
  351. createRoot({
  352. cached: 1,
  353. codegenNode: createCacheExpression(
  354. 1,
  355. createSimpleExpression(`foo`, false)
  356. )
  357. }),
  358. {
  359. mode: 'module',
  360. prefixIdentifiers: true
  361. }
  362. )
  363. expect(code).toMatch(`const _cache = _ctx.$cache`)
  364. expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
  365. expect(code).toMatchSnapshot()
  366. })
  367. test('CacheExpression w/ isVNode: true', () => {
  368. const { code } = generate(
  369. createRoot({
  370. cached: 1,
  371. codegenNode: createCacheExpression(
  372. 1,
  373. createSimpleExpression(`foo`, false),
  374. true
  375. )
  376. }),
  377. {
  378. mode: 'module',
  379. prefixIdentifiers: true
  380. }
  381. )
  382. expect(code).toMatch(`const _cache = _ctx.$cache`)
  383. expect(code).toMatch(
  384. `
  385. _cache[1] || (
  386. setBlockTracking(-1),
  387. _cache[1] = foo,
  388. setBlockTracking(1),
  389. _cache[1]
  390. )
  391. `.trim()
  392. )
  393. expect(code).toMatchSnapshot()
  394. })
  395. test('TemplateLiteral', () => {
  396. const { code } = generate(
  397. createRoot({
  398. codegenNode: createCallExpression(`_push`, [
  399. createTemplateLiteral([
  400. `foo`,
  401. createCallExpression(`_renderAttr`, ['id', 'foo']),
  402. `bar`
  403. ])
  404. ])
  405. }),
  406. { ssr: true, mode: 'module' }
  407. )
  408. expect(code).toMatchInlineSnapshot(`
  409. "
  410. export function ssrRender(_ctx, _push, _parent) {
  411. _push(\`foo\${_renderAttr(id, foo)}bar\`)
  412. }"
  413. `)
  414. })
  415. describe('IfStatement', () => {
  416. test('if', () => {
  417. const { code } = generate(
  418. createRoot({
  419. codegenNode: createBlockStatement([
  420. createIfStatement(
  421. createSimpleExpression('foo', false),
  422. createBlockStatement([createCallExpression(`ok`)])
  423. )
  424. ])
  425. }),
  426. { ssr: true, mode: 'module' }
  427. )
  428. expect(code).toMatchInlineSnapshot(`
  429. "
  430. export function ssrRender(_ctx, _push, _parent) {
  431. if (foo) {
  432. ok()
  433. }
  434. }"
  435. `)
  436. })
  437. test('if/else', () => {
  438. const { code } = generate(
  439. createRoot({
  440. codegenNode: createBlockStatement([
  441. createIfStatement(
  442. createSimpleExpression('foo', false),
  443. createBlockStatement([createCallExpression(`foo`)]),
  444. createBlockStatement([createCallExpression('bar')])
  445. )
  446. ])
  447. }),
  448. { ssr: true, mode: 'module' }
  449. )
  450. expect(code).toMatchInlineSnapshot(`
  451. "
  452. export function ssrRender(_ctx, _push, _parent) {
  453. if (foo) {
  454. foo()
  455. } else {
  456. bar()
  457. }
  458. }"
  459. `)
  460. })
  461. test('if/else-if', () => {
  462. const { code } = generate(
  463. createRoot({
  464. codegenNode: createBlockStatement([
  465. createIfStatement(
  466. createSimpleExpression('foo', false),
  467. createBlockStatement([createCallExpression(`foo`)]),
  468. createIfStatement(
  469. createSimpleExpression('bar', false),
  470. createBlockStatement([createCallExpression(`bar`)])
  471. )
  472. )
  473. ])
  474. }),
  475. { ssr: true, mode: 'module' }
  476. )
  477. expect(code).toMatchInlineSnapshot(`
  478. "
  479. export function ssrRender(_ctx, _push, _parent) {
  480. if (foo) {
  481. foo()
  482. } else if (bar) {
  483. bar()
  484. }
  485. }"
  486. `)
  487. })
  488. test('if/else-if/else', () => {
  489. const { code } = generate(
  490. createRoot({
  491. codegenNode: createBlockStatement([
  492. createIfStatement(
  493. createSimpleExpression('foo', false),
  494. createBlockStatement([createCallExpression(`foo`)]),
  495. createIfStatement(
  496. createSimpleExpression('bar', false),
  497. createBlockStatement([createCallExpression(`bar`)]),
  498. createBlockStatement([createCallExpression('baz')])
  499. )
  500. )
  501. ])
  502. }),
  503. { ssr: true, mode: 'module' }
  504. )
  505. expect(code).toMatchInlineSnapshot(`
  506. "
  507. export function ssrRender(_ctx, _push, _parent) {
  508. if (foo) {
  509. foo()
  510. } else if (bar) {
  511. bar()
  512. } else {
  513. baz()
  514. }
  515. }"
  516. `)
  517. })
  518. })
  519. })