codegen.ts 28 KB

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