codegen.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  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. ConditionalExpression,
  19. CacheExpression,
  20. locStub,
  21. SSRCodegenNode,
  22. TemplateLiteral,
  23. IfStatement,
  24. AssignmentExpression,
  25. ReturnStatement,
  26. VNodeCall,
  27. SequenceExpression
  28. } from './ast'
  29. import { SourceMapGenerator, RawSourceMap } from 'source-map'
  30. import {
  31. advancePositionWithMutation,
  32. assert,
  33. getVNodeBlockHelper,
  34. getVNodeHelper,
  35. isSimpleIdentifier,
  36. toValidAssetId
  37. } from './utils'
  38. import { isString, isArray, isSymbol } from '@vue/shared'
  39. import {
  40. helperNameMap,
  41. TO_DISPLAY_STRING,
  42. CREATE_VNODE,
  43. RESOLVE_COMPONENT,
  44. RESOLVE_DIRECTIVE,
  45. SET_BLOCK_TRACKING,
  46. CREATE_COMMENT,
  47. CREATE_TEXT,
  48. PUSH_SCOPE_ID,
  49. POP_SCOPE_ID,
  50. WITH_DIRECTIVES,
  51. CREATE_ELEMENT_VNODE,
  52. OPEN_BLOCK,
  53. CREATE_STATIC,
  54. WITH_CTX,
  55. RESOLVE_FILTER
  56. } from './runtimeHelpers'
  57. import { ImportItem } from './transform'
  58. const PURE_ANNOTATION = `/*#__PURE__*/`
  59. type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
  60. export interface CodegenResult {
  61. code: string
  62. preamble: string
  63. ast: RootNode
  64. map?: RawSourceMap
  65. }
  66. export interface CodegenContext
  67. extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
  68. source: string
  69. code: string
  70. line: number
  71. column: number
  72. offset: number
  73. indentLevel: number
  74. pure: boolean
  75. map?: SourceMapGenerator
  76. helper(key: symbol): string
  77. push(code: string, node?: CodegenNode): void
  78. indent(): void
  79. deindent(withoutNewLine?: boolean): void
  80. newline(): void
  81. }
  82. function createCodegenContext(
  83. ast: RootNode,
  84. {
  85. mode = 'function',
  86. prefixIdentifiers = mode === 'module',
  87. sourceMap = false,
  88. filename = `template.vue.html`,
  89. scopeId = null,
  90. optimizeImports = false,
  91. runtimeGlobalName = `Vue`,
  92. runtimeModuleName = `vue`,
  93. ssrRuntimeModuleName = 'vue/server-renderer',
  94. ssr = false,
  95. isTS = false,
  96. inSSR = false
  97. }: CodegenOptions
  98. ): CodegenContext {
  99. const context: CodegenContext = {
  100. mode,
  101. prefixIdentifiers,
  102. sourceMap,
  103. filename,
  104. scopeId,
  105. optimizeImports,
  106. runtimeGlobalName,
  107. runtimeModuleName,
  108. ssrRuntimeModuleName,
  109. ssr,
  110. isTS,
  111. inSSR,
  112. source: ast.loc.source,
  113. code: ``,
  114. column: 1,
  115. line: 1,
  116. offset: 0,
  117. indentLevel: 0,
  118. pure: false,
  119. map: undefined,
  120. helper(key) {
  121. return `_${helperNameMap[key]}`
  122. },
  123. push(code, node) {
  124. context.code += code
  125. if (!__BROWSER__ && context.map) {
  126. if (node) {
  127. let name
  128. if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
  129. const content = node.content.replace(/^_ctx\./, '')
  130. if (content !== node.content && isSimpleIdentifier(content)) {
  131. name = content
  132. }
  133. }
  134. addMapping(node.loc.start, name)
  135. }
  136. advancePositionWithMutation(context, code)
  137. if (node && node.loc !== locStub) {
  138. addMapping(node.loc.end)
  139. }
  140. }
  141. },
  142. indent() {
  143. newline(++context.indentLevel)
  144. },
  145. deindent(withoutNewLine = false) {
  146. if (withoutNewLine) {
  147. --context.indentLevel
  148. } else {
  149. newline(--context.indentLevel)
  150. }
  151. },
  152. newline() {
  153. newline(context.indentLevel)
  154. }
  155. }
  156. function newline(n: number) {
  157. context.push('\n' + ` `.repeat(n))
  158. }
  159. function addMapping(loc: Position, name?: string) {
  160. context.map!.addMapping({
  161. name,
  162. source: context.filename,
  163. original: {
  164. line: loc.line,
  165. column: loc.column - 1 // source-map column is 0 based
  166. },
  167. generated: {
  168. line: context.line,
  169. column: context.column - 1
  170. }
  171. })
  172. }
  173. if (!__BROWSER__ && sourceMap) {
  174. // lazy require source-map implementation, only in non-browser builds
  175. context.map = new SourceMapGenerator()
  176. context.map!.setSourceContent(filename, context.source)
  177. }
  178. return context
  179. }
  180. export function generate(
  181. ast: RootNode,
  182. options: CodegenOptions & {
  183. onContextCreated?: (context: CodegenContext) => void
  184. } = {}
  185. ): CodegenResult {
  186. const context = createCodegenContext(ast, options)
  187. if (options.onContextCreated) options.onContextCreated(context)
  188. const {
  189. mode,
  190. push,
  191. prefixIdentifiers,
  192. indent,
  193. deindent,
  194. newline,
  195. scopeId,
  196. ssr
  197. } = context
  198. const hasHelpers = ast.helpers.length > 0
  199. const useWithBlock = !prefixIdentifiers && mode !== 'module'
  200. const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
  201. const isSetupInlined = !__BROWSER__ && !!options.inline
  202. // preambles
  203. // in setup() inline mode, the preamble is generated in a sub context
  204. // and returned separately.
  205. const preambleContext = isSetupInlined
  206. ? createCodegenContext(ast, options)
  207. : context
  208. if (!__BROWSER__ && mode === 'module') {
  209. genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
  210. } else {
  211. genFunctionPreamble(ast, preambleContext)
  212. }
  213. // enter render function
  214. const functionName = ssr ? `ssrRender` : `render`
  215. const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
  216. if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
  217. // binding optimization args
  218. args.push('$props', '$setup', '$data', '$options')
  219. }
  220. const signature =
  221. !__BROWSER__ && options.isTS
  222. ? args.map(arg => `${arg}: any`).join(',')
  223. : args.join(', ')
  224. if (isSetupInlined) {
  225. push(`(${signature}) => {`)
  226. } else {
  227. push(`function ${functionName}(${signature}) {`)
  228. }
  229. indent()
  230. if (useWithBlock) {
  231. push(`with (_ctx) {`)
  232. indent()
  233. // function mode const declarations should be inside with block
  234. // also they should be renamed to avoid collision with user properties
  235. if (hasHelpers) {
  236. push(
  237. `const { ${ast.helpers
  238. .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
  239. .join(', ')} } = _Vue`
  240. )
  241. push(`\n`)
  242. newline()
  243. }
  244. }
  245. // generate asset resolution statements
  246. if (ast.components.length) {
  247. genAssets(ast.components, 'component', context)
  248. if (ast.directives.length || ast.temps > 0) {
  249. newline()
  250. }
  251. }
  252. if (ast.directives.length) {
  253. genAssets(ast.directives, 'directive', context)
  254. if (ast.temps > 0) {
  255. newline()
  256. }
  257. }
  258. if (__COMPAT__ && ast.filters && ast.filters.length) {
  259. newline()
  260. genAssets(ast.filters, 'filter', context)
  261. newline()
  262. }
  263. if (ast.temps > 0) {
  264. push(`let `)
  265. for (let i = 0; i < ast.temps; i++) {
  266. push(`${i > 0 ? `, ` : ``}_temp${i}`)
  267. }
  268. }
  269. if (ast.components.length || ast.directives.length || ast.temps) {
  270. push(`\n`)
  271. newline()
  272. }
  273. // generate the VNode tree expression
  274. if (!ssr) {
  275. push(`return `)
  276. }
  277. if (ast.codegenNode) {
  278. genNode(ast.codegenNode, context)
  279. } else {
  280. push(`null`)
  281. }
  282. if (useWithBlock) {
  283. deindent()
  284. push(`}`)
  285. }
  286. deindent()
  287. push(`}`)
  288. return {
  289. ast,
  290. code: context.code,
  291. preamble: isSetupInlined ? preambleContext.code : ``,
  292. // SourceMapGenerator does have toJSON() method but it's not in the types
  293. map: context.map ? (context.map as any).toJSON() : undefined
  294. }
  295. }
  296. function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
  297. const {
  298. ssr,
  299. prefixIdentifiers,
  300. push,
  301. newline,
  302. runtimeModuleName,
  303. runtimeGlobalName,
  304. ssrRuntimeModuleName
  305. } = context
  306. const VueBinding =
  307. !__BROWSER__ && ssr
  308. ? `require(${JSON.stringify(runtimeModuleName)})`
  309. : runtimeGlobalName
  310. const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
  311. // Generate const declaration for helpers
  312. // In prefix mode, we place the const declaration at top so it's done
  313. // only once; But if we not prefixing, we place the declaration inside the
  314. // with block so it doesn't incur the `in` check cost for every helper access.
  315. if (ast.helpers.length > 0) {
  316. if (!__BROWSER__ && prefixIdentifiers) {
  317. push(
  318. `const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`
  319. )
  320. } else {
  321. // "with" mode.
  322. // save Vue in a separate variable to avoid collision
  323. push(`const _Vue = ${VueBinding}\n`)
  324. // in "with" mode, helpers are declared inside the with block to avoid
  325. // has check cost, but hoists are lifted out of the function - we need
  326. // to provide the helper here.
  327. if (ast.hoists.length) {
  328. const staticHelpers = [
  329. CREATE_VNODE,
  330. CREATE_ELEMENT_VNODE,
  331. CREATE_COMMENT,
  332. CREATE_TEXT,
  333. CREATE_STATIC
  334. ]
  335. .filter(helper => ast.helpers.includes(helper))
  336. .map(aliasHelper)
  337. .join(', ')
  338. push(`const { ${staticHelpers} } = _Vue\n`)
  339. }
  340. }
  341. }
  342. // generate variables for ssr helpers
  343. if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
  344. // ssr guarantees prefixIdentifier: true
  345. push(
  346. `const { ${ast.ssrHelpers
  347. .map(aliasHelper)
  348. .join(', ')} } = require("${ssrRuntimeModuleName}")\n`
  349. )
  350. }
  351. genHoists(ast.hoists, context)
  352. newline()
  353. push(`return `)
  354. }
  355. function genModulePreamble(
  356. ast: RootNode,
  357. context: CodegenContext,
  358. genScopeId: boolean,
  359. inline?: boolean
  360. ) {
  361. const {
  362. push,
  363. newline,
  364. optimizeImports,
  365. runtimeModuleName,
  366. ssrRuntimeModuleName
  367. } = context
  368. if (genScopeId && ast.hoists.length) {
  369. ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
  370. }
  371. // generate import statements for helpers
  372. if (ast.helpers.length) {
  373. if (optimizeImports) {
  374. // when bundled with webpack with code-split, calling an import binding
  375. // as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`,
  376. // incurring both payload size increase and potential perf overhead.
  377. // therefore we assign the imports to variables (which is a constant ~50b
  378. // cost per-component instead of scaling with template size)
  379. push(
  380. `import { ${ast.helpers
  381. .map(s => helperNameMap[s])
  382. .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
  383. )
  384. push(
  385. `\n// Binding optimization for webpack code-split\nconst ${ast.helpers
  386. .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
  387. .join(', ')}\n`
  388. )
  389. } else {
  390. push(
  391. `import { ${ast.helpers
  392. .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
  393. .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
  394. )
  395. }
  396. }
  397. if (ast.ssrHelpers && ast.ssrHelpers.length) {
  398. push(
  399. `import { ${ast.ssrHelpers
  400. .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
  401. .join(', ')} } from "${ssrRuntimeModuleName}"\n`
  402. )
  403. }
  404. if (ast.imports.length) {
  405. genImports(ast.imports, context)
  406. newline()
  407. }
  408. genHoists(ast.hoists, context)
  409. newline()
  410. if (!inline) {
  411. push(`export `)
  412. }
  413. }
  414. function genAssets(
  415. assets: string[],
  416. type: 'component' | 'directive' | 'filter',
  417. { helper, push, newline, isTS }: CodegenContext
  418. ) {
  419. const resolver = helper(
  420. __COMPAT__ && type === 'filter'
  421. ? RESOLVE_FILTER
  422. : type === 'component'
  423. ? RESOLVE_COMPONENT
  424. : RESOLVE_DIRECTIVE
  425. )
  426. for (let i = 0; i < assets.length; i++) {
  427. let id = assets[i]
  428. // potential component implicit self-reference inferred from SFC filename
  429. const maybeSelfReference = id.endsWith('__self')
  430. if (maybeSelfReference) {
  431. id = id.slice(0, -6)
  432. }
  433. push(
  434. `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
  435. maybeSelfReference ? `, true` : ``
  436. })${isTS ? `!` : ``}`
  437. )
  438. if (i < assets.length - 1) {
  439. newline()
  440. }
  441. }
  442. }
  443. function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
  444. if (!hoists.length) {
  445. return
  446. }
  447. context.pure = true
  448. const { push, newline, helper, scopeId, mode } = context
  449. const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
  450. newline()
  451. // generate inlined withScopeId helper
  452. if (genScopeId) {
  453. push(
  454. `const _withScopeId = n => (${helper(
  455. PUSH_SCOPE_ID
  456. )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`
  457. )
  458. newline()
  459. }
  460. for (let i = 0; i < hoists.length; i++) {
  461. const exp = hoists[i]
  462. if (exp) {
  463. const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
  464. push(
  465. `const _hoisted_${i + 1} = ${
  466. needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
  467. }`
  468. )
  469. genNode(exp, context)
  470. if (needScopeIdWrapper) {
  471. push(`)`)
  472. }
  473. newline()
  474. }
  475. }
  476. context.pure = false
  477. }
  478. function genImports(importsOptions: ImportItem[], context: CodegenContext) {
  479. if (!importsOptions.length) {
  480. return
  481. }
  482. importsOptions.forEach(imports => {
  483. context.push(`import `)
  484. genNode(imports.exp, context)
  485. context.push(` from '${imports.path}'`)
  486. context.newline()
  487. })
  488. }
  489. function isText(n: string | CodegenNode) {
  490. return (
  491. isString(n) ||
  492. n.type === NodeTypes.SIMPLE_EXPRESSION ||
  493. n.type === NodeTypes.TEXT ||
  494. n.type === NodeTypes.INTERPOLATION ||
  495. n.type === NodeTypes.COMPOUND_EXPRESSION
  496. )
  497. }
  498. function genNodeListAsArray(
  499. nodes: (string | CodegenNode | TemplateChildNode[])[],
  500. context: CodegenContext
  501. ) {
  502. const multilines =
  503. nodes.length > 3 ||
  504. ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
  505. context.push(`[`)
  506. multilines && context.indent()
  507. genNodeList(nodes, context, multilines)
  508. multilines && context.deindent()
  509. context.push(`]`)
  510. }
  511. function genNodeList(
  512. nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
  513. context: CodegenContext,
  514. multilines: boolean = false,
  515. comma: boolean = true
  516. ) {
  517. const { push, newline } = context
  518. for (let i = 0; i < nodes.length; i++) {
  519. const node = nodes[i]
  520. if (isString(node)) {
  521. push(node)
  522. } else if (isArray(node)) {
  523. genNodeListAsArray(node, context)
  524. } else {
  525. genNode(node, context)
  526. }
  527. if (i < nodes.length - 1) {
  528. if (multilines) {
  529. comma && push(',')
  530. newline()
  531. } else {
  532. comma && push(', ')
  533. }
  534. }
  535. }
  536. }
  537. function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
  538. if (isString(node)) {
  539. context.push(node)
  540. return
  541. }
  542. if (isSymbol(node)) {
  543. context.push(context.helper(node))
  544. return
  545. }
  546. switch (node.type) {
  547. case NodeTypes.ELEMENT:
  548. case NodeTypes.IF:
  549. case NodeTypes.FOR:
  550. __DEV__ &&
  551. assert(
  552. node.codegenNode != null,
  553. `Codegen node is missing for element/if/for node. ` +
  554. `Apply appropriate transforms first.`
  555. )
  556. genNode(node.codegenNode!, context)
  557. break
  558. case NodeTypes.TEXT:
  559. genText(node, context)
  560. break
  561. case NodeTypes.SIMPLE_EXPRESSION:
  562. genExpression(node, context)
  563. break
  564. case NodeTypes.INTERPOLATION:
  565. genInterpolation(node, context)
  566. break
  567. case NodeTypes.TEXT_CALL:
  568. genNode(node.codegenNode, context)
  569. break
  570. case NodeTypes.COMPOUND_EXPRESSION:
  571. genCompoundExpression(node, context)
  572. break
  573. case NodeTypes.COMMENT:
  574. genComment(node, context)
  575. break
  576. case NodeTypes.VNODE_CALL:
  577. genVNodeCall(node, context)
  578. break
  579. case NodeTypes.JS_CALL_EXPRESSION:
  580. genCallExpression(node, context)
  581. break
  582. case NodeTypes.JS_OBJECT_EXPRESSION:
  583. genObjectExpression(node, context)
  584. break
  585. case NodeTypes.JS_ARRAY_EXPRESSION:
  586. genArrayExpression(node, context)
  587. break
  588. case NodeTypes.JS_FUNCTION_EXPRESSION:
  589. genFunctionExpression(node, context)
  590. break
  591. case NodeTypes.JS_CONDITIONAL_EXPRESSION:
  592. genConditionalExpression(node, context)
  593. break
  594. case NodeTypes.JS_CACHE_EXPRESSION:
  595. genCacheExpression(node, context)
  596. break
  597. case NodeTypes.JS_BLOCK_STATEMENT:
  598. genNodeList(node.body, context, true, false)
  599. break
  600. // SSR only types
  601. case NodeTypes.JS_TEMPLATE_LITERAL:
  602. !__BROWSER__ && genTemplateLiteral(node, context)
  603. break
  604. case NodeTypes.JS_IF_STATEMENT:
  605. !__BROWSER__ && genIfStatement(node, context)
  606. break
  607. case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
  608. !__BROWSER__ && genAssignmentExpression(node, context)
  609. break
  610. case NodeTypes.JS_SEQUENCE_EXPRESSION:
  611. !__BROWSER__ && genSequenceExpression(node, context)
  612. break
  613. case NodeTypes.JS_RETURN_STATEMENT:
  614. !__BROWSER__ && genReturnStatement(node, context)
  615. break
  616. /* istanbul ignore next */
  617. case NodeTypes.IF_BRANCH:
  618. // noop
  619. break
  620. default:
  621. if (__DEV__) {
  622. assert(false, `unhandled codegen node type: ${(node as any).type}`)
  623. // make sure we exhaust all possible types
  624. const exhaustiveCheck: never = node
  625. return exhaustiveCheck
  626. }
  627. }
  628. }
  629. function genText(
  630. node: TextNode | SimpleExpressionNode,
  631. context: CodegenContext
  632. ) {
  633. context.push(JSON.stringify(node.content), node)
  634. }
  635. function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
  636. const { content, isStatic } = node
  637. context.push(isStatic ? JSON.stringify(content) : content, node)
  638. }
  639. function genInterpolation(node: InterpolationNode, context: CodegenContext) {
  640. const { push, helper, pure } = context
  641. if (pure) push(PURE_ANNOTATION)
  642. push(`${helper(TO_DISPLAY_STRING)}(`)
  643. genNode(node.content, context)
  644. push(`)`)
  645. }
  646. function genCompoundExpression(
  647. node: CompoundExpressionNode,
  648. context: CodegenContext
  649. ) {
  650. for (let i = 0; i < node.children!.length; i++) {
  651. const child = node.children![i]
  652. if (isString(child)) {
  653. context.push(child)
  654. } else {
  655. genNode(child, context)
  656. }
  657. }
  658. }
  659. function genExpressionAsPropertyKey(
  660. node: ExpressionNode,
  661. context: CodegenContext
  662. ) {
  663. const { push } = context
  664. if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
  665. push(`[`)
  666. genCompoundExpression(node, context)
  667. push(`]`)
  668. } else if (node.isStatic) {
  669. // only quote keys if necessary
  670. const text = isSimpleIdentifier(node.content)
  671. ? node.content
  672. : JSON.stringify(node.content)
  673. push(text, node)
  674. } else {
  675. push(`[${node.content}]`, node)
  676. }
  677. }
  678. function genComment(node: CommentNode, context: CodegenContext) {
  679. const { push, helper, pure } = context
  680. if (pure) {
  681. push(PURE_ANNOTATION)
  682. }
  683. push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
  684. }
  685. function genVNodeCall(node: VNodeCall, context: CodegenContext) {
  686. const { push, helper, pure } = context
  687. const {
  688. tag,
  689. props,
  690. children,
  691. patchFlag,
  692. dynamicProps,
  693. directives,
  694. isBlock,
  695. disableTracking,
  696. isComponent
  697. } = node
  698. if (directives) {
  699. push(helper(WITH_DIRECTIVES) + `(`)
  700. }
  701. if (isBlock) {
  702. push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
  703. }
  704. if (pure) {
  705. push(PURE_ANNOTATION)
  706. }
  707. const callHelper: symbol = isBlock
  708. ? getVNodeBlockHelper(context.inSSR, isComponent)
  709. : getVNodeHelper(context.inSSR, isComponent)
  710. push(helper(callHelper) + `(`, node)
  711. genNodeList(
  712. genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
  713. context
  714. )
  715. push(`)`)
  716. if (isBlock) {
  717. push(`)`)
  718. }
  719. if (directives) {
  720. push(`, `)
  721. genNode(directives, context)
  722. push(`)`)
  723. }
  724. }
  725. function genNullableArgs(args: any[]): CallExpression['arguments'] {
  726. let i = args.length
  727. while (i--) {
  728. if (args[i] != null) break
  729. }
  730. return args.slice(0, i + 1).map(arg => arg || `null`)
  731. }
  732. // JavaScript
  733. function genCallExpression(node: CallExpression, context: CodegenContext) {
  734. const { push, helper, pure } = context
  735. const callee = isString(node.callee) ? node.callee : helper(node.callee)
  736. if (pure) {
  737. push(PURE_ANNOTATION)
  738. }
  739. push(callee + `(`, node)
  740. genNodeList(node.arguments, context)
  741. push(`)`)
  742. }
  743. function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
  744. const { push, indent, deindent, newline } = context
  745. const { properties } = node
  746. if (!properties.length) {
  747. push(`{}`, node)
  748. return
  749. }
  750. const multilines =
  751. properties.length > 1 ||
  752. ((!__BROWSER__ || __DEV__) &&
  753. properties.some(p => p.value.type !== NodeTypes.SIMPLE_EXPRESSION))
  754. push(multilines ? `{` : `{ `)
  755. multilines && indent()
  756. for (let i = 0; i < properties.length; i++) {
  757. const { key, value } = properties[i]
  758. // key
  759. genExpressionAsPropertyKey(key, context)
  760. push(`: `)
  761. // value
  762. genNode(value, context)
  763. if (i < properties.length - 1) {
  764. // will only reach this if it's multilines
  765. push(`,`)
  766. newline()
  767. }
  768. }
  769. multilines && deindent()
  770. push(multilines ? `}` : ` }`)
  771. }
  772. function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
  773. genNodeListAsArray(node.elements as CodegenNode[], context)
  774. }
  775. function genFunctionExpression(
  776. node: FunctionExpression,
  777. context: CodegenContext
  778. ) {
  779. const { push, indent, deindent } = context
  780. const { params, returns, body, newline, isSlot } = node
  781. if (isSlot) {
  782. // wrap slot functions with owner context
  783. push(`_${helperNameMap[WITH_CTX]}(`)
  784. }
  785. push(`(`, node)
  786. if (isArray(params)) {
  787. genNodeList(params, context)
  788. } else if (params) {
  789. genNode(params, context)
  790. }
  791. push(`) => `)
  792. if (newline || body) {
  793. push(`{`)
  794. indent()
  795. }
  796. if (returns) {
  797. if (newline) {
  798. push(`return `)
  799. }
  800. if (isArray(returns)) {
  801. genNodeListAsArray(returns, context)
  802. } else {
  803. genNode(returns, context)
  804. }
  805. } else if (body) {
  806. genNode(body, context)
  807. }
  808. if (newline || body) {
  809. deindent()
  810. push(`}`)
  811. }
  812. if (isSlot) {
  813. if (__COMPAT__ && node.isNonScopedSlot) {
  814. push(`, undefined, true`)
  815. }
  816. push(`)`)
  817. }
  818. }
  819. function genConditionalExpression(
  820. node: ConditionalExpression,
  821. context: CodegenContext
  822. ) {
  823. const { test, consequent, alternate, newline: needNewline } = node
  824. const { push, indent, deindent, newline } = context
  825. if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
  826. const needsParens = !isSimpleIdentifier(test.content)
  827. needsParens && push(`(`)
  828. genExpression(test, context)
  829. needsParens && push(`)`)
  830. } else {
  831. push(`(`)
  832. genNode(test, context)
  833. push(`)`)
  834. }
  835. needNewline && indent()
  836. context.indentLevel++
  837. needNewline || push(` `)
  838. push(`? `)
  839. genNode(consequent, context)
  840. context.indentLevel--
  841. needNewline && newline()
  842. needNewline || push(` `)
  843. push(`: `)
  844. const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
  845. if (!isNested) {
  846. context.indentLevel++
  847. }
  848. genNode(alternate, context)
  849. if (!isNested) {
  850. context.indentLevel--
  851. }
  852. needNewline && deindent(true /* without newline */)
  853. }
  854. function genCacheExpression(node: CacheExpression, context: CodegenContext) {
  855. const { push, helper, indent, deindent, newline } = context
  856. push(`_cache[${node.index}] || (`)
  857. if (node.isVNode) {
  858. indent()
  859. push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
  860. newline()
  861. }
  862. push(`_cache[${node.index}] = `)
  863. genNode(node.value, context)
  864. if (node.isVNode) {
  865. push(`,`)
  866. newline()
  867. push(`${helper(SET_BLOCK_TRACKING)}(1),`)
  868. newline()
  869. push(`_cache[${node.index}]`)
  870. deindent()
  871. }
  872. push(`)`)
  873. }
  874. function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
  875. const { push, indent, deindent } = context
  876. push('`')
  877. const l = node.elements.length
  878. const multilines = l > 3
  879. for (let i = 0; i < l; i++) {
  880. const e = node.elements[i]
  881. if (isString(e)) {
  882. push(e.replace(/(`|\$|\\)/g, '\\$1'))
  883. } else {
  884. push('${')
  885. if (multilines) indent()
  886. genNode(e, context)
  887. if (multilines) deindent()
  888. push('}')
  889. }
  890. }
  891. push('`')
  892. }
  893. function genIfStatement(node: IfStatement, context: CodegenContext) {
  894. const { push, indent, deindent } = context
  895. const { test, consequent, alternate } = node
  896. push(`if (`)
  897. genNode(test, context)
  898. push(`) {`)
  899. indent()
  900. genNode(consequent, context)
  901. deindent()
  902. push(`}`)
  903. if (alternate) {
  904. push(` else `)
  905. if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
  906. genIfStatement(alternate, context)
  907. } else {
  908. push(`{`)
  909. indent()
  910. genNode(alternate, context)
  911. deindent()
  912. push(`}`)
  913. }
  914. }
  915. }
  916. function genAssignmentExpression(
  917. node: AssignmentExpression,
  918. context: CodegenContext
  919. ) {
  920. genNode(node.left, context)
  921. context.push(` = `)
  922. genNode(node.right, context)
  923. }
  924. function genSequenceExpression(
  925. node: SequenceExpression,
  926. context: CodegenContext
  927. ) {
  928. context.push(`(`)
  929. genNodeList(node.expressions, context)
  930. context.push(`)`)
  931. }
  932. function genReturnStatement(
  933. { returns }: ReturnStatement,
  934. context: CodegenContext
  935. ) {
  936. context.push(`return `)
  937. if (isArray(returns)) {
  938. genNodeListAsArray(returns, context)
  939. } else {
  940. genNode(returns, context)
  941. }
  942. }