codegen.ts 29 KB

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