codegen.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  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 { isArray, isString, isSymbol } from '@vue/shared'
  39. import {
  40. CREATE_COMMENT,
  41. CREATE_ELEMENT_VNODE,
  42. CREATE_STATIC,
  43. CREATE_TEXT,
  44. CREATE_VNODE,
  45. HOIST_LAZY,
  46. OPEN_BLOCK,
  47. POP_SCOPE_ID,
  48. PUSH_SCOPE_ID,
  49. RESOLVE_COMPONENT,
  50. RESOLVE_DIRECTIVE,
  51. RESOLVE_FILTER,
  52. SET_BLOCK_TRACKING,
  53. TO_DISPLAY_STRING,
  54. WITH_CTX,
  55. WITH_DIRECTIVES,
  56. helperNameMap,
  57. } from './runtimeHelpers'
  58. import type { ImportItem } from './transform'
  59. /**
  60. * The `SourceMapGenerator` type from `source-map-js` is a bit incomplete as it
  61. * misses `toJSON()`. We also need to add types for internal properties which we
  62. * need to access for better performance.
  63. *
  64. * Since TS 5.3, dts generation starts to strangely include broken triple slash
  65. * references for source-map-js, so we are inlining all source map related types
  66. * here to to workaround that.
  67. */
  68. export interface CodegenSourceMapGenerator {
  69. setSourceContent(sourceFile: string, sourceContent: string): void
  70. // SourceMapGenerator has this method but the types do not include it
  71. toJSON(): RawSourceMap
  72. _sources: Set<string>
  73. _names: Set<string>
  74. _mappings: {
  75. add(mapping: MappingItem): void
  76. }
  77. }
  78. export interface RawSourceMap {
  79. file?: string
  80. sourceRoot?: string
  81. version: string
  82. sources: string[]
  83. names: string[]
  84. sourcesContent?: string[]
  85. mappings: string
  86. }
  87. interface MappingItem {
  88. source: string
  89. generatedLine: number
  90. generatedColumn: number
  91. originalLine: number
  92. originalColumn: number
  93. name: string | null
  94. }
  95. const PURE_ANNOTATION = `/*#__PURE__*/`
  96. const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
  97. type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
  98. export interface CodegenResult {
  99. code: string
  100. preamble: string
  101. ast: RootNode
  102. map?: RawSourceMap
  103. }
  104. enum NewlineType {
  105. Start = 0,
  106. End = -1,
  107. None = -2,
  108. Unknown = -3,
  109. }
  110. export interface CodegenContext
  111. extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
  112. source: string
  113. code: string
  114. line: number
  115. column: number
  116. offset: number
  117. indentLevel: number
  118. pure: boolean
  119. map?: CodegenSourceMapGenerator
  120. helper(key: symbol): string
  121. push(code: string, newlineIndex?: number, node?: CodegenNode): void
  122. indent(): void
  123. deindent(withoutNewLine?: boolean): void
  124. newline(): void
  125. }
  126. function createCodegenContext(
  127. ast: RootNode,
  128. {
  129. mode = 'function',
  130. prefixIdentifiers = mode === 'module',
  131. sourceMap = false,
  132. filename = `template.vue.html`,
  133. scopeId = null,
  134. optimizeImports = false,
  135. runtimeGlobalName = `Vue`,
  136. runtimeModuleName = `vue`,
  137. ssrRuntimeModuleName = 'vue/server-renderer',
  138. ssr = false,
  139. isTS = false,
  140. inSSR = false,
  141. }: CodegenOptions,
  142. ): CodegenContext {
  143. const context: CodegenContext = {
  144. mode,
  145. prefixIdentifiers,
  146. sourceMap,
  147. filename,
  148. scopeId,
  149. optimizeImports,
  150. runtimeGlobalName,
  151. runtimeModuleName,
  152. ssrRuntimeModuleName,
  153. ssr,
  154. isTS,
  155. inSSR,
  156. source: ast.source,
  157. code: ``,
  158. column: 1,
  159. line: 1,
  160. offset: 0,
  161. indentLevel: 0,
  162. pure: false,
  163. map: undefined,
  164. helper(key) {
  165. return `_${helperNameMap[key]}`
  166. },
  167. push(code, newlineIndex = NewlineType.None, node) {
  168. context.code += code
  169. if (!__BROWSER__ && context.map) {
  170. if (node) {
  171. let name
  172. if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
  173. const content = node.content.replace(/^_ctx\./, '')
  174. if (content !== node.content && isSimpleIdentifier(content)) {
  175. name = content
  176. }
  177. }
  178. addMapping(node.loc.start, name)
  179. }
  180. if (newlineIndex === NewlineType.Unknown) {
  181. // multiple newlines, full iteration
  182. advancePositionWithMutation(context, code)
  183. } else {
  184. // fast paths
  185. context.offset += code.length
  186. if (newlineIndex === NewlineType.None) {
  187. // no newlines; fast path to avoid newline detection
  188. if (__TEST__ && code.includes('\n')) {
  189. throw new Error(
  190. `CodegenContext.push() called newlineIndex: none, but contains` +
  191. `newlines: ${code.replace(/\n/g, '\\n')}`,
  192. )
  193. }
  194. context.column += code.length
  195. } else {
  196. // single newline at known index
  197. if (newlineIndex === NewlineType.End) {
  198. newlineIndex = code.length - 1
  199. }
  200. if (
  201. __TEST__ &&
  202. (code.charAt(newlineIndex) !== '\n' ||
  203. code.slice(0, newlineIndex).includes('\n') ||
  204. code.slice(newlineIndex + 1).includes('\n'))
  205. ) {
  206. throw new Error(
  207. `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
  208. `but does not conform: ${code.replace(/\n/g, '\\n')}`,
  209. )
  210. }
  211. context.line++
  212. context.column = code.length - newlineIndex
  213. }
  214. }
  215. if (node && node.loc !== locStub) {
  216. addMapping(node.loc.end)
  217. }
  218. }
  219. },
  220. indent() {
  221. newline(++context.indentLevel)
  222. },
  223. deindent(withoutNewLine = false) {
  224. if (withoutNewLine) {
  225. --context.indentLevel
  226. } else {
  227. newline(--context.indentLevel)
  228. }
  229. },
  230. newline() {
  231. newline(context.indentLevel)
  232. },
  233. }
  234. function newline(n: number) {
  235. context.push('\n' + ` `.repeat(n), NewlineType.Start)
  236. }
  237. function addMapping(loc: Position, name: string | null = null) {
  238. // we use the private property to directly add the mapping
  239. // because the addMapping() implementation in source-map-js has a bunch of
  240. // unnecessary arg and validation checks that are pure overhead in our case.
  241. const { _names, _mappings } = context.map!
  242. if (name !== null && !_names.has(name)) _names.add(name)
  243. _mappings.add({
  244. originalLine: loc.line,
  245. originalColumn: loc.column - 1, // source-map column is 0 based
  246. generatedLine: context.line,
  247. generatedColumn: context.column - 1,
  248. source: filename,
  249. name,
  250. })
  251. }
  252. if (!__BROWSER__ && sourceMap) {
  253. // lazy require source-map implementation, only in non-browser builds
  254. context.map =
  255. new SourceMapGenerator() as unknown as CodegenSourceMapGenerator
  256. context.map.setSourceContent(filename, context.source)
  257. context.map._sources.add(filename)
  258. }
  259. return context
  260. }
  261. export function generate(
  262. ast: RootNode,
  263. options: CodegenOptions & {
  264. onContextCreated?: (context: CodegenContext) => void
  265. } = {},
  266. ): CodegenResult {
  267. const context = createCodegenContext(ast, options)
  268. if (options.onContextCreated) options.onContextCreated(context)
  269. const {
  270. mode,
  271. push,
  272. prefixIdentifiers,
  273. indent,
  274. deindent,
  275. newline,
  276. scopeId,
  277. ssr,
  278. } = context
  279. const helpers = Array.from(ast.helpers)
  280. const hasHelpers = helpers.length > 0
  281. const useWithBlock = !prefixIdentifiers && mode !== 'module'
  282. const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
  283. const isSetupInlined = !__BROWSER__ && !!options.inline
  284. // preambles
  285. // in setup() inline mode, the preamble is generated in a sub context
  286. // and returned separately.
  287. const preambleContext = isSetupInlined
  288. ? createCodegenContext(ast, options)
  289. : context
  290. if (!__BROWSER__ && mode === 'module') {
  291. genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
  292. } else {
  293. genFunctionPreamble(ast, preambleContext)
  294. }
  295. // enter render function
  296. const functionName = ssr ? `ssrRender` : `render`
  297. const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
  298. if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
  299. // binding optimization args
  300. args.push('$props', '$setup', '$data', '$options')
  301. }
  302. const signature =
  303. !__BROWSER__ && options.isTS
  304. ? args.map(arg => `${arg}: any`).join(',')
  305. : args.join(', ')
  306. if (isSetupInlined) {
  307. push(`(${signature}) => {`)
  308. } else {
  309. push(`function ${functionName}(${signature}) {`)
  310. }
  311. indent()
  312. if (useWithBlock) {
  313. push(`with (_ctx) {`)
  314. indent()
  315. // function mode const declarations should be inside with block
  316. // also they should be renamed to avoid collision with user properties
  317. if (hasHelpers) {
  318. push(
  319. `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
  320. NewlineType.End,
  321. )
  322. newline()
  323. }
  324. }
  325. // generate asset resolution statements
  326. if (ast.components.length) {
  327. genAssets(ast.components, 'component', context)
  328. if (ast.directives.length || ast.temps > 0) {
  329. newline()
  330. }
  331. }
  332. if (ast.directives.length) {
  333. genAssets(ast.directives, 'directive', context)
  334. if (ast.temps > 0) {
  335. newline()
  336. }
  337. }
  338. if (__COMPAT__ && ast.filters && ast.filters.length) {
  339. newline()
  340. genAssets(ast.filters, 'filter', context)
  341. newline()
  342. }
  343. if (ast.temps > 0) {
  344. push(`let `)
  345. for (let i = 0; i < ast.temps; i++) {
  346. push(`${i > 0 ? `, ` : ``}_temp${i}`)
  347. }
  348. }
  349. if (ast.components.length || ast.directives.length || ast.temps) {
  350. push(`\n`, NewlineType.Start)
  351. newline()
  352. }
  353. // generate the VNode tree expression
  354. if (!ssr) {
  355. push(`return `)
  356. }
  357. if (ast.codegenNode) {
  358. genNode(ast.codegenNode, context)
  359. } else {
  360. push(`null`)
  361. }
  362. if (useWithBlock) {
  363. deindent()
  364. push(`}`)
  365. }
  366. deindent()
  367. push(`}`)
  368. return {
  369. ast,
  370. code: context.code,
  371. preamble: isSetupInlined ? preambleContext.code : ``,
  372. map: context.map ? context.map.toJSON() : undefined,
  373. }
  374. }
  375. function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
  376. const {
  377. ssr,
  378. prefixIdentifiers,
  379. push,
  380. newline,
  381. runtimeModuleName,
  382. runtimeGlobalName,
  383. ssrRuntimeModuleName,
  384. } = context
  385. const VueBinding =
  386. !__BROWSER__ && ssr
  387. ? `require(${JSON.stringify(runtimeModuleName)})`
  388. : runtimeGlobalName
  389. // Generate const declaration for helpers
  390. // In prefix mode, we place the const declaration at top so it's done
  391. // only once; But if we not prefixing, we place the declaration inside the
  392. // with block so it doesn't incur the `in` check cost for every helper access.
  393. const helpers = Array.from(ast.helpers)
  394. if (helpers.length > 0) {
  395. if (!__BROWSER__ && prefixIdentifiers) {
  396. push(
  397. `const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
  398. NewlineType.End,
  399. )
  400. } else {
  401. // "with" mode.
  402. // save Vue in a separate variable to avoid collision
  403. push(`const _Vue = ${VueBinding}\n`, NewlineType.End)
  404. // in "with" mode, helpers are declared inside the with block to avoid
  405. // has check cost, but hoists are lifted out of the function - we need
  406. // to provide the helper here.
  407. if (ast.hoists.length) {
  408. const staticHelpers = [
  409. CREATE_VNODE,
  410. CREATE_ELEMENT_VNODE,
  411. CREATE_COMMENT,
  412. CREATE_TEXT,
  413. CREATE_STATIC,
  414. HOIST_LAZY,
  415. ]
  416. .filter(helper => helpers.includes(helper))
  417. .map(aliasHelper)
  418. .join(', ')
  419. push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
  420. }
  421. }
  422. }
  423. // generate variables for ssr helpers
  424. if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
  425. // ssr guarantees prefixIdentifier: true
  426. push(
  427. `const { ${ast.ssrHelpers
  428. .map(aliasHelper)
  429. .join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
  430. NewlineType.End,
  431. )
  432. }
  433. genHoists(ast.hoists, context)
  434. newline()
  435. push(`return `)
  436. }
  437. function genModulePreamble(
  438. ast: RootNode,
  439. context: CodegenContext,
  440. genScopeId: boolean,
  441. inline?: boolean,
  442. ) {
  443. const {
  444. push,
  445. newline,
  446. optimizeImports,
  447. runtimeModuleName,
  448. ssrRuntimeModuleName,
  449. } = context
  450. if (genScopeId && ast.hoists.length) {
  451. ast.helpers.add(PUSH_SCOPE_ID)
  452. ast.helpers.add(POP_SCOPE_ID)
  453. }
  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, helper, scopeId, mode } = context
  537. const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
  538. newline()
  539. // generate inlined withScopeId helper
  540. if (genScopeId) {
  541. push(
  542. `const _withScopeId = n => (${helper(
  543. PUSH_SCOPE_ID,
  544. )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
  545. )
  546. newline()
  547. }
  548. for (let i = 0; i < hoists.length; i++) {
  549. const exp = hoists[i]
  550. if (exp) {
  551. const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
  552. push(
  553. `const _hoisted_${i + 1} = ${PURE_ANNOTATION} ${helper(HOIST_LAZY)}(() => (${
  554. needScopeIdWrapper ? `_withScopeId(() => ` : ``
  555. }`,
  556. )
  557. genNode(exp, context)
  558. if (needScopeIdWrapper) {
  559. push(`)`)
  560. }
  561. push('))')
  562. newline()
  563. }
  564. }
  565. context.pure = false
  566. }
  567. function genImports(importsOptions: ImportItem[], context: CodegenContext) {
  568. if (!importsOptions.length) {
  569. return
  570. }
  571. importsOptions.forEach(imports => {
  572. context.push(`import `)
  573. genNode(imports.exp, context)
  574. context.push(` from '${imports.path}'`)
  575. context.newline()
  576. })
  577. }
  578. function isText(n: string | CodegenNode) {
  579. return (
  580. isString(n) ||
  581. n.type === NodeTypes.SIMPLE_EXPRESSION ||
  582. n.type === NodeTypes.TEXT ||
  583. n.type === NodeTypes.INTERPOLATION ||
  584. n.type === NodeTypes.COMPOUND_EXPRESSION
  585. )
  586. }
  587. function genNodeListAsArray(
  588. nodes: (string | CodegenNode | TemplateChildNode[])[],
  589. context: CodegenContext,
  590. ) {
  591. const multilines =
  592. nodes.length > 3 ||
  593. ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
  594. context.push(`[`)
  595. multilines && context.indent()
  596. genNodeList(nodes, context, multilines)
  597. multilines && context.deindent()
  598. context.push(`]`)
  599. }
  600. function genNodeList(
  601. nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
  602. context: CodegenContext,
  603. multilines: boolean = false,
  604. comma: boolean = true,
  605. ) {
  606. const { push, newline } = context
  607. for (let i = 0; i < nodes.length; i++) {
  608. const node = nodes[i]
  609. if (isString(node)) {
  610. push(node, NewlineType.Unknown)
  611. } else if (isArray(node)) {
  612. genNodeListAsArray(node, context)
  613. } else {
  614. genNode(node, context)
  615. }
  616. if (i < nodes.length - 1) {
  617. if (multilines) {
  618. comma && push(',')
  619. newline()
  620. } else {
  621. comma && push(', ')
  622. }
  623. }
  624. }
  625. }
  626. function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
  627. if (isString(node)) {
  628. context.push(node, NewlineType.Unknown)
  629. return
  630. }
  631. if (isSymbol(node)) {
  632. context.push(context.helper(node))
  633. return
  634. }
  635. switch (node.type) {
  636. case NodeTypes.ELEMENT:
  637. case NodeTypes.IF:
  638. case NodeTypes.FOR:
  639. __DEV__ &&
  640. assert(
  641. node.codegenNode != null,
  642. `Codegen node is missing for element/if/for node. ` +
  643. `Apply appropriate transforms first.`,
  644. )
  645. genNode(node.codegenNode!, context)
  646. break
  647. case NodeTypes.TEXT:
  648. genText(node, context)
  649. break
  650. case NodeTypes.SIMPLE_EXPRESSION:
  651. genExpression(node, context)
  652. break
  653. case NodeTypes.INTERPOLATION:
  654. genInterpolation(node, context)
  655. break
  656. case NodeTypes.TEXT_CALL:
  657. genNode(node.codegenNode, context)
  658. break
  659. case NodeTypes.COMPOUND_EXPRESSION:
  660. genCompoundExpression(node, context)
  661. break
  662. case NodeTypes.COMMENT:
  663. genComment(node, context)
  664. break
  665. case NodeTypes.VNODE_CALL:
  666. genVNodeCall(node, context)
  667. break
  668. case NodeTypes.JS_CALL_EXPRESSION:
  669. genCallExpression(node, context)
  670. break
  671. case NodeTypes.JS_OBJECT_EXPRESSION:
  672. genObjectExpression(node, context)
  673. break
  674. case NodeTypes.JS_ARRAY_EXPRESSION:
  675. genArrayExpression(node, context)
  676. break
  677. case NodeTypes.JS_FUNCTION_EXPRESSION:
  678. genFunctionExpression(node, context)
  679. break
  680. case NodeTypes.JS_CONDITIONAL_EXPRESSION:
  681. genConditionalExpression(node, context)
  682. break
  683. case NodeTypes.JS_CACHE_EXPRESSION:
  684. genCacheExpression(node, context)
  685. break
  686. case NodeTypes.JS_BLOCK_STATEMENT:
  687. genNodeList(node.body, context, true, false)
  688. break
  689. // SSR only types
  690. case NodeTypes.JS_TEMPLATE_LITERAL:
  691. !__BROWSER__ && genTemplateLiteral(node, context)
  692. break
  693. case NodeTypes.JS_IF_STATEMENT:
  694. !__BROWSER__ && genIfStatement(node, context)
  695. break
  696. case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
  697. !__BROWSER__ && genAssignmentExpression(node, context)
  698. break
  699. case NodeTypes.JS_SEQUENCE_EXPRESSION:
  700. !__BROWSER__ && genSequenceExpression(node, context)
  701. break
  702. case NodeTypes.JS_RETURN_STATEMENT:
  703. !__BROWSER__ && genReturnStatement(node, context)
  704. break
  705. /* istanbul ignore next */
  706. case NodeTypes.IF_BRANCH:
  707. // noop
  708. break
  709. default:
  710. if (__DEV__) {
  711. assert(false, `unhandled codegen node type: ${(node as any).type}`)
  712. // make sure we exhaust all possible types
  713. const exhaustiveCheck: never = node
  714. return exhaustiveCheck
  715. }
  716. }
  717. }
  718. function genText(
  719. node: TextNode | SimpleExpressionNode,
  720. context: CodegenContext,
  721. ) {
  722. context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
  723. }
  724. function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
  725. const { content, isStatic } = node
  726. context.push(
  727. isStatic ? JSON.stringify(content) : content,
  728. NewlineType.Unknown,
  729. node,
  730. )
  731. }
  732. function genInterpolation(node: InterpolationNode, context: CodegenContext) {
  733. const { push, helper, pure } = context
  734. if (pure) push(PURE_ANNOTATION)
  735. push(`${helper(TO_DISPLAY_STRING)}(`)
  736. genNode(node.content, context)
  737. push(`)`)
  738. }
  739. function genCompoundExpression(
  740. node: CompoundExpressionNode,
  741. context: CodegenContext,
  742. ) {
  743. for (let i = 0; i < node.children!.length; i++) {
  744. const child = node.children![i]
  745. if (isString(child)) {
  746. context.push(child, NewlineType.Unknown)
  747. } else {
  748. genNode(child, context)
  749. }
  750. }
  751. }
  752. function genExpressionAsPropertyKey(
  753. node: ExpressionNode,
  754. context: CodegenContext,
  755. ) {
  756. const { push } = context
  757. if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
  758. push(`[`)
  759. genCompoundExpression(node, context)
  760. push(`]`)
  761. } else if (node.isStatic) {
  762. // only quote keys if necessary
  763. const text = isSimpleIdentifier(node.content)
  764. ? node.content
  765. : JSON.stringify(node.content)
  766. push(text, NewlineType.None, node)
  767. } else {
  768. push(`[${node.content}]`, NewlineType.Unknown, node)
  769. }
  770. }
  771. function genComment(node: CommentNode, context: CodegenContext) {
  772. const { push, helper, pure } = context
  773. if (pure) {
  774. push(PURE_ANNOTATION)
  775. }
  776. push(
  777. `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
  778. NewlineType.Unknown,
  779. node,
  780. )
  781. }
  782. function genVNodeCall(node: VNodeCall, context: CodegenContext) {
  783. const { push, helper, pure } = context
  784. const {
  785. tag,
  786. props,
  787. children,
  788. patchFlag,
  789. dynamicProps,
  790. directives,
  791. isBlock,
  792. disableTracking,
  793. isComponent,
  794. } = node
  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, patchFlag, 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. push(`_cache[${node.index}] || (`)
  954. if (node.isVNode) {
  955. indent()
  956. push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
  957. newline()
  958. }
  959. push(`_cache[${node.index}] = `)
  960. genNode(node.value, context)
  961. if (node.isVNode) {
  962. push(`,`)
  963. newline()
  964. push(`${helper(SET_BLOCK_TRACKING)}(1),`)
  965. newline()
  966. push(`_cache[${node.index}]`)
  967. deindent()
  968. }
  969. push(`)`)
  970. }
  971. function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
  972. const { push, indent, deindent } = context
  973. push('`')
  974. const l = node.elements.length
  975. const multilines = l > 3
  976. for (let i = 0; i < l; i++) {
  977. const e = node.elements[i]
  978. if (isString(e)) {
  979. push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown)
  980. } else {
  981. push('${')
  982. if (multilines) indent()
  983. genNode(e, context)
  984. if (multilines) deindent()
  985. push('}')
  986. }
  987. }
  988. push('`')
  989. }
  990. function genIfStatement(node: IfStatement, context: CodegenContext) {
  991. const { push, indent, deindent } = context
  992. const { test, consequent, alternate } = node
  993. push(`if (`)
  994. genNode(test, context)
  995. push(`) {`)
  996. indent()
  997. genNode(consequent, context)
  998. deindent()
  999. push(`}`)
  1000. if (alternate) {
  1001. push(` else `)
  1002. if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
  1003. genIfStatement(alternate, context)
  1004. } else {
  1005. push(`{`)
  1006. indent()
  1007. genNode(alternate, context)
  1008. deindent()
  1009. push(`}`)
  1010. }
  1011. }
  1012. }
  1013. function genAssignmentExpression(
  1014. node: AssignmentExpression,
  1015. context: CodegenContext,
  1016. ) {
  1017. genNode(node.left, context)
  1018. context.push(` = `)
  1019. genNode(node.right, context)
  1020. }
  1021. function genSequenceExpression(
  1022. node: SequenceExpression,
  1023. context: CodegenContext,
  1024. ) {
  1025. context.push(`(`)
  1026. genNodeList(node.expressions, context)
  1027. context.push(`)`)
  1028. }
  1029. function genReturnStatement(
  1030. { returns }: ReturnStatement,
  1031. context: CodegenContext,
  1032. ) {
  1033. context.push(`return `)
  1034. if (isArray(returns)) {
  1035. genNodeListAsArray(returns, context)
  1036. } else {
  1037. genNode(returns, context)
  1038. }
  1039. }