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 = ''
|
2023-11-24 11:07:31 +08:00
|
|
|
let preamble = ''
|
2023-11-17 03:01:19 +08:00
|
|
|
|
2023-11-24 11:07:31 +08:00
|
|
|
const { helpers, vaporHelpers } = ir
|
|
|
|
if (ir.template.length) {
|
|
|
|
preamble += ir.template
|
|
|
|
.map(
|
|
|
|
(template, i) => `const t${i} = template(\`${template.template}\`)\n`,
|
|
|
|
)
|
|
|
|
.join('')
|
|
|
|
vaporHelpers.add('template')
|
|
|
|
}
|
2023-11-17 03:01:19 +08:00
|
|
|
|
2023-11-26 02:13:59 +08:00
|
|
|
{
|
2023-11-26 03:08:35 +08:00
|
|
|
code += `const n${ir.children.id} = t0()\n`
|
|
|
|
if (Object.keys(ir.children.children).length) {
|
|
|
|
code += `const {${genChildren(ir.children.children)}} = children(root)\n`
|
|
|
|
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)) {
|
|
|
|
// TODO don't use watchEffect from vue/core, implement `effect` function in runtime-vapor package
|
|
|
|
let scope = `watchEffect(() => {\n`
|
|
|
|
helpers.add('watchEffect')
|
|
|
|
for (const operation of operations) {
|
|
|
|
scope += genOperation(operation)
|
|
|
|
}
|
|
|
|
scope += '})\n'
|
|
|
|
code += scope
|
|
|
|
}
|
|
|
|
// TODO multiple-template
|
2023-11-26 03:08:35 +08:00
|
|
|
code += `return n${ir.children.id}\n`
|
2023-11-24 20:29:05 +08:00
|
|
|
}
|
2023-11-17 03:01:19 +08:00
|
|
|
|
2023-11-24 11:07:31 +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`
|
2023-11-24 11:07:31 +08:00
|
|
|
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
|
|
|
|
|
|
|
function genOperation(operation: OperationNode) {
|
|
|
|
let code = ''
|
|
|
|
|
2023-11-24 20:38:59 +08:00
|
|
|
// TODO: cache old value
|
2023-11-24 15:25:34 +08:00
|
|
|
switch (operation.type) {
|
|
|
|
case IRNodeTypes.SET_PROP: {
|
|
|
|
code = `setAttr(n${operation.element}, ${JSON.stringify(
|
|
|
|
operation.name,
|
|
|
|
)}, undefined, ${operation.value})\n`
|
|
|
|
vaporHelpers.add('setAttr')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
case IRNodeTypes.SET_TEXT: {
|
|
|
|
code = `setText(n${operation.element}, undefined, ${operation.value})\n`
|
|
|
|
vaporHelpers.add('setText')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
case IRNodeTypes.SET_EVENT: {
|
|
|
|
code = `on(n${operation.element}, ${JSON.stringify(operation.name)}, ${
|
|
|
|
operation.value
|
|
|
|
})\n`
|
|
|
|
vaporHelpers.add('on')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
case IRNodeTypes.SET_HTML: {
|
|
|
|
code = `setHtml(n${operation.element}, undefined, ${operation.value})\n`
|
|
|
|
vaporHelpers.add('setHtml')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
case IRNodeTypes.TEXT_NODE: {
|
|
|
|
// TODO handle by runtime: document.createTextNode
|
|
|
|
code = `const n${operation.id} = document.createTextNode(${operation.value})\n`
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
case IRNodeTypes.INSERT_NODE: {
|
|
|
|
let anchor = ''
|
|
|
|
if (typeof operation.anchor === 'number') {
|
|
|
|
anchor = `, n${operation.anchor}`
|
|
|
|
} else if (operation.anchor === 'first') {
|
|
|
|
anchor = `, 0 /* InsertPosition.FIRST */`
|
|
|
|
}
|
|
|
|
code = `insert(n${operation.element}, n${operation.parent}${anchor})\n`
|
|
|
|
vaporHelpers.add('insert')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
checkNever(operation)
|
|
|
|
}
|
|
|
|
|
|
|
|
return code
|
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
}
|
|
|
|
|
2023-11-24 11:07:31 +08:00
|
|
|
function genChildren(children: DynamicChildren) {
|
2023-11-23 23:42:08 +08:00
|
|
|
let str = ''
|
|
|
|
for (const [index, child] of Object.entries(children)) {
|
|
|
|
str += ` ${index}: [`
|
|
|
|
if (child.store) {
|
|
|
|
str += `n${child.id}`
|
|
|
|
}
|
|
|
|
if (Object.keys(child.children).length) {
|
2023-11-24 11:07:31 +08:00
|
|
|
str += `, {${genChildren(child.children)}}`
|
2023-11-23 23:42:08 +08:00
|
|
|
}
|
|
|
|
str += '],'
|
2023-11-17 03:01:19 +08:00
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
return str
|
2023-11-17 03:01:19 +08:00
|
|
|
}
|