generate.ts 4.2 KB

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