codegen.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. import { CodegenOptions } from './options'
  2. import {
  3. RootNode,
  4. TemplateChildNode,
  5. TextNode,
  6. CommentNode,
  7. ExpressionNode,
  8. NodeTypes,
  9. JSChildNode,
  10. CallExpression,
  11. ArrayExpression,
  12. ObjectExpression,
  13. Position,
  14. InterpolationNode,
  15. CompoundExpressionNode,
  16. SimpleExpressionNode,
  17. FunctionExpression,
  18. ConditionalExpression,
  19. CacheExpression,
  20. locStub,
  21. SSRCodegenNode,
  22. TemplateLiteral,
  23. IfStatement,
  24. AssignmentExpression,
  25. ReturnStatement,
  26. VNodeCall,
  27. SequenceExpression
  28. } from './ast'
  29. import { SourceMapGenerator, RawSourceMap } from 'source-map'
  30. import {
  31. advancePositionWithMutation,
  32. assert,
  33. getVNodeBlockHelper,
  34. getVNodeHelper,
  35. isSimpleIdentifier,
  36. toValidAssetId
  37. } from './utils'
  38. import { isString, isArray, isSymbol } from '@vue/shared'
  39. import {
  40. helperNameMap,
  41. TO_DISPLAY_STRING,
  42. CREATE_VNODE,
  43. RESOLVE_COMPONENT,
  44. RESOLVE_DIRECTIVE,
  45. SET_BLOCK_TRACKING,
  46. CREATE_COMMENT,
  47. CREATE_TEXT,
  48. PUSH_SCOPE_ID,
  49. POP_SCOPE_ID,
  50. WITH_DIRECTIVES,
  51. CREATE_ELEMENT_VNODE,
  52. OPEN_BLOCK,
  53. CREATE_STATIC,
  54. WITH_CTX,
  55. RESOLVE_FILTER
  56. } from './runtimeHelpers'
  57. import { ImportItem } from './transform'
  58. const PURE_ANNOTATION = `/*#__PURE__*/`
  59. const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
  60. type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
  61. export interface CodegenResult {
  62. code: string
  63. preamble: string
  64. ast: RootNode
  65. map?: RawSourceMap
  66. }
  67. export interface CodegenContext
  68. extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
  69. source: string
  70. code: string
  71. line: number
  72. column: number
  73. offset: number
  74. indentLevel: number
  75. pure: boolean
  76. map?: SourceMapGenerator
  77. helper(key: symbol): string
  78. push(code: string, node?: CodegenNode): void
  79. indent(): void
  80. deindent(withoutNewLine?: boolean): void
  81. newline(): void
  82. }
  83. function createCodegenContext(
  84. ast: RootNode,
  85. {
  86. mode = 'function',
  87. prefixIdentifiers = mode === 'module',
  88. sourceMap = false,
  89. filename = `template.vue.html`,
  90. scopeId = null,
  91. optimizeImports = false,
  92. runtimeGlobalName = `Vue`,
  93. runtimeModuleName = `vue`,
  94. ssrRuntimeModuleName = 'vue/server-renderer',
  95. ssr = false,
  96. isTS = false,
  97. inSSR = false
  98. }: CodegenOptions
  99. ): CodegenContext {
  100. const context: CodegenContext = {
  101. mode,
  102. prefixIdentifiers,
  103. sourceMap,
  104. filename,
  105. scopeId,
  106. optimizeImports,
  107. runtimeGlobalName,
  108. runtimeModuleName,
  109. ssrRuntimeModuleName,
  110. ssr,
  111. isTS,
  112. inSSR,
  113. source: ast.loc.source,
  114. code: ``,
  115. column: 1,
  116. line: 1,
  117. offset: 0,
  118. indentLevel: 0,
  119. pure: false,
  120. map: undefined,
  121. helper(key) {
  122. return `_${helperNameMap[key]}`
  123. },
  124. push(code, node) {
  125. context.code += code
  126. if (!__BROWSER__ && context.map) {
  127. if (node) {
  128. let name
  129. if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
  130. const content = node.content.replace(/^_ctx\./, '')
  131. if (content !== node.content && isSimpleIdentifier(content)) {
  132. name = content
  133. }
  134. }
  135. addMapping(node.loc.start, name)
  136. }
  137. advancePositionWithMutation(context, code)
  138. if (node && node.loc !== locStub) {
  139. addMapping(node.loc.end)
  140. }
  141. }
  142. },
  143. indent() {
  144. newline(++context.indentLevel)
  145. },
  146. deindent(withoutNewLine = false) {
  147. if (withoutNewLine) {
  148. --context.indentLevel
  149. } else {
  150. newline(--context.indentLevel)
  151. }
  152. },
  153. newline() {
  154. newline(context.indentLevel)
  155. }
  156. }
  157. function newline(n: number) {
  158. context.push('\n' + ` `.repeat(n))
  159. }
  160. function addMapping(loc: Position, name?: string) {
  161. context.map!.addMapping({
  162. name,
  163. source: context.filename,
  164. original: {
  165. line: loc.line,
  166. column: loc.column - 1 // source-map column is 0 based
  167. },
  168. generated: {
  169. line: context.line,
  170. column: context.column - 1
  171. }
  172. })
  173. }
  174. if (!__BROWSER__ && sourceMap) {
  175. // lazy require source-map implementation, only in non-browser builds
  176. context.map = new SourceMapGenerator()
  177. context.map!.setSourceContent(filename, context.source)
  178. }
  179. return context
  180. }
  181. export function generate(
  182. ast: RootNode,
  183. options: CodegenOptions & {
  184. onContextCreated?: (context: CodegenContext) => void
  185. } = {}
  186. ): CodegenResult {
  187. const context = createCodegenContext(ast, options)
  188. if (options.onContextCreated) options.onContextCreated(context)
  189. const {
  190. mode,
  191. push,
  192. prefixIdentifiers,
  193. indent,
  194. deindent,
  195. newline,
  196. scopeId,
  197. ssr
  198. } = context
  199. const hasHelpers = ast.helpers.length > 0
  200. const useWithBlock = !prefixIdentifiers && mode !== 'module'
  201. const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
  202. const isSetupInlined = !__BROWSER__ && !!options.inline
  203. // preambles
  204. // in setup() inline mode, the preamble is generated in a sub context
  205. // and returned separately.
  206. const preambleContext = isSetupInlined
  207. ? createCodegenContext(ast, options)
  208. : context
  209. if (!__BROWSER__ && mode === 'module') {
  210. genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
  211. } else {
  212. genFunctionPreamble(ast, preambleContext)
  213. }
  214. // enter render function
  215. const functionName = ssr ? `ssrRender` : `render`
  216. const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
  217. if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
  218. // binding optimization args
  219. args.push('$props', '$setup', '$data', '$options')
  220. }
  221. const signature =
  222. !__BROWSER__ && options.isTS
  223. ? args.map(arg => `${arg}: any`).join(',')
  224. : args.join(', ')
  225. if (isSetupInlined) {
  226. push(`(${signature}) => {`)
  227. } else {
  228. push(`function ${functionName}(${signature}) {`)
  229. }
  230. indent()
  231. if (useWithBlock) {
  232. push(`with (_ctx) {`)
  233. indent()
  234. // function mode const declarations should be inside with block
  235. // also they should be renamed to avoid collision with user properties
  236. if (hasHelpers) {
  237. push(`const { ${ast.helpers.map(aliasHelper).join(', ')} } = _Vue`)
  238. push(`\n`)
  239. newline()
  240. }
  241. }
  242. // generate asset resolution statements
  243. if (ast.components.length) {
  244. genAssets(ast.components, 'component', context)
  245. if (ast.directives.length || ast.temps > 0) {
  246. newline()
  247. }
  248. }
  249. if (ast.directives.length) {
  250. genAssets(ast.directives, 'directive', context)
  251. if (ast.temps > 0) {
  252. newline()
  253. }
  254. }
  255. if (__COMPAT__ && ast.filters && ast.filters.length) {
  256. newline()
  257. genAssets(ast.filters, 'filter', context)
  258. newline()
  259. }
  260. if (ast.temps > 0) {
  261. push(`let `)
  262. for (let i = 0; i < ast.temps; i++) {
  263. push(`${i > 0 ? `, ` : ``}_temp${i}`)
  264. }
  265. }
  266. if (ast.components.length || ast.directives.length || ast.temps) {
  267. push(`\n`)
  268. newline()
  269. }
  270. // generate the VNode tree expression
  271. if (!ssr) {
  272. push(`return `)
  273. }
  274. if (ast.codegenNode) {
  275. genNode(ast.codegenNode, context)
  276. } else {
  277. push(`null`)
  278. }
  279. if (useWithBlock) {
  280. deindent()
  281. push(`}`)
  282. }
  283. deindent()
  284. push(`}`)
  285. return {
  286. ast,
  287. code: context.code,
  288. preamble: isSetupInlined ? preambleContext.code : ``,
  289. // SourceMapGenerator does have toJSON() method but it's not in the types
  290. map: context.map ? (context.map as any).toJSON() : undefined
  291. }
  292. }
  293. function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
  294. const {
  295. ssr,
  296. prefixIdentifiers,
  297. push,
  298. newline,
  299. runtimeModuleName,
  300. runtimeGlobalName,
  301. ssrRuntimeModuleName
  302. } = context
  303. const VueBinding =
  304. !__BROWSER__ && ssr
  305. ? `require(${JSON.stringify(runtimeModuleName)})`
  306. : runtimeGlobalName
  307. // Generate const declaration for helpers
  308. // In prefix mode, we place the const declaration at top so it's done
  309. // only once; But if we not prefixing, we place the declaration inside the
  310. // with block so it doesn't incur the `in` check cost for every helper access.
  311. if (ast.helpers.length > 0) {
  312. if (!__BROWSER__ && prefixIdentifiers) {
  313. push(
  314. `const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`
  315. )
  316. } else {
  317. // "with" mode.
  318. // save Vue in a separate variable to avoid collision
  319. push(`const _Vue = ${VueBinding}\n`)
  320. // in "with" mode, helpers are declared inside the with block to avoid
  321. // has check cost, but hoists are lifted out of the function - we need
  322. // to provide the helper here.
  323. if (ast.hoists.length) {
  324. const staticHelpers = [
  325. CREATE_VNODE,
  326. CREATE_ELEMENT_VNODE,
  327. CREATE_COMMENT,
  328. CREATE_TEXT,
  329. CREATE_STATIC
  330. ]
  331. .filter(helper => ast.helpers.includes(helper))
  332. .map(aliasHelper)
  333. .join(', ')
  334. push(`const { ${staticHelpers} } = _Vue\n`)
  335. }
  336. }
  337. }
  338. // generate variables for ssr helpers
  339. if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
  340. // ssr guarantees prefixIdentifier: true
  341. push(
  342. `const { ${ast.ssrHelpers
  343. .map(aliasHelper)
  344. .join(', ')} } = require("${ssrRuntimeModuleName}")\n`
  345. )
  346. }
  347. genHoists(ast.hoists, context)
  348. newline()
  349. push(`return `)
  350. }
  351. function genModulePreamble(
  352. ast: RootNode,
  353. context: CodegenContext,
  354. genScopeId: boolean,
  355. inline?: boolean
  356. ) {
  357. const {
  358. push,
  359. newline,
  360. optimizeImports,
  361. runtimeModuleName,
  362. ssrRuntimeModuleName
  363. } = context
  364. if (genScopeId && ast.hoists.length) {
  365. ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
  366. }
  367. // generate import statements for helpers
  368. if (ast.helpers.length) {
  369. if (optimizeImports) {
  370. // when bundled with webpack with code-split, calling an import binding
  371. // as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`,
  372. // incurring both payload size increase and potential perf overhead.
  373. // therefore we assign the imports to variables (which is a constant ~50b
  374. // cost per-component instead of scaling with template size)
  375. push(
  376. `import { ${ast.helpers
  377. .map(s => helperNameMap[s])
  378. .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
  379. )
  380. push(
  381. `\n// Binding optimization for webpack code-split\nconst ${ast.helpers
  382. .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
  383. .join(', ')}\n`
  384. )
  385. } else {
  386. push(
  387. `import { ${ast.helpers
  388. .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
  389. .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
  390. )
  391. }
  392. }
  393. if (ast.ssrHelpers && ast.ssrHelpers.length) {
  394. push(
  395. `import { ${ast.ssrHelpers
  396. .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
  397. .join(', ')} } from "${ssrRuntimeModuleName}"\n`
  398. )
  399. }
  400. if (ast.imports.length) {
  401. genImports(ast.imports, context)
  402. newline()
  403. }
  404. genHoists(ast.hoists, context)
  405. newline()
  406. if (!inline) {
  407. push(`export `)
  408. }
  409. }
  410. function genAssets(
  411. assets: string[],
  412. type: 'component' | 'directive' | 'filter',
  413. { helper, push, newline, isTS }: CodegenContext
  414. ) {
  415. const resolver = helper(
  416. __COMPAT__ && type === 'filter'
  417. ? RESOLVE_FILTER
  418. : type === 'component'
  419. ? RESOLVE_COMPONENT
  420. : RESOLVE_DIRECTIVE
  421. )
  422. for (let i = 0; i < assets.length; i++) {
  423. let id = assets[i]
  424. // potential component implicit self-reference inferred from SFC filename
  425. const maybeSelfReference = id.endsWith('__self')
  426. if (maybeSelfReference) {
  427. id = id.slice(0, -6)
  428. }
  429. push(
  430. `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
  431. maybeSelfReference ? `, true` : ``
  432. })${isTS ? `!` : ``}`
  433. )
  434. if (i < assets.length - 1) {
  435. newline()
  436. }
  437. }
  438. }
  439. function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
  440. if (!hoists.length) {
  441. return
  442. }
  443. context.pure = true
  444. const { push, newline, helper, scopeId, mode } = context
  445. const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
  446. newline()
  447. // generate inlined withScopeId helper
  448. if (genScopeId) {
  449. push(
  450. `const _withScopeId = n => (${helper(
  451. PUSH_SCOPE_ID
  452. )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`
  453. )
  454. newline()
  455. }
  456. for (let i = 0; i < hoists.length; i++) {
  457. const exp = hoists[i]
  458. if (exp) {
  459. const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
  460. push(
  461. `const _hoisted_${i + 1} = ${
  462. needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
  463. }`
  464. )
  465. genNode(exp, context)
  466. if (needScopeIdWrapper) {
  467. push(`)`)
  468. }
  469. newline()
  470. }
  471. }
  472. context.pure = false
  473. }
  474. function genImports(importsOptions: ImportItem[], context: CodegenContext) {
  475. if (!importsOptions.length) {
  476. return
  477. }
  478. importsOptions.forEach(imports => {
  479. context.push(`import `)
  480. genNode(imports.exp, context)
  481. context.push(` from '${imports.path}'`)
  482. context.newline()
  483. })
  484. }
  485. function isText(n: string | CodegenNode) {
  486. return (
  487. isString(n) ||
  488. n.type === NodeTypes.SIMPLE_EXPRESSION ||
  489. n.type === NodeTypes.TEXT ||
  490. n.type === NodeTypes.INTERPOLATION ||
  491. n.type === NodeTypes.COMPOUND_EXPRESSION
  492. )
  493. }
  494. function genNodeListAsArray(
  495. nodes: (string | CodegenNode | TemplateChildNode[])[],
  496. context: CodegenContext
  497. ) {
  498. const multilines =
  499. nodes.length > 3 ||
  500. ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
  501. context.push(`[`)
  502. multilines && context.indent()
  503. genNodeList(nodes, context, multilines)
  504. multilines && context.deindent()
  505. context.push(`]`)
  506. }
  507. function genNodeList(
  508. nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
  509. context: CodegenContext,
  510. multilines: boolean = false,
  511. comma: boolean = true
  512. ) {
  513. const { push, newline } = context
  514. for (let i = 0; i < nodes.length; i++) {
  515. const node = nodes[i]
  516. if (isString(node)) {
  517. push(node)
  518. } else if (isArray(node)) {
  519. genNodeListAsArray(node, context)
  520. } else {
  521. genNode(node, context)
  522. }
  523. if (i < nodes.length - 1) {
  524. if (multilines) {
  525. comma && push(',')
  526. newline()
  527. } else {
  528. comma && push(', ')
  529. }
  530. }
  531. }
  532. }
  533. function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
  534. if (isString(node)) {
  535. context.push(node)
  536. return
  537. }
  538. if (isSymbol(node)) {
  539. context.push(context.helper(node))
  540. return
  541. }
  542. switch (node.type) {
  543. case NodeTypes.ELEMENT:
  544. case NodeTypes.IF:
  545. case NodeTypes.FOR:
  546. __DEV__ &&
  547. assert(
  548. node.codegenNode != null,
  549. `Codegen node is missing for element/if/for node. ` +
  550. `Apply appropriate transforms first.`
  551. )
  552. genNode(node.codegenNode!, context)
  553. break
  554. case NodeTypes.TEXT:
  555. genText(node, context)
  556. break
  557. case NodeTypes.SIMPLE_EXPRESSION:
  558. genExpression(node, context)
  559. break
  560. case NodeTypes.INTERPOLATION:
  561. genInterpolation(node, context)
  562. break
  563. case NodeTypes.TEXT_CALL:
  564. genNode(node.codegenNode, context)
  565. break
  566. case NodeTypes.COMPOUND_EXPRESSION:
  567. genCompoundExpression(node, context)
  568. break
  569. case NodeTypes.COMMENT:
  570. genComment(node, context)
  571. break
  572. case NodeTypes.VNODE_CALL:
  573. genVNodeCall(node, context)
  574. break
  575. case NodeTypes.JS_CALL_EXPRESSION:
  576. genCallExpression(node, context)
  577. break
  578. case NodeTypes.JS_OBJECT_EXPRESSION:
  579. genObjectExpression(node, context)
  580. break
  581. case NodeTypes.JS_ARRAY_EXPRESSION:
  582. genArrayExpression(node, context)
  583. break
  584. case NodeTypes.JS_FUNCTION_EXPRESSION:
  585. genFunctionExpression(node, context)
  586. break
  587. case NodeTypes.JS_CONDITIONAL_EXPRESSION:
  588. genConditionalExpression(node, context)
  589. break
  590. case NodeTypes.JS_CACHE_EXPRESSION:
  591. genCacheExpression(node, context)
  592. break
  593. case NodeTypes.JS_BLOCK_STATEMENT:
  594. genNodeList(node.body, context, true, false)
  595. break
  596. // SSR only types
  597. case NodeTypes.JS_TEMPLATE_LITERAL:
  598. !__BROWSER__ && genTemplateLiteral(node, context)
  599. break
  600. case NodeTypes.JS_IF_STATEMENT:
  601. !__BROWSER__ && genIfStatement(node, context)
  602. break
  603. case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
  604. !__BROWSER__ && genAssignmentExpression(node, context)
  605. break
  606. case NodeTypes.JS_SEQUENCE_EXPRESSION:
  607. !__BROWSER__ && genSequenceExpression(node, context)
  608. break
  609. case NodeTypes.JS_RETURN_STATEMENT:
  610. !__BROWSER__ && genReturnStatement(node, context)
  611. break
  612. /* istanbul ignore next */
  613. case NodeTypes.IF_BRANCH:
  614. // noop
  615. break
  616. default:
  617. if (__DEV__) {
  618. assert(false, `unhandled codegen node type: ${(node as any).type}`)
  619. // make sure we exhaust all possible types
  620. const exhaustiveCheck: never = node
  621. return exhaustiveCheck
  622. }
  623. }
  624. }
  625. function genText(
  626. node: TextNode | SimpleExpressionNode,
  627. context: CodegenContext
  628. ) {
  629. context.push(JSON.stringify(node.content), node)
  630. }
  631. function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
  632. const { content, isStatic } = node
  633. context.push(isStatic ? JSON.stringify(content) : content, node)
  634. }
  635. function genInterpolation(node: InterpolationNode, context: CodegenContext) {
  636. const { push, helper, pure } = context
  637. if (pure) push(PURE_ANNOTATION)
  638. push(`${helper(TO_DISPLAY_STRING)}(`)
  639. genNode(node.content, context)
  640. push(`)`)
  641. }
  642. function genCompoundExpression(
  643. node: CompoundExpressionNode,
  644. context: CodegenContext
  645. ) {
  646. for (let i = 0; i < node.children!.length; i++) {
  647. const child = node.children![i]
  648. if (isString(child)) {
  649. context.push(child)
  650. } else {
  651. genNode(child, context)
  652. }
  653. }
  654. }
  655. function genExpressionAsPropertyKey(
  656. node: ExpressionNode,
  657. context: CodegenContext
  658. ) {
  659. const { push } = context
  660. if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
  661. push(`[`)
  662. genCompoundExpression(node, context)
  663. push(`]`)
  664. } else if (node.isStatic) {
  665. // only quote keys if necessary
  666. const text = isSimpleIdentifier(node.content)
  667. ? node.content
  668. : JSON.stringify(node.content)
  669. push(text, node)
  670. } else {
  671. push(`[${node.content}]`, node)
  672. }
  673. }
  674. function genComment(node: CommentNode, context: CodegenContext) {
  675. const { push, helper, pure } = context
  676. if (pure) {
  677. push(PURE_ANNOTATION)
  678. }
  679. push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
  680. }
  681. function genVNodeCall(node: VNodeCall, context: CodegenContext) {
  682. const { push, helper, pure } = context
  683. const {
  684. tag,
  685. props,
  686. children,
  687. patchFlag,
  688. dynamicProps,
  689. directives,
  690. isBlock,
  691. disableTracking,
  692. isComponent
  693. } = node
  694. if (directives) {
  695. push(helper(WITH_DIRECTIVES) + `(`)
  696. }
  697. if (isBlock) {
  698. push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
  699. }
  700. if (pure) {
  701. push(PURE_ANNOTATION)
  702. }
  703. const callHelper: symbol = isBlock
  704. ? getVNodeBlockHelper(context.inSSR, isComponent)
  705. : getVNodeHelper(context.inSSR, isComponent)
  706. push(helper(callHelper) + `(`, node)
  707. genNodeList(
  708. genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
  709. context
  710. )
  711. push(`)`)
  712. if (isBlock) {
  713. push(`)`)
  714. }
  715. if (directives) {
  716. push(`, `)
  717. genNode(directives, context)
  718. push(`)`)
  719. }
  720. }
  721. function genNullableArgs(args: any[]): CallExpression['arguments'] {
  722. let i = args.length
  723. while (i--) {
  724. if (args[i] != null) break
  725. }
  726. return args.slice(0, i + 1).map(arg => arg || `null`)
  727. }
  728. // JavaScript
  729. function genCallExpression(node: CallExpression, context: CodegenContext) {
  730. const { push, helper, pure } = context
  731. const callee = isString(node.callee) ? node.callee : helper(node.callee)
  732. if (pure) {
  733. push(PURE_ANNOTATION)
  734. }
  735. push(callee + `(`, node)
  736. genNodeList(node.arguments, context)
  737. push(`)`)
  738. }
  739. function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
  740. const { push, indent, deindent, newline } = context
  741. const { properties } = node
  742. if (!properties.length) {
  743. push(`{}`, node)
  744. return
  745. }
  746. const multilines =
  747. properties.length > 1 ||
  748. ((!__BROWSER__ || __DEV__) &&
  749. properties.some(p => p.value.type !== NodeTypes.SIMPLE_EXPRESSION))
  750. push(multilines ? `{` : `{ `)
  751. multilines && indent()
  752. for (let i = 0; i < properties.length; i++) {
  753. const { key, value } = properties[i]
  754. // key
  755. genExpressionAsPropertyKey(key, context)
  756. push(`: `)
  757. // value
  758. genNode(value, context)
  759. if (i < properties.length - 1) {
  760. // will only reach this if it's multilines
  761. push(`,`)
  762. newline()
  763. }
  764. }
  765. multilines && deindent()
  766. push(multilines ? `}` : ` }`)
  767. }
  768. function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
  769. genNodeListAsArray(node.elements as CodegenNode[], context)
  770. }
  771. function genFunctionExpression(
  772. node: FunctionExpression,
  773. context: CodegenContext
  774. ) {
  775. const { push, indent, deindent } = context
  776. const { params, returns, body, newline, isSlot } = node
  777. if (isSlot) {
  778. // wrap slot functions with owner context
  779. push(`_${helperNameMap[WITH_CTX]}(`)
  780. }
  781. push(`(`, node)
  782. if (isArray(params)) {
  783. genNodeList(params, context)
  784. } else if (params) {
  785. genNode(params, context)
  786. }
  787. push(`) => `)
  788. if (newline || body) {
  789. push(`{`)
  790. indent()
  791. }
  792. if (returns) {
  793. if (newline) {
  794. push(`return `)
  795. }
  796. if (isArray(returns)) {
  797. genNodeListAsArray(returns, context)
  798. } else {
  799. genNode(returns, context)
  800. }
  801. } else if (body) {
  802. genNode(body, context)
  803. }
  804. if (newline || body) {
  805. deindent()
  806. push(`}`)
  807. }
  808. if (isSlot) {
  809. if (__COMPAT__ && node.isNonScopedSlot) {
  810. push(`, undefined, true`)
  811. }
  812. push(`)`)
  813. }
  814. }
  815. function genConditionalExpression(
  816. node: ConditionalExpression,
  817. context: CodegenContext
  818. ) {
  819. const { test, consequent, alternate, newline: needNewline } = node
  820. const { push, indent, deindent, newline } = context
  821. if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
  822. const needsParens = !isSimpleIdentifier(test.content)
  823. needsParens && push(`(`)
  824. genExpression(test, context)
  825. needsParens && push(`)`)
  826. } else {
  827. push(`(`)
  828. genNode(test, context)
  829. push(`)`)
  830. }
  831. needNewline && indent()
  832. context.indentLevel++
  833. needNewline || push(` `)
  834. push(`? `)
  835. genNode(consequent, context)
  836. context.indentLevel--
  837. needNewline && newline()
  838. needNewline || push(` `)
  839. push(`: `)
  840. const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
  841. if (!isNested) {
  842. context.indentLevel++
  843. }
  844. genNode(alternate, context)
  845. if (!isNested) {
  846. context.indentLevel--
  847. }
  848. needNewline && deindent(true /* without newline */)
  849. }
  850. function genCacheExpression(node: CacheExpression, context: CodegenContext) {
  851. const { push, helper, indent, deindent, newline } = context
  852. push(`_cache[${node.index}] || (`)
  853. if (node.isVNode) {
  854. indent()
  855. push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
  856. newline()
  857. }
  858. push(`_cache[${node.index}] = `)
  859. genNode(node.value, context)
  860. if (node.isVNode) {
  861. push(`,`)
  862. newline()
  863. push(`${helper(SET_BLOCK_TRACKING)}(1),`)
  864. newline()
  865. push(`_cache[${node.index}]`)
  866. deindent()
  867. }
  868. push(`)`)
  869. }
  870. function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
  871. const { push, indent, deindent } = context
  872. push('`')
  873. const l = node.elements.length
  874. const multilines = l > 3
  875. for (let i = 0; i < l; i++) {
  876. const e = node.elements[i]
  877. if (isString(e)) {
  878. push(e.replace(/(`|\$|\\)/g, '\\$1'))
  879. } else {
  880. push('${')
  881. if (multilines) indent()
  882. genNode(e, context)
  883. if (multilines) deindent()
  884. push('}')
  885. }
  886. }
  887. push('`')
  888. }
  889. function genIfStatement(node: IfStatement, context: CodegenContext) {
  890. const { push, indent, deindent } = context
  891. const { test, consequent, alternate } = node
  892. push(`if (`)
  893. genNode(test, context)
  894. push(`) {`)
  895. indent()
  896. genNode(consequent, context)
  897. deindent()
  898. push(`}`)
  899. if (alternate) {
  900. push(` else `)
  901. if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
  902. genIfStatement(alternate, context)
  903. } else {
  904. push(`{`)
  905. indent()
  906. genNode(alternate, context)
  907. deindent()
  908. push(`}`)
  909. }
  910. }
  911. }
  912. function genAssignmentExpression(
  913. node: AssignmentExpression,
  914. context: CodegenContext
  915. ) {
  916. genNode(node.left, context)
  917. context.push(` = `)
  918. genNode(node.right, context)
  919. }
  920. function genSequenceExpression(
  921. node: SequenceExpression,
  922. context: CodegenContext
  923. ) {
  924. context.push(`(`)
  925. genNodeList(node.expressions, context)
  926. context.push(`)`)
  927. }
  928. function genReturnStatement(
  929. { returns }: ReturnStatement,
  930. context: CodegenContext
  931. ) {
  932. context.push(`return `)
  933. if (isArray(returns)) {
  934. genNodeListAsArray(returns, context)
  935. } else {
  936. genNode(returns, context)
  937. }
  938. }