codegen.spec.ts 13 KB

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