vue3-core/packages/compiler-vapor/src/generate.ts

175 lines
4.5 KiB
TypeScript
Raw Normal View History

2023-11-24 11:15:33 +08:00
import type { CodegenOptions, CodegenResult } from '@vue/compiler-dom'
2023-11-24 15:25:34 +08:00
import {
type DynamicChildren,
type RootIRNode,
IRNodeTypes,
OperationNode,
} from './ir'
2023-11-17 03:01:19 +08:00
2023-11-24 14:44:57 +08:00
// remove when stable
function checkNever(x: never): void {}
2023-11-17 03:01:19 +08:00
// IR -> JS codegen
export function generate(
2023-11-23 23:42:08 +08:00
ir: RootIRNode,
2023-11-24 11:15:33 +08:00
options: CodegenOptions = {},
2023-11-17 03:01:19 +08:00
): CodegenResult {
let code = ''
let preamble = ''
2023-11-17 03:01:19 +08:00
const { helpers, vaporHelpers } = ir
2023-11-26 03:53:47 +08:00
ir.template.forEach((template, i) => {
if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
preamble += `const t${i} = template(${JSON.stringify(
template.template,
)})\n`
vaporHelpers.add('template')
} else {
// fragment
code += `const t0 = fragment()\n`
vaporHelpers.add('fragment')
}
})
2023-11-17 03:01:19 +08:00
2023-11-26 02:13:59 +08:00
{
2023-11-27 05:16:21 +08:00
code += `const n${ir.dynamic.id} = t0()\n`
const children = genChildren(ir.dynamic.children)
if (children) {
code += `const ${children} = children(n${ir.dynamic.id})\n`
2023-11-26 03:08:35 +08:00
vaporHelpers.add('children')
}
2023-11-23 23:42:08 +08:00
2023-11-24 20:29:05 +08:00
for (const operation of ir.operation) {
code += genOperation(operation)
2023-11-23 23:42:08 +08:00
}
2023-11-24 20:29:05 +08:00
for (const [_expr, operations] of Object.entries(ir.effect)) {
2023-11-27 23:47:21 +08:00
let scope = `effect(() => {\n`
vaporHelpers.add('effect')
2023-11-24 20:29:05 +08:00
for (const operation of operations) {
scope += genOperation(operation)
}
scope += '})\n'
code += scope
}
// TODO multiple-template
2023-11-26 03:53:47 +08:00
// TODO return statement in IR
2023-11-27 05:16:21 +08:00
code += `return n${ir.dynamic.id}\n`
2023-11-24 20:29:05 +08:00
}
2023-11-17 03:01:19 +08:00
if (vaporHelpers.size)
preamble =
`import { ${[...vaporHelpers].join(', ')} } from 'vue/vapor'\n` + preamble
if (helpers.size)
preamble = `import { ${[...helpers].join(', ')} } from 'vue'\n` + preamble
2023-11-17 03:01:19 +08:00
const functionName = options.ssr ? `ssrRender` : `render`
const isSetupInlined = !!options.inline
2023-11-17 03:01:19 +08:00
if (isSetupInlined) {
code = `(() => {\n${code}\n})();`
} else {
2023-11-17 17:35:49 +08:00
code = `${preamble}export function ${functionName}() {\n${code}\n}`
2023-11-17 03:01:19 +08:00
}
return {
code,
2023-11-23 23:42:08 +08:00
ast: ir as any,
preamble,
}
2023-11-24 15:25:34 +08:00
2023-11-27 05:16:21 +08:00
function genOperation(oper: OperationNode) {
2023-11-24 15:25:34 +08:00
let code = ''
2023-11-24 20:38:59 +08:00
// TODO: cache old value
2023-11-27 05:16:21 +08:00
switch (oper.type) {
2023-11-24 15:25:34 +08:00
case IRNodeTypes.SET_PROP: {
2023-11-27 05:16:21 +08:00
code = `setAttr(n${oper.element}, ${JSON.stringify(
oper.name,
)}, undefined, ${oper.value})\n`
2023-11-24 15:25:34 +08:00
vaporHelpers.add('setAttr')
break
}
case IRNodeTypes.SET_TEXT: {
2023-11-27 05:16:21 +08:00
code = `setText(n${oper.element}, undefined, ${oper.value})\n`
2023-11-24 15:25:34 +08:00
vaporHelpers.add('setText')
break
}
case IRNodeTypes.SET_EVENT: {
2023-11-27 05:16:21 +08:00
code = `on(n${oper.element}, ${JSON.stringify(oper.name)}, ${
oper.value
2023-11-24 15:25:34 +08:00
})\n`
vaporHelpers.add('on')
break
}
case IRNodeTypes.SET_HTML: {
2023-11-27 05:16:21 +08:00
code = `setHtml(n${oper.element}, undefined, ${oper.value})\n`
2023-11-24 15:25:34 +08:00
vaporHelpers.add('setHtml')
break
}
case IRNodeTypes.CREATE_TEXT_NODE: {
2023-11-27 05:16:21 +08:00
code = `const n${oper.id} = createTextNode(${oper.value})\n`
vaporHelpers.add('createTextNode')
2023-11-24 15:25:34 +08:00
break
}
case IRNodeTypes.INSERT_NODE: {
const elements = ([] as number[]).concat(oper.element)
let element = elements.map((el) => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
code = `insert(${element}, n${oper.parent}${`, n${oper.anchor}`})\n`
2023-11-24 15:25:34 +08:00
vaporHelpers.add('insert')
break
}
case IRNodeTypes.PREPEND_NODE: {
code = `prepend(n${oper.parent}, ${oper.elements
.map((el) => `n${el}`)
.join(', ')})\n`
vaporHelpers.add('prepend')
break
}
2023-11-27 05:16:21 +08:00
case IRNodeTypes.APPEND_NODE: {
code = `append(n${oper.parent}, ${oper.elements
.map((el) => `n${el}`)
.join(', ')})\n`
vaporHelpers.add('append')
break
}
2023-11-24 15:25:34 +08:00
default:
2023-11-27 05:16:21 +08:00
checkNever(oper)
2023-11-24 15:25:34 +08:00
}
return code
}
2023-11-23 23:42:08 +08:00
}
function genChildren(children: DynamicChildren) {
2023-11-27 05:16:21 +08:00
let code = ''
// TODO
let offset = 0
2023-11-23 23:42:08 +08:00
for (const [index, child] of Object.entries(children)) {
2023-11-27 05:16:21 +08:00
const childrenLength = Object.keys(child.children).length
if (child.ghost && child.placeholder === null && childrenLength === 0) {
offset--
2023-11-27 05:16:21 +08:00
continue
}
2023-11-27 05:16:21 +08:00
code += ` ${Number(index) + offset}: [`
const id = child.ghost ? child.placeholder : child.id
if (id !== null) code += `n${id}`
const childrenString = childrenLength && genChildren(child.children)
if (childrenString) code += `, ${childrenString}`
code += '],'
2023-11-17 03:01:19 +08:00
}
2023-11-27 05:16:21 +08:00
if (!code) return ''
return `{${code}}`
2023-11-17 03:01:19 +08:00
}