codegen.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. import { CodegenOptions } from './options'
  2. import {
  3. RootNode,
  4. TemplateChildNode,
  5. TextNode,
  6. CommentNode,
  7. ExpressionNode,
  8. NodeTypes,
  9. JSChildNode,
  10. CallExpression,
  11. ArrayExpression,
  12. ObjectExpression,
  13. Position,
  14. InterpolationNode,
  15. CompoundExpressionNode,
  16. SimpleExpressionNode,
  17. FunctionExpression,
  18. SequenceExpression,
  19. ConditionalExpression,
  20. CacheExpression,
  21. locStub,
  22. SSRCodegenNode,
  23. TemplateLiteral,
  24. IfStatement
  25. } from './ast'
  26. import { SourceMapGenerator, RawSourceMap } from 'source-map'
  27. import {
  28. advancePositionWithMutation,
  29. assert,
  30. isSimpleIdentifier,
  31. loadDep,
  32. toValidAssetId
  33. } from './utils'
  34. import { isString, isArray, isSymbol } from '@vue/shared'
  35. import {
  36. helperNameMap,
  37. TO_DISPLAY_STRING,
  38. CREATE_VNODE,
  39. RESOLVE_COMPONENT,
  40. RESOLVE_DIRECTIVE,
  41. SET_BLOCK_TRACKING,
  42. CREATE_COMMENT,
  43. CREATE_TEXT,
  44. PUSH_SCOPE_ID,
  45. POP_SCOPE_ID,
  46. WITH_SCOPE_ID
  47. } from './runtimeHelpers'
  48. import { ImportItem } from './transform'
  49. type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
  50. export interface CodegenResult {
  51. code: string
  52. ast: RootNode
  53. map?: RawSourceMap
  54. }
  55. export interface CodegenContext extends Required<CodegenOptions> {
  56. source: string
  57. code: string
  58. line: number
  59. column: number
  60. offset: number
  61. indentLevel: number
  62. map?: SourceMapGenerator
  63. helper(key: symbol): string
  64. push(code: string, node?: CodegenNode): void
  65. indent(): void
  66. deindent(withoutNewLine?: boolean): void
  67. newline(): void
  68. }
  69. function createCodegenContext(
  70. ast: RootNode,
  71. {
  72. mode = 'function',
  73. prefixIdentifiers = mode === 'module',
  74. sourceMap = false,
  75. filename = `template.vue.html`,
  76. scopeId = null,
  77. ssr = false
  78. }: CodegenOptions
  79. ): CodegenContext {
  80. const context: CodegenContext = {
  81. mode,
  82. prefixIdentifiers,
  83. sourceMap,
  84. filename,
  85. scopeId,
  86. ssr,
  87. source: ast.loc.source,
  88. code: ``,
  89. column: 1,
  90. line: 1,
  91. offset: 0,
  92. indentLevel: 0,
  93. map: undefined,
  94. helper(key) {
  95. const name = helperNameMap[key]
  96. return prefixIdentifiers ? name : `_${name}`
  97. },
  98. push(code, node) {
  99. context.code += code
  100. if (!__BROWSER__ && context.map) {
  101. if (node) {
  102. let name
  103. if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
  104. const content = node.content.replace(/^_ctx\./, '')
  105. if (content !== node.content && isSimpleIdentifier(content)) {
  106. name = content
  107. }
  108. }
  109. addMapping(node.loc.start, name)
  110. }
  111. advancePositionWithMutation(context, code)
  112. if (node && node.loc !== locStub) {
  113. addMapping(node.loc.end)
  114. }
  115. }
  116. },
  117. indent() {
  118. newline(++context.indentLevel)
  119. },
  120. deindent(withoutNewLine = false) {
  121. if (withoutNewLine) {
  122. --context.indentLevel
  123. } else {
  124. newline(--context.indentLevel)
  125. }
  126. },
  127. newline() {
  128. newline(context.indentLevel)
  129. }
  130. }
  131. function newline(n: number) {
  132. context.push('\n' + ` `.repeat(n))
  133. }
  134. function addMapping(loc: Position, name?: string) {
  135. context.map!.addMapping({
  136. name,
  137. source: context.filename,
  138. original: {
  139. line: loc.line,
  140. column: loc.column - 1 // source-map column is 0 based
  141. },
  142. generated: {
  143. line: context.line,
  144. column: context.column - 1
  145. }
  146. })
  147. }
  148. if (!__BROWSER__ && sourceMap) {
  149. // lazy require source-map implementation, only in non-browser builds
  150. context.map = new (loadDep('source-map')).SourceMapGenerator()
  151. context.map!.setSourceContent(filename, context.source)
  152. }
  153. return context
  154. }
  155. export function generate(
  156. ast: RootNode,
  157. options: CodegenOptions = {}
  158. ): CodegenResult {
  159. const context = createCodegenContext(ast, options)
  160. const {
  161. mode,
  162. push,
  163. prefixIdentifiers,
  164. indent,
  165. deindent,
  166. newline,
  167. scopeId,
  168. ssr
  169. } = context
  170. const hasHelpers = ast.helpers.length > 0
  171. const useWithBlock = !prefixIdentifiers && mode !== 'module'
  172. const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
  173. // preambles
  174. if (mode === 'module') {
  175. genModulePreamble(ast, context, genScopeId)
  176. } else {
  177. genFunctionPreamble(ast, context)
  178. }
  179. // enter render function
  180. if (genScopeId && !ssr) {
  181. push(`const render = withId(`)
  182. }
  183. if (!ssr) {
  184. push(`function render() {`)
  185. } else {
  186. push(`function ssrRender(_ctx, _push, _parent) {`)
  187. }
  188. indent()
  189. if (useWithBlock) {
  190. push(`with (this) {`)
  191. indent()
  192. // function mode const declarations should be inside with block
  193. // also they should be renamed to avoid collision with user properties
  194. if (hasHelpers) {
  195. push(
  196. `const { ${ast.helpers
  197. .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
  198. .join(', ')} } = _Vue`
  199. )
  200. newline()
  201. if (ast.cached > 0) {
  202. push(`const _cache = $cache`)
  203. newline()
  204. }
  205. newline()
  206. }
  207. } else if (!ssr) {
  208. push(`const _ctx = this`)
  209. if (ast.cached > 0) {
  210. newline()
  211. push(`const _cache = _ctx.$cache`)
  212. }
  213. newline()
  214. }
  215. // generate asset resolution statements
  216. if (ast.components.length) {
  217. genAssets(ast.components, 'component', context)
  218. }
  219. if (ast.directives.length) {
  220. genAssets(ast.directives, 'directive', context)
  221. }
  222. if (ast.components.length || ast.directives.length) {
  223. newline()
  224. }
  225. // generate the VNode tree expression
  226. if (!ssr) {
  227. push(`return `)
  228. }
  229. if (ast.codegenNode) {
  230. genNode(ast.codegenNode, context)
  231. } else {
  232. push(`null`)
  233. }
  234. if (useWithBlock) {
  235. deindent()
  236. push(`}`)
  237. }
  238. deindent()
  239. push(`}`)
  240. if (genScopeId && !ssr) {
  241. push(`)`)
  242. }
  243. return {
  244. ast,
  245. code: context.code,
  246. // SourceMapGenerator does have toJSON() method but it's not in the types
  247. map: context.map ? (context.map as any).toJSON() : undefined
  248. }
  249. }
  250. function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
  251. const { ssr, helper, prefixIdentifiers, push, newline } = context
  252. const VueBinding = ssr ? `require("vue")` : `Vue`
  253. // Generate const declaration for helpers
  254. // In prefix mode, we place the const declaration at top so it's done
  255. // only once; But if we not prefixing, we place the declaration inside the
  256. // with block so it doesn't incur the `in` check cost for every helper access.
  257. if (ast.helpers.length > 0) {
  258. if (prefixIdentifiers) {
  259. push(`const { ${ast.helpers.map(helper).join(', ')} } = ${VueBinding}\n`)
  260. } else {
  261. // "with" mode.
  262. // save Vue in a separate variable to avoid collision
  263. push(`const _Vue = ${VueBinding}\n`)
  264. // in "with" mode, helpers are declared inside the with block to avoid
  265. // has check cost, but hoists are lifted out of the function - we need
  266. // to provide the helper here.
  267. if (ast.hoists.length) {
  268. const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
  269. .filter(helper => ast.helpers.includes(helper))
  270. .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
  271. .join(', ')
  272. push(`const { ${staticHelpers} } = _Vue\n`)
  273. }
  274. }
  275. }
  276. // generate variables for ssr helpers
  277. if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
  278. // ssr guaruntees prefixIdentifier: true
  279. push(
  280. `const { ${ast.ssrHelpers
  281. .map(helper)
  282. .join(', ')} } = require("@vue/server-renderer")\n`
  283. )
  284. }
  285. genHoists(ast.hoists, context)
  286. newline()
  287. push(`return `)
  288. }
  289. function genModulePreamble(
  290. ast: RootNode,
  291. context: CodegenContext,
  292. genScopeId: boolean
  293. ) {
  294. const { push, helper, newline, scopeId } = context
  295. // generate import statements for helpers
  296. if (genScopeId) {
  297. ast.helpers.push(WITH_SCOPE_ID)
  298. if (ast.hoists.length) {
  299. ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
  300. }
  301. }
  302. if (ast.helpers.length) {
  303. push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
  304. }
  305. if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
  306. push(
  307. `import { ${ast.ssrHelpers
  308. .map(helper)
  309. .join(', ')} } from "@vue/server-renderer"\n`
  310. )
  311. }
  312. if (ast.imports.length) {
  313. genImports(ast.imports, context)
  314. newline()
  315. }
  316. if (genScopeId) {
  317. push(`const withId = ${helper(WITH_SCOPE_ID)}("${scopeId}")`)
  318. newline()
  319. }
  320. genHoists(ast.hoists, context)
  321. newline()
  322. push(`export `)
  323. }
  324. function genAssets(
  325. assets: string[],
  326. type: 'component' | 'directive',
  327. context: CodegenContext
  328. ) {
  329. const resolver = context.helper(
  330. type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
  331. )
  332. for (let i = 0; i < assets.length; i++) {
  333. const id = assets[i]
  334. context.push(
  335. `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
  336. )
  337. context.newline()
  338. }
  339. }
  340. function genHoists(hoists: JSChildNode[], context: CodegenContext) {
  341. if (!hoists.length) {
  342. return
  343. }
  344. const { push, newline, helper, scopeId, mode } = context
  345. const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
  346. newline()
  347. // push scope Id before initilaizing hoisted vnodes so that these vnodes
  348. // get the proper scopeId as well.
  349. if (genScopeId) {
  350. push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
  351. newline()
  352. }
  353. hoists.forEach((exp, i) => {
  354. push(`const _hoisted_${i + 1} = `)
  355. genNode(exp, context)
  356. newline()
  357. })
  358. if (genScopeId) {
  359. push(`${helper(POP_SCOPE_ID)}()`)
  360. newline()
  361. }
  362. }
  363. function genImports(importsOptions: ImportItem[], context: CodegenContext) {
  364. if (!importsOptions.length) {
  365. return
  366. }
  367. importsOptions.forEach(imports => {
  368. context.push(`import `)
  369. genNode(imports.exp, context)
  370. context.push(` from '${imports.path}'`)
  371. context.newline()
  372. })
  373. }
  374. function isText(n: string | CodegenNode) {
  375. return (
  376. isString(n) ||
  377. n.type === NodeTypes.SIMPLE_EXPRESSION ||
  378. n.type === NodeTypes.TEXT ||
  379. n.type === NodeTypes.INTERPOLATION ||
  380. n.type === NodeTypes.COMPOUND_EXPRESSION
  381. )
  382. }
  383. function genNodeListAsArray(
  384. nodes: (string | CodegenNode | TemplateChildNode[])[],
  385. context: CodegenContext
  386. ) {
  387. const multilines =
  388. nodes.length > 3 ||
  389. ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
  390. context.push(`[`)
  391. multilines && context.indent()
  392. genNodeList(nodes, context, multilines)
  393. multilines && context.deindent()
  394. context.push(`]`)
  395. }
  396. function genNodeList(
  397. nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
  398. context: CodegenContext,
  399. multilines: boolean = false,
  400. comma: boolean = true
  401. ) {
  402. const { push, newline } = context
  403. for (let i = 0; i < nodes.length; i++) {
  404. const node = nodes[i]
  405. if (isString(node)) {
  406. push(node)
  407. } else if (isArray(node)) {
  408. genNodeListAsArray(node, context)
  409. } else {
  410. genNode(node, context)
  411. }
  412. if (i < nodes.length - 1) {
  413. if (multilines) {
  414. comma && push(',')
  415. newline()
  416. } else {
  417. comma && push(', ')
  418. }
  419. }
  420. }
  421. }
  422. function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
  423. if (isString(node)) {
  424. context.push(node)
  425. return
  426. }
  427. if (isSymbol(node)) {
  428. context.push(context.helper(node))
  429. return
  430. }
  431. switch (node.type) {
  432. case NodeTypes.ELEMENT:
  433. case NodeTypes.IF:
  434. case NodeTypes.FOR:
  435. __DEV__ &&
  436. assert(
  437. node.codegenNode != null,
  438. `Codegen node is missing for element/if/for node. ` +
  439. `Apply appropriate transforms first.`
  440. )
  441. genNode(node.codegenNode!, context)
  442. break
  443. case NodeTypes.TEXT:
  444. genText(node, context)
  445. break
  446. case NodeTypes.SIMPLE_EXPRESSION:
  447. genExpression(node, context)
  448. break
  449. case NodeTypes.INTERPOLATION:
  450. genInterpolation(node, context)
  451. break
  452. case NodeTypes.TEXT_CALL:
  453. genNode(node.codegenNode, context)
  454. break
  455. case NodeTypes.COMPOUND_EXPRESSION:
  456. genCompoundExpression(node, context)
  457. break
  458. case NodeTypes.COMMENT:
  459. genComment(node, context)
  460. break
  461. case NodeTypes.JS_CALL_EXPRESSION:
  462. genCallExpression(node, context)
  463. break
  464. case NodeTypes.JS_OBJECT_EXPRESSION:
  465. genObjectExpression(node, context)
  466. break
  467. case NodeTypes.JS_ARRAY_EXPRESSION:
  468. genArrayExpression(node, context)
  469. break
  470. case NodeTypes.JS_FUNCTION_EXPRESSION:
  471. genFunctionExpression(node, context)
  472. break
  473. case NodeTypes.JS_SEQUENCE_EXPRESSION:
  474. genSequenceExpression(node, context)
  475. break
  476. case NodeTypes.JS_CONDITIONAL_EXPRESSION:
  477. genConditionalExpression(node, context)
  478. break
  479. case NodeTypes.JS_CACHE_EXPRESSION:
  480. genCacheExpression(node, context)
  481. break
  482. // SSR only types
  483. case NodeTypes.JS_BLOCK_STATEMENT:
  484. !__BROWSER__ && genNodeList(node.body, context, true, false)
  485. break
  486. case NodeTypes.JS_TEMPLATE_LITERAL:
  487. !__BROWSER__ && genTemplateLiteral(node, context)
  488. break
  489. case NodeTypes.JS_IF_STATEMENT:
  490. !__BROWSER__ && genIfStatement(node, context)
  491. break
  492. /* istanbul ignore next */
  493. default:
  494. if (__DEV__) {
  495. assert(false, `unhandled codegen node type: ${(node as any).type}`)
  496. // make sure we exhaust all possible types
  497. const exhaustiveCheck: never = node
  498. return exhaustiveCheck
  499. }
  500. }
  501. }
  502. function genText(
  503. node: TextNode | SimpleExpressionNode,
  504. context: CodegenContext
  505. ) {
  506. context.push(JSON.stringify(node.content), node)
  507. }
  508. function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
  509. const { content, isStatic } = node
  510. context.push(isStatic ? JSON.stringify(content) : content, node)
  511. }
  512. function genInterpolation(node: InterpolationNode, context: CodegenContext) {
  513. const { push, helper } = context
  514. push(`${helper(TO_DISPLAY_STRING)}(`)
  515. genNode(node.content, context)
  516. push(`)`)
  517. }
  518. function genCompoundExpression(
  519. node: CompoundExpressionNode,
  520. context: CodegenContext
  521. ) {
  522. for (let i = 0; i < node.children!.length; i++) {
  523. const child = node.children![i]
  524. if (isString(child)) {
  525. context.push(child)
  526. } else {
  527. genNode(child, context)
  528. }
  529. }
  530. }
  531. function genExpressionAsPropertyKey(
  532. node: ExpressionNode,
  533. context: CodegenContext
  534. ) {
  535. const { push } = context
  536. if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
  537. push(`[`)
  538. genCompoundExpression(node, context)
  539. push(`]`)
  540. } else if (node.isStatic) {
  541. // only quote keys if necessary
  542. const text = isSimpleIdentifier(node.content)
  543. ? node.content
  544. : JSON.stringify(node.content)
  545. push(text, node)
  546. } else {
  547. push(`[${node.content}]`, node)
  548. }
  549. }
  550. function genComment(node: CommentNode, context: CodegenContext) {
  551. if (__DEV__) {
  552. const { push, helper } = context
  553. push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
  554. }
  555. }
  556. // JavaScript
  557. function genCallExpression(node: CallExpression, context: CodegenContext) {
  558. const callee = isString(node.callee)
  559. ? node.callee
  560. : context.helper(node.callee)
  561. context.push(callee + `(`, node)
  562. genNodeList(node.arguments, context)
  563. context.push(`)`)
  564. }
  565. function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
  566. const { push, indent, deindent, newline } = context
  567. const { properties } = node
  568. if (!properties.length) {
  569. push(`{}`, node)
  570. return
  571. }
  572. const multilines =
  573. properties.length > 1 ||
  574. ((!__BROWSER__ || __DEV__) &&
  575. properties.some(p => p.value.type !== NodeTypes.SIMPLE_EXPRESSION))
  576. push(multilines ? `{` : `{ `)
  577. multilines && indent()
  578. for (let i = 0; i < properties.length; i++) {
  579. const { key, value } = properties[i]
  580. // key
  581. genExpressionAsPropertyKey(key, context)
  582. push(`: `)
  583. // value
  584. genNode(value, context)
  585. if (i < properties.length - 1) {
  586. // will only reach this if it's multilines
  587. push(`,`)
  588. newline()
  589. }
  590. }
  591. multilines && deindent()
  592. push(multilines ? `}` : ` }`)
  593. }
  594. function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
  595. genNodeListAsArray(node.elements, context)
  596. }
  597. function genFunctionExpression(
  598. node: FunctionExpression,
  599. context: CodegenContext
  600. ) {
  601. const { push, indent, deindent, scopeId, mode } = context
  602. const { params, returns, body, newline, isSlot } = node
  603. // slot functions also need to push scopeId before rendering its content
  604. const genScopeId =
  605. !__BROWSER__ && isSlot && scopeId != null && mode !== 'function'
  606. if (genScopeId) {
  607. push(`withId(`)
  608. }
  609. push(`(`, node)
  610. if (isArray(params)) {
  611. genNodeList(params, context)
  612. } else if (params) {
  613. genNode(params, context)
  614. }
  615. push(`) => `)
  616. if (newline || body) {
  617. push(`{`)
  618. indent()
  619. }
  620. if (returns) {
  621. if (newline) {
  622. push(`return `)
  623. }
  624. if (isArray(returns)) {
  625. genNodeListAsArray(returns, context)
  626. } else {
  627. genNode(returns, context)
  628. }
  629. } else if (body) {
  630. genNode(body, context)
  631. }
  632. if (newline || body) {
  633. deindent()
  634. push(`}`)
  635. }
  636. if (genScopeId) {
  637. push(`)`)
  638. }
  639. }
  640. function genConditionalExpression(
  641. node: ConditionalExpression,
  642. context: CodegenContext
  643. ) {
  644. const { test, consequent, alternate, newline: needNewline } = node
  645. const { push, indent, deindent, newline } = context
  646. if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
  647. const needsParens = !isSimpleIdentifier(test.content)
  648. needsParens && push(`(`)
  649. genExpression(test, context)
  650. needsParens && push(`)`)
  651. } else {
  652. push(`(`)
  653. genNode(test, context)
  654. push(`)`)
  655. }
  656. needNewline && indent()
  657. context.indentLevel++
  658. push(`? `)
  659. genNode(consequent, context)
  660. context.indentLevel--
  661. needNewline && newline()
  662. push(`: `)
  663. const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
  664. if (!isNested) {
  665. context.indentLevel++
  666. }
  667. genNode(alternate, context)
  668. if (!isNested) {
  669. context.indentLevel--
  670. }
  671. needNewline && deindent(true /* without newline */)
  672. }
  673. function genSequenceExpression(
  674. node: SequenceExpression,
  675. context: CodegenContext
  676. ) {
  677. context.push(`(`)
  678. genNodeList(node.expressions, context)
  679. context.push(`)`)
  680. }
  681. function genCacheExpression(node: CacheExpression, context: CodegenContext) {
  682. const { push, helper, indent, deindent, newline } = context
  683. push(`_cache[${node.index}] || (`)
  684. if (node.isVNode) {
  685. indent()
  686. push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
  687. newline()
  688. }
  689. push(`_cache[${node.index}] = `)
  690. genNode(node.value, context)
  691. if (node.isVNode) {
  692. push(`,`)
  693. newline()
  694. push(`${helper(SET_BLOCK_TRACKING)}(1),`)
  695. newline()
  696. push(`_cache[${node.index}]`)
  697. deindent()
  698. }
  699. push(`)`)
  700. }
  701. function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
  702. const { push, indent, deindent } = context
  703. push('`')
  704. const l = node.elements.length
  705. const multilines = l > 3
  706. for (let i = 0; i < l; i++) {
  707. const e = node.elements[i]
  708. if (isString(e)) {
  709. push(e.replace(/`/g, '\\`'))
  710. } else {
  711. push('${')
  712. if (multilines) indent()
  713. genNode(e, context)
  714. if (multilines) deindent()
  715. push('}')
  716. }
  717. }
  718. push('`')
  719. }
  720. function genIfStatement(node: IfStatement, context: CodegenContext) {
  721. const { push, indent, deindent } = context
  722. const { test, consequent, alternate } = node
  723. push(`if (`)
  724. genNode(test, context)
  725. push(`) {`)
  726. indent()
  727. genNode(consequent, context)
  728. deindent()
  729. push(`}`)
  730. if (alternate) {
  731. push(` else `)
  732. if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
  733. genIfStatement(alternate, context)
  734. } else {
  735. push(`{`)
  736. indent()
  737. genNode(alternate, context)
  738. deindent()
  739. push(`}`)
  740. }
  741. }
  742. }