generate.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import type { CodegenOptions, CodegenResult } from '@vue/compiler-dom'
  2. import {
  3. type DynamicChildren,
  4. type RootIRNode,
  5. IRNodeTypes,
  6. OperationNode,
  7. } from './ir'
  8. // remove when stable
  9. function checkNever(x: never): void {}
  10. // IR -> JS codegen
  11. export function generate(
  12. ir: RootIRNode,
  13. options: CodegenOptions = {},
  14. ): CodegenResult {
  15. let code = ''
  16. let preamble = ''
  17. const { helpers, vaporHelpers } = ir
  18. if (ir.template.length) {
  19. preamble += ir.template
  20. .map(
  21. (template, i) => `const t${i} = template(\`${template.template}\`)\n`,
  22. )
  23. .join('')
  24. vaporHelpers.add('template')
  25. }
  26. for (const [, { id, children }] of Object.entries(ir.children)) {
  27. code += `const n${id} = t0()\n`
  28. if (Object.keys(children).length) {
  29. code += `const {${genChildren(children)}} = children(n${id})\n`
  30. vaporHelpers.add('children')
  31. }
  32. for (const operation of ir.operation) {
  33. code += genOperation(operation)
  34. }
  35. for (const [_expr, operations] of Object.entries(ir.effect)) {
  36. // TODO don't use watchEffect from vue/core, implement `effect` function in runtime-vapor package
  37. let scope = `watchEffect(() => {\n`
  38. helpers.add('watchEffect')
  39. for (const operation of operations) {
  40. scope += genOperation(operation)
  41. }
  42. scope += '})\n'
  43. code += scope
  44. }
  45. // TODO multiple-template
  46. code += `return n${id}\n`
  47. }
  48. if (vaporHelpers.size)
  49. preamble =
  50. `import { ${[...vaporHelpers].join(', ')} } from 'vue/vapor'\n` + preamble
  51. if (helpers.size)
  52. preamble = `import { ${[...helpers].join(', ')} } from 'vue'\n` + preamble
  53. const functionName = options.ssr ? `ssrRender` : `render`
  54. const isSetupInlined = !!options.inline
  55. if (isSetupInlined) {
  56. code = `(() => {\n${code}\n})();`
  57. } else {
  58. code = `${preamble}export function ${functionName}() {\n${code}\n}`
  59. }
  60. return {
  61. code,
  62. ast: ir as any,
  63. preamble,
  64. }
  65. function genOperation(operation: OperationNode) {
  66. let code = ''
  67. switch (operation.type) {
  68. case IRNodeTypes.SET_PROP: {
  69. code = `setAttr(n${operation.element}, ${JSON.stringify(
  70. operation.name,
  71. )}, undefined, ${operation.value})\n`
  72. vaporHelpers.add('setAttr')
  73. break
  74. }
  75. case IRNodeTypes.SET_TEXT: {
  76. code = `setText(n${operation.element}, undefined, ${operation.value})\n`
  77. vaporHelpers.add('setText')
  78. break
  79. }
  80. case IRNodeTypes.SET_EVENT: {
  81. code = `on(n${operation.element}, ${JSON.stringify(operation.name)}, ${
  82. operation.value
  83. })\n`
  84. vaporHelpers.add('on')
  85. break
  86. }
  87. case IRNodeTypes.SET_HTML: {
  88. code = `setHtml(n${operation.element}, undefined, ${operation.value})\n`
  89. vaporHelpers.add('setHtml')
  90. break
  91. }
  92. case IRNodeTypes.TEXT_NODE: {
  93. // TODO handle by runtime: document.createTextNode
  94. code = `const n${operation.id} = document.createTextNode(${operation.value})\n`
  95. break
  96. }
  97. case IRNodeTypes.INSERT_NODE: {
  98. let anchor = ''
  99. if (typeof operation.anchor === 'number') {
  100. anchor = `, n${operation.anchor}`
  101. } else if (operation.anchor === 'first') {
  102. anchor = `, 0 /* InsertPosition.FIRST */`
  103. }
  104. code = `insert(n${operation.element}, n${operation.parent}${anchor})\n`
  105. vaporHelpers.add('insert')
  106. break
  107. }
  108. default:
  109. checkNever(operation)
  110. }
  111. return code
  112. }
  113. }
  114. function genChildren(children: DynamicChildren) {
  115. let str = ''
  116. for (const [index, child] of Object.entries(children)) {
  117. str += ` ${index}: [`
  118. if (child.store) {
  119. str += `n${child.id}`
  120. }
  121. if (Object.keys(child.children).length) {
  122. str += `, {${genChildren(child.children)}}`
  123. }
  124. str += '],'
  125. }
  126. return str
  127. }