codegen.spec.ts 14 KB

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