codegen.ts 28 KB

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