generate.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import type {
  2. CodegenOptions as BaseCodegenOptions,
  3. BaseCodegenResult,
  4. } from '@vue/compiler-dom'
  5. import type { BlockIRNode, CoreHelper, RootIRNode, VaporHelper } from './ir'
  6. import { extend, remove } from '@vue/shared'
  7. import { genBlockContent } from './generators/block'
  8. import { genTemplates } from './generators/template'
  9. import {
  10. type CodeFragment,
  11. INDENT_END,
  12. INDENT_START,
  13. LF,
  14. NEWLINE,
  15. buildCodeFragment,
  16. codeFragmentToString,
  17. genCall,
  18. } from './generators/utils'
  19. import { setTemplateRefIdent } from './generators/templateRef'
  20. export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
  21. export class CodegenContext {
  22. options: Required<CodegenOptions>
  23. helpers: Set<string> = new Set<string>([])
  24. helper = (name: CoreHelper | VaporHelper) => {
  25. this.helpers.add(name)
  26. return `_${name}`
  27. }
  28. delegates: Set<string> = new Set<string>()
  29. identifiers: Record<string, string[]> = Object.create(null)
  30. seenInlineHandlerNames: Record<string, number> = Object.create(null)
  31. block: BlockIRNode
  32. withId<T>(fn: () => T, map: Record<string, string | null>): T {
  33. const { identifiers } = this
  34. const ids = Object.keys(map)
  35. for (const id of ids) {
  36. identifiers[id] ||= []
  37. identifiers[id].unshift(map[id] || id)
  38. }
  39. const ret = fn()
  40. ids.forEach(id => remove(identifiers[id], map[id] || id))
  41. return ret
  42. }
  43. enterBlock(block: BlockIRNode) {
  44. const parent = this.block
  45. this.block = block
  46. return (): BlockIRNode => (this.block = parent)
  47. }
  48. scopeLevel: number = 0
  49. enterScope(): [level: number, exit: () => number] {
  50. return [this.scopeLevel++, () => this.scopeLevel--] as const
  51. }
  52. constructor(
  53. public ir: RootIRNode,
  54. options: CodegenOptions,
  55. ) {
  56. const defaultOptions: Required<CodegenOptions> = {
  57. mode: 'module',
  58. prefixIdentifiers: true,
  59. sourceMap: false,
  60. filename: `template.vue.html`,
  61. scopeId: null,
  62. runtimeGlobalName: `Vue`,
  63. runtimeModuleName: `vue`,
  64. ssrRuntimeModuleName: 'vue/server-renderer',
  65. ssr: false,
  66. isTS: false,
  67. inSSR: false,
  68. inline: false,
  69. bindingMetadata: {},
  70. expressionPlugins: [],
  71. }
  72. this.options = extend(defaultOptions, options)
  73. this.block = ir.block
  74. }
  75. }
  76. export interface VaporCodegenResult extends BaseCodegenResult {
  77. ast: RootIRNode
  78. helpers: Set<string>
  79. }
  80. // IR -> JS codegen
  81. export function generate(
  82. ir: RootIRNode,
  83. options: CodegenOptions = {},
  84. ): VaporCodegenResult {
  85. const [frag, push] = buildCodeFragment()
  86. const context = new CodegenContext(ir, options)
  87. const { helpers } = context
  88. const { inline, bindingMetadata } = options
  89. const functionName = 'render'
  90. const args = ['_ctx']
  91. if (bindingMetadata && !inline) {
  92. // binding optimization args
  93. args.push('$props', '$emit', '$attrs', '$slots')
  94. }
  95. const signature = (options.isTS ? args.map(arg => `${arg}: any`) : args).join(
  96. ', ',
  97. )
  98. if (!inline) {
  99. push(NEWLINE, `export function ${functionName}(${signature}) {`)
  100. }
  101. push(INDENT_START)
  102. if (ir.hasTemplateRef) {
  103. push(
  104. NEWLINE,
  105. `const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`,
  106. )
  107. }
  108. push(...genBlockContent(ir.block, context, true))
  109. push(INDENT_END, NEWLINE)
  110. if (!inline) {
  111. push('}')
  112. }
  113. const delegates = genDelegates(context)
  114. const templates = genTemplates(ir.template, ir.rootTemplateIndex, context)
  115. const imports = genHelperImports(context)
  116. const preamble = imports + templates + delegates
  117. const newlineCount = [...preamble].filter(c => c === '\n').length
  118. if (newlineCount && !inline) {
  119. frag.unshift(...new Array<CodeFragment>(newlineCount).fill(LF))
  120. }
  121. let [code, map] = codeFragmentToString(frag, context)
  122. if (!inline) {
  123. code = preamble + code
  124. }
  125. return {
  126. code,
  127. ast: ir,
  128. preamble,
  129. map: map && map.toJSON(),
  130. helpers,
  131. }
  132. }
  133. function genDelegates({ delegates, helper }: CodegenContext) {
  134. return delegates.size
  135. ? genCall(
  136. helper('delegateEvents'),
  137. ...Array.from(delegates).map(v => `"${v}"`),
  138. ).join('') + '\n'
  139. : ''
  140. }
  141. function genHelperImports({ helpers, helper, options }: CodegenContext) {
  142. let imports = ''
  143. if (helpers.size) {
  144. imports += `import { ${[...helpers]
  145. .map(h => `${h} as _${h}`)
  146. .join(', ')} } from '${options.runtimeModuleName}';\n`
  147. }
  148. return imports
  149. }