codegen.spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  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. } from '../src'
  19. import {
  20. CREATE_VNODE,
  21. TO_STRING,
  22. RESOLVE_DIRECTIVE,
  23. helperNameMap,
  24. RESOLVE_COMPONENT,
  25. CREATE_COMMENT
  26. } from '../src/runtimeHelpers'
  27. import { createElementWithCodegen } from './testUtils'
  28. import { PatchFlags } from '@vue/shared'
  29. function createRoot(options: Partial<RootNode> = {}): RootNode {
  30. return {
  31. type: NodeTypes.ROOT,
  32. children: [],
  33. helpers: [],
  34. components: [],
  35. directives: [],
  36. imports: [],
  37. hoists: [],
  38. cached: 0,
  39. codegenNode: createSimpleExpression(`null`, false),
  40. loc: locStub,
  41. ...options
  42. }
  43. }
  44. describe('compiler: codegen', () => {
  45. test('module mode preamble', () => {
  46. const root = createRoot({
  47. helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
  48. })
  49. const { code } = generate(root, { mode: 'module' })
  50. expect(code).toMatch(
  51. `import { ${helperNameMap[CREATE_VNODE]}, ${
  52. helperNameMap[RESOLVE_DIRECTIVE]
  53. } } from "vue"`
  54. )
  55. expect(code).toMatchSnapshot()
  56. })
  57. test('function mode preamble', () => {
  58. const root = createRoot({
  59. helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
  60. })
  61. const { code } = generate(root, { mode: 'function' })
  62. expect(code).toMatch(`const _Vue = Vue`)
  63. expect(code).toMatch(
  64. `const { ${helperNameMap[CREATE_VNODE]}: _${
  65. helperNameMap[CREATE_VNODE]
  66. }, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${
  67. helperNameMap[RESOLVE_DIRECTIVE]
  68. } } = _Vue`
  69. )
  70. expect(code).toMatchSnapshot()
  71. })
  72. test('function mode preamble w/ prefixIdentifiers: true', () => {
  73. const root = createRoot({
  74. helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
  75. })
  76. const { code } = generate(root, {
  77. mode: 'function',
  78. prefixIdentifiers: true
  79. })
  80. expect(code).not.toMatch(`const _Vue = Vue`)
  81. expect(code).toMatch(
  82. `const { ${helperNameMap[CREATE_VNODE]}, ${
  83. helperNameMap[RESOLVE_DIRECTIVE]
  84. } } = Vue`
  85. )
  86. expect(code).toMatchSnapshot()
  87. })
  88. test('assets', () => {
  89. const root = createRoot({
  90. components: [`Foo`, `bar-baz`, `barbaz`],
  91. directives: [`my_dir`]
  92. })
  93. const { code } = generate(root, { mode: 'function' })
  94. expect(code).toMatch(
  95. `const _component_Foo = _${helperNameMap[RESOLVE_COMPONENT]}("Foo")\n`
  96. )
  97. expect(code).toMatch(
  98. `const _component_bar_baz = _${
  99. helperNameMap[RESOLVE_COMPONENT]
  100. }("bar-baz")\n`
  101. )
  102. expect(code).toMatch(
  103. `const _component_barbaz = _${
  104. helperNameMap[RESOLVE_COMPONENT]
  105. }("barbaz")\n`
  106. )
  107. expect(code).toMatch(
  108. `const _directive_my_dir = _${
  109. helperNameMap[RESOLVE_DIRECTIVE]
  110. }("my_dir")\n`
  111. )
  112. expect(code).toMatchSnapshot()
  113. })
  114. test('hoists', () => {
  115. const root = createRoot({
  116. hoists: [
  117. createSimpleExpression(`hello`, false, locStub),
  118. createObjectExpression(
  119. [
  120. createObjectProperty(
  121. createSimpleExpression(`id`, true, locStub),
  122. createSimpleExpression(`foo`, true, locStub)
  123. )
  124. ],
  125. locStub
  126. )
  127. ]
  128. })
  129. const { code } = generate(root)
  130. expect(code).toMatch(`const _hoisted_1 = hello`)
  131. expect(code).toMatch(`const _hoisted_2 = { id: "foo" }`)
  132. expect(code).toMatchSnapshot()
  133. })
  134. test('prefixIdentifiers: true should inject _ctx statement', () => {
  135. const { code } = generate(createRoot(), { prefixIdentifiers: true })
  136. expect(code).toMatch(`const _ctx = this\n`)
  137. expect(code).toMatchSnapshot()
  138. })
  139. test('static text', () => {
  140. const { code } = generate(
  141. createRoot({
  142. codegenNode: {
  143. type: NodeTypes.TEXT,
  144. content: 'hello',
  145. loc: locStub
  146. }
  147. })
  148. )
  149. expect(code).toMatch(`return "hello"`)
  150. expect(code).toMatchSnapshot()
  151. })
  152. test('interpolation', () => {
  153. const { code } = generate(
  154. createRoot({
  155. codegenNode: createInterpolation(`hello`, locStub)
  156. })
  157. )
  158. expect(code).toMatch(`return _${helperNameMap[TO_STRING]}(hello)`)
  159. expect(code).toMatchSnapshot()
  160. })
  161. test('comment', () => {
  162. const { code } = generate(
  163. createRoot({
  164. codegenNode: {
  165. type: NodeTypes.COMMENT,
  166. content: 'foo',
  167. loc: locStub
  168. }
  169. })
  170. )
  171. expect(code).toMatch(`return _${helperNameMap[CREATE_COMMENT]}("foo")`)
  172. expect(code).toMatchSnapshot()
  173. })
  174. test('compound expression', () => {
  175. const { code } = generate(
  176. createRoot({
  177. codegenNode: createCompoundExpression([
  178. `_ctx.`,
  179. createSimpleExpression(`foo`, false, locStub),
  180. ` + `,
  181. {
  182. type: NodeTypes.INTERPOLATION,
  183. loc: locStub,
  184. content: createSimpleExpression(`bar`, false, locStub)
  185. }
  186. ])
  187. })
  188. )
  189. expect(code).toMatch(`return _ctx.foo + _${helperNameMap[TO_STRING]}(bar)`)
  190. expect(code).toMatchSnapshot()
  191. })
  192. test('ifNode', () => {
  193. const { code } = generate(
  194. createRoot({
  195. codegenNode: {
  196. type: NodeTypes.IF,
  197. loc: locStub,
  198. branches: [],
  199. codegenNode: createSequenceExpression([
  200. createSimpleExpression('foo', false),
  201. createSimpleExpression('bar', false)
  202. ]) as IfCodegenNode
  203. }
  204. })
  205. )
  206. expect(code).toMatch(`return (foo, bar)`)
  207. expect(code).toMatchSnapshot()
  208. })
  209. test('forNode', () => {
  210. const { code } = generate(
  211. createRoot({
  212. codegenNode: {
  213. type: NodeTypes.FOR,
  214. loc: locStub,
  215. source: createSimpleExpression('foo', false),
  216. valueAlias: undefined,
  217. keyAlias: undefined,
  218. objectIndexAlias: undefined,
  219. children: [],
  220. codegenNode: createSequenceExpression([
  221. createSimpleExpression('foo', false),
  222. createSimpleExpression('bar', false)
  223. ]) as ForCodegenNode
  224. }
  225. })
  226. )
  227. expect(code).toMatch(`return (foo, bar)`)
  228. expect(code).toMatchSnapshot()
  229. })
  230. test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
  231. const { code } = generate(
  232. createRoot({
  233. codegenNode: createElementWithCodegen([
  234. // string
  235. `"div"`,
  236. // ObjectExpression
  237. createObjectExpression(
  238. [
  239. createObjectProperty(
  240. createSimpleExpression(`id`, true, locStub),
  241. createSimpleExpression(`foo`, true, locStub)
  242. ),
  243. createObjectProperty(
  244. createSimpleExpression(`prop`, false, locStub),
  245. createSimpleExpression(`bar`, false, locStub)
  246. ),
  247. // compound expression as computed key
  248. createObjectProperty(
  249. {
  250. type: NodeTypes.COMPOUND_EXPRESSION,
  251. loc: locStub,
  252. children: [
  253. `foo + `,
  254. createSimpleExpression(`bar`, false, locStub)
  255. ]
  256. },
  257. createSimpleExpression(`bar`, false, locStub)
  258. )
  259. ],
  260. locStub
  261. ),
  262. // ChildNode[]
  263. [
  264. createElementWithCodegen([
  265. `"p"`,
  266. createObjectExpression(
  267. [
  268. createObjectProperty(
  269. // should quote the key!
  270. createSimpleExpression(`some-key`, true, locStub),
  271. createSimpleExpression(`foo`, true, locStub)
  272. )
  273. ],
  274. locStub
  275. )
  276. ])
  277. ],
  278. // flag
  279. PatchFlags.FULL_PROPS + ''
  280. ])
  281. })
  282. )
  283. expect(code).toMatch(`
  284. return _${helperNameMap[CREATE_VNODE]}("div", {
  285. id: "foo",
  286. [prop]: bar,
  287. [foo + bar]: bar
  288. }, [
  289. _${helperNameMap[CREATE_VNODE]}("p", { "some-key": "foo" })
  290. ], ${PatchFlags.FULL_PROPS})`)
  291. expect(code).toMatchSnapshot()
  292. })
  293. test('ArrayExpression', () => {
  294. const { code } = generate(
  295. createRoot({
  296. codegenNode: createArrayExpression([
  297. createSimpleExpression(`foo`, false),
  298. createCallExpression(`bar`, [`baz`])
  299. ])
  300. })
  301. )
  302. expect(code).toMatch(`return [
  303. foo,
  304. bar(baz)
  305. ]`)
  306. expect(code).toMatchSnapshot()
  307. })
  308. test('SequenceExpression', () => {
  309. const { code } = generate(
  310. createRoot({
  311. codegenNode: createSequenceExpression([
  312. createSimpleExpression(`foo`, false),
  313. createCallExpression(`bar`, [`baz`])
  314. ])
  315. })
  316. )
  317. expect(code).toMatch(`return (foo, bar(baz))`)
  318. expect(code).toMatchSnapshot()
  319. })
  320. test('ConditionalExpression', () => {
  321. const { code } = generate(
  322. createRoot({
  323. codegenNode: createConditionalExpression(
  324. createSimpleExpression(`ok`, false),
  325. createCallExpression(`foo`),
  326. createConditionalExpression(
  327. createSimpleExpression(`orNot`, false),
  328. createCallExpression(`bar`),
  329. createCallExpression(`baz`)
  330. )
  331. )
  332. })
  333. )
  334. expect(code).toMatch(
  335. `return ok
  336. ? foo()
  337. : orNot
  338. ? bar()
  339. : baz()`
  340. )
  341. expect(code).toMatchSnapshot()
  342. })
  343. test('CacheExpression', () => {
  344. const { code } = generate(
  345. createRoot({
  346. cached: 1,
  347. codegenNode: createCacheExpression(
  348. 1,
  349. createSimpleExpression(`foo`, false)
  350. )
  351. }),
  352. {
  353. mode: 'module',
  354. prefixIdentifiers: true
  355. }
  356. )
  357. expect(code).toMatch(`const _cache = _ctx.$cache`)
  358. expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
  359. expect(code).toMatchSnapshot()
  360. })
  361. test('CacheExpression w/ isVNode: true', () => {
  362. const { code } = generate(
  363. createRoot({
  364. cached: 1,
  365. codegenNode: createCacheExpression(
  366. 1,
  367. createSimpleExpression(`foo`, false),
  368. true
  369. )
  370. }),
  371. {
  372. mode: 'module',
  373. prefixIdentifiers: true
  374. }
  375. )
  376. expect(code).toMatch(`const _cache = _ctx.$cache`)
  377. expect(code).toMatch(
  378. `
  379. _cache[1] || (
  380. setBlockTracking(-1),
  381. _cache[1] = foo,
  382. setBlockTracking(1),
  383. _cache[1]
  384. )
  385. `.trim()
  386. )
  387. expect(code).toMatchSnapshot()
  388. })
  389. })