codegen.ts 28 KB

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