codegen.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  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. push(
  540. `const _withScopeId = n => (${helper(
  541. PUSH_SCOPE_ID,
  542. )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
  543. )
  544. newline()
  545. }
  546. for (let i = 0; i < hoists.length; i++) {
  547. const exp = hoists[i]
  548. if (exp) {
  549. const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
  550. push(
  551. `const _hoisted_${i + 1} = ${
  552. needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
  553. }`,
  554. )
  555. genNode(exp, context)
  556. if (needScopeIdWrapper) {
  557. push(`)`)
  558. }
  559. newline()
  560. }
  561. }
  562. context.pure = false
  563. }
  564. function genImports(importsOptions: ImportItem[], context: CodegenContext) {
  565. if (!importsOptions.length) {
  566. return
  567. }
  568. importsOptions.forEach(imports => {
  569. context.push(`import `)
  570. genNode(imports.exp, context)
  571. context.push(` from '${imports.path}'`)
  572. context.newline()
  573. })
  574. }
  575. function isText(n: string | CodegenNode) {
  576. return (
  577. isString(n) ||
  578. n.type === NodeTypes.SIMPLE_EXPRESSION ||
  579. n.type === NodeTypes.TEXT ||
  580. n.type === NodeTypes.INTERPOLATION ||
  581. n.type === NodeTypes.COMPOUND_EXPRESSION
  582. )
  583. }
  584. function genNodeListAsArray(
  585. nodes: (string | CodegenNode | TemplateChildNode[])[],
  586. context: CodegenContext,
  587. ) {
  588. const multilines =
  589. nodes.length > 3 ||
  590. ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
  591. context.push(`[`)
  592. multilines && context.indent()
  593. genNodeList(nodes, context, multilines)
  594. multilines && context.deindent()
  595. context.push(`]`)
  596. }
  597. function genNodeList(
  598. nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
  599. context: CodegenContext,
  600. multilines: boolean = false,
  601. comma: boolean = true,
  602. ) {
  603. const { push, newline } = context
  604. for (let i = 0; i < nodes.length; i++) {
  605. const node = nodes[i]
  606. if (isString(node)) {
  607. push(node, NewlineType.Unknown)
  608. } else if (isArray(node)) {
  609. genNodeListAsArray(node, context)
  610. } else {
  611. genNode(node, context)
  612. }
  613. if (i < nodes.length - 1) {
  614. if (multilines) {
  615. comma && push(',')
  616. newline()
  617. } else {
  618. comma && push(', ')
  619. }
  620. }
  621. }
  622. }
  623. function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
  624. if (isString(node)) {
  625. context.push(node, NewlineType.Unknown)
  626. return
  627. }
  628. if (isSymbol(node)) {
  629. context.push(context.helper(node))
  630. return
  631. }
  632. switch (node.type) {
  633. case NodeTypes.ELEMENT:
  634. case NodeTypes.IF:
  635. case NodeTypes.FOR:
  636. __DEV__ &&
  637. assert(
  638. node.codegenNode != null,
  639. `Codegen node is missing for element/if/for node. ` +
  640. `Apply appropriate transforms first.`,
  641. )
  642. genNode(node.codegenNode!, context)
  643. break
  644. case NodeTypes.TEXT:
  645. genText(node, context)
  646. break
  647. case NodeTypes.SIMPLE_EXPRESSION:
  648. genExpression(node, context)
  649. break
  650. case NodeTypes.INTERPOLATION:
  651. genInterpolation(node, context)
  652. break
  653. case NodeTypes.TEXT_CALL:
  654. genNode(node.codegenNode, context)
  655. break
  656. case NodeTypes.COMPOUND_EXPRESSION:
  657. genCompoundExpression(node, context)
  658. break
  659. case NodeTypes.COMMENT:
  660. genComment(node, context)
  661. break
  662. case NodeTypes.VNODE_CALL:
  663. genVNodeCall(node, context)
  664. break
  665. case NodeTypes.JS_CALL_EXPRESSION:
  666. genCallExpression(node, context)
  667. break
  668. case NodeTypes.JS_OBJECT_EXPRESSION:
  669. genObjectExpression(node, context)
  670. break
  671. case NodeTypes.JS_ARRAY_EXPRESSION:
  672. genArrayExpression(node, context)
  673. break
  674. case NodeTypes.JS_FUNCTION_EXPRESSION:
  675. genFunctionExpression(node, context)
  676. break
  677. case NodeTypes.JS_CONDITIONAL_EXPRESSION:
  678. genConditionalExpression(node, context)
  679. break
  680. case NodeTypes.JS_CACHE_EXPRESSION:
  681. genCacheExpression(node, context)
  682. break
  683. case NodeTypes.JS_BLOCK_STATEMENT:
  684. genNodeList(node.body, context, true, false)
  685. break
  686. // SSR only types
  687. case NodeTypes.JS_TEMPLATE_LITERAL:
  688. !__BROWSER__ && genTemplateLiteral(node, context)
  689. break
  690. case NodeTypes.JS_IF_STATEMENT:
  691. !__BROWSER__ && genIfStatement(node, context)
  692. break
  693. case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
  694. !__BROWSER__ && genAssignmentExpression(node, context)
  695. break
  696. case NodeTypes.JS_SEQUENCE_EXPRESSION:
  697. !__BROWSER__ && genSequenceExpression(node, context)
  698. break
  699. case NodeTypes.JS_RETURN_STATEMENT:
  700. !__BROWSER__ && genReturnStatement(node, context)
  701. break
  702. /* istanbul ignore next */
  703. case NodeTypes.IF_BRANCH:
  704. // noop
  705. break
  706. default:
  707. if (__DEV__) {
  708. assert(false, `unhandled codegen node type: ${(node as any).type}`)
  709. // make sure we exhaust all possible types
  710. const exhaustiveCheck: never = node
  711. return exhaustiveCheck
  712. }
  713. }
  714. }
  715. function genText(
  716. node: TextNode | SimpleExpressionNode,
  717. context: CodegenContext,
  718. ) {
  719. context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
  720. }
  721. function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
  722. const { content, isStatic } = node
  723. context.push(
  724. isStatic ? JSON.stringify(content) : content,
  725. NewlineType.Unknown,
  726. node,
  727. )
  728. }
  729. function genInterpolation(node: InterpolationNode, context: CodegenContext) {
  730. const { push, helper, pure } = context
  731. if (pure) push(PURE_ANNOTATION)
  732. push(`${helper(TO_DISPLAY_STRING)}(`)
  733. genNode(node.content, context)
  734. push(`)`)
  735. }
  736. function genCompoundExpression(
  737. node: CompoundExpressionNode,
  738. context: CodegenContext,
  739. ) {
  740. for (let i = 0; i < node.children!.length; i++) {
  741. const child = node.children![i]
  742. if (isString(child)) {
  743. context.push(child, NewlineType.Unknown)
  744. } else {
  745. genNode(child, context)
  746. }
  747. }
  748. }
  749. function genExpressionAsPropertyKey(
  750. node: ExpressionNode,
  751. context: CodegenContext,
  752. ) {
  753. const { push } = context
  754. if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
  755. push(`[`)
  756. genCompoundExpression(node, context)
  757. push(`]`)
  758. } else if (node.isStatic) {
  759. // only quote keys if necessary
  760. const text = isSimpleIdentifier(node.content)
  761. ? node.content
  762. : JSON.stringify(node.content)
  763. push(text, NewlineType.None, node)
  764. } else {
  765. push(`[${node.content}]`, NewlineType.Unknown, node)
  766. }
  767. }
  768. function genComment(node: CommentNode, context: CodegenContext) {
  769. const { push, helper, pure } = context
  770. if (pure) {
  771. push(PURE_ANNOTATION)
  772. }
  773. push(
  774. `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
  775. NewlineType.Unknown,
  776. node,
  777. )
  778. }
  779. function genVNodeCall(node: VNodeCall, context: CodegenContext) {
  780. const { push, helper, pure } = context
  781. const {
  782. tag,
  783. props,
  784. children,
  785. patchFlag,
  786. dynamicProps,
  787. directives,
  788. isBlock,
  789. disableTracking,
  790. isComponent,
  791. } = node
  792. if (directives) {
  793. push(helper(WITH_DIRECTIVES) + `(`)
  794. }
  795. if (isBlock) {
  796. push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
  797. }
  798. if (pure) {
  799. push(PURE_ANNOTATION)
  800. }
  801. const callHelper: symbol = isBlock
  802. ? getVNodeBlockHelper(context.inSSR, isComponent)
  803. : getVNodeHelper(context.inSSR, isComponent)
  804. push(helper(callHelper) + `(`, NewlineType.None, node)
  805. genNodeList(
  806. genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
  807. context,
  808. )
  809. push(`)`)
  810. if (isBlock) {
  811. push(`)`)
  812. }
  813. if (directives) {
  814. push(`, `)
  815. genNode(directives, context)
  816. push(`)`)
  817. }
  818. }
  819. function genNullableArgs(args: any[]): CallExpression['arguments'] {
  820. let i = args.length
  821. while (i--) {
  822. if (args[i] != null) break
  823. }
  824. return args.slice(0, i + 1).map(arg => arg || `null`)
  825. }
  826. // JavaScript
  827. function genCallExpression(node: CallExpression, context: CodegenContext) {
  828. const { push, helper, pure } = context
  829. const callee = isString(node.callee) ? node.callee : helper(node.callee)
  830. if (pure) {
  831. push(PURE_ANNOTATION)
  832. }
  833. push(callee + `(`, NewlineType.None, node)
  834. genNodeList(node.arguments, context)
  835. push(`)`)
  836. }
  837. function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
  838. const { push, indent, deindent, newline } = context
  839. const { properties } = node
  840. if (!properties.length) {
  841. push(`{}`, NewlineType.None, node)
  842. return
  843. }
  844. const multilines =
  845. properties.length > 1 ||
  846. ((!__BROWSER__ || __DEV__) &&
  847. properties.some(p => p.value.type !== NodeTypes.SIMPLE_EXPRESSION))
  848. push(multilines ? `{` : `{ `)
  849. multilines && indent()
  850. for (let i = 0; i < properties.length; i++) {
  851. const { key, value } = properties[i]
  852. // key
  853. genExpressionAsPropertyKey(key, context)
  854. push(`: `)
  855. // value
  856. genNode(value, context)
  857. if (i < properties.length - 1) {
  858. // will only reach this if it's multilines
  859. push(`,`)
  860. newline()
  861. }
  862. }
  863. multilines && deindent()
  864. push(multilines ? `}` : ` }`)
  865. }
  866. function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
  867. genNodeListAsArray(node.elements as CodegenNode[], context)
  868. }
  869. function genFunctionExpression(
  870. node: FunctionExpression,
  871. context: CodegenContext,
  872. ) {
  873. const { push, indent, deindent } = context
  874. const { params, returns, body, newline, isSlot } = node
  875. if (isSlot) {
  876. // wrap slot functions with owner context
  877. push(`_${helperNameMap[WITH_CTX]}(`)
  878. }
  879. push(`(`, NewlineType.None, node)
  880. if (isArray(params)) {
  881. genNodeList(params, context)
  882. } else if (params) {
  883. genNode(params, context)
  884. }
  885. push(`) => `)
  886. if (newline || body) {
  887. push(`{`)
  888. indent()
  889. }
  890. if (returns) {
  891. if (newline) {
  892. push(`return `)
  893. }
  894. if (isArray(returns)) {
  895. genNodeListAsArray(returns, context)
  896. } else {
  897. genNode(returns, context)
  898. }
  899. } else if (body) {
  900. genNode(body, context)
  901. }
  902. if (newline || body) {
  903. deindent()
  904. push(`}`)
  905. }
  906. if (isSlot) {
  907. if (__COMPAT__ && node.isNonScopedSlot) {
  908. push(`, undefined, true`)
  909. }
  910. push(`)`)
  911. }
  912. }
  913. function genConditionalExpression(
  914. node: ConditionalExpression,
  915. context: CodegenContext,
  916. ) {
  917. const { test, consequent, alternate, newline: needNewline } = node
  918. const { push, indent, deindent, newline } = context
  919. if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
  920. const needsParens = !isSimpleIdentifier(test.content)
  921. needsParens && push(`(`)
  922. genExpression(test, context)
  923. needsParens && push(`)`)
  924. } else {
  925. push(`(`)
  926. genNode(test, context)
  927. push(`)`)
  928. }
  929. needNewline && indent()
  930. context.indentLevel++
  931. needNewline || push(` `)
  932. push(`? `)
  933. genNode(consequent, context)
  934. context.indentLevel--
  935. needNewline && newline()
  936. needNewline || push(` `)
  937. push(`: `)
  938. const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
  939. if (!isNested) {
  940. context.indentLevel++
  941. }
  942. genNode(alternate, context)
  943. if (!isNested) {
  944. context.indentLevel--
  945. }
  946. needNewline && deindent(true /* without newline */)
  947. }
  948. function genCacheExpression(node: CacheExpression, context: CodegenContext) {
  949. const { push, helper, indent, deindent, newline } = context
  950. push(`_cache[${node.index}] || (`)
  951. if (node.isVNode) {
  952. indent()
  953. push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
  954. newline()
  955. }
  956. push(`_cache[${node.index}] = `)
  957. genNode(node.value, context)
  958. if (node.isVNode) {
  959. push(`,`)
  960. newline()
  961. push(`${helper(SET_BLOCK_TRACKING)}(1),`)
  962. newline()
  963. push(`_cache[${node.index}]`)
  964. deindent()
  965. }
  966. push(`)`)
  967. }
  968. function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
  969. const { push, indent, deindent } = context
  970. push('`')
  971. const l = node.elements.length
  972. const multilines = l > 3
  973. for (let i = 0; i < l; i++) {
  974. const e = node.elements[i]
  975. if (isString(e)) {
  976. push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown)
  977. } else {
  978. push('${')
  979. if (multilines) indent()
  980. genNode(e, context)
  981. if (multilines) deindent()
  982. push('}')
  983. }
  984. }
  985. push('`')
  986. }
  987. function genIfStatement(node: IfStatement, context: CodegenContext) {
  988. const { push, indent, deindent } = context
  989. const { test, consequent, alternate } = node
  990. push(`if (`)
  991. genNode(test, context)
  992. push(`) {`)
  993. indent()
  994. genNode(consequent, context)
  995. deindent()
  996. push(`}`)
  997. if (alternate) {
  998. push(` else `)
  999. if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
  1000. genIfStatement(alternate, context)
  1001. } else {
  1002. push(`{`)
  1003. indent()
  1004. genNode(alternate, context)
  1005. deindent()
  1006. push(`}`)
  1007. }
  1008. }
  1009. }
  1010. function genAssignmentExpression(
  1011. node: AssignmentExpression,
  1012. context: CodegenContext,
  1013. ) {
  1014. genNode(node.left, context)
  1015. context.push(` = `)
  1016. genNode(node.right, context)
  1017. }
  1018. function genSequenceExpression(
  1019. node: SequenceExpression,
  1020. context: CodegenContext,
  1021. ) {
  1022. context.push(`(`)
  1023. genNodeList(node.expressions, context)
  1024. context.push(`)`)
  1025. }
  1026. function genReturnStatement(
  1027. { returns }: ReturnStatement,
  1028. context: CodegenContext,
  1029. ) {
  1030. context.push(`return `)
  1031. if (isArray(returns)) {
  1032. genNodeListAsArray(returns, context)
  1033. } else {
  1034. genNode(returns, context)
  1035. }
  1036. }