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,
|
2023-12-01 05:18:20 +08:00
|
|
|
VaporHelper,
|
2023-11-24 15:25:34 +08:00
|
|
|
} 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-12-01 05:18:20 +08:00
|
|
|
export interface CodegenContext {
|
|
|
|
options: CodegenOptions
|
|
|
|
helpers: Set<string>
|
|
|
|
vaporHelpers: Set<string>
|
|
|
|
helper(name: string): string
|
|
|
|
vaporHelper(name: string): string
|
|
|
|
}
|
|
|
|
|
|
|
|
function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
|
|
|
|
const { helpers, vaporHelpers } = ir
|
|
|
|
return {
|
|
|
|
options,
|
|
|
|
helpers,
|
|
|
|
vaporHelpers,
|
|
|
|
helper(name: string) {
|
|
|
|
helpers.add(name)
|
2023-12-01 08:26:01 +08:00
|
|
|
return `_${name}`
|
2023-12-01 05:18:20 +08:00
|
|
|
},
|
|
|
|
vaporHelper(name: VaporHelper) {
|
|
|
|
vaporHelpers.add(name)
|
2023-12-01 08:26:01 +08:00
|
|
|
return `_${name}`
|
2023-12-01 05:18:20 +08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 {
|
2023-12-01 05:18:20 +08:00
|
|
|
const ctx = createCodegenContext(ir, options)
|
|
|
|
const { vaporHelper, helpers, vaporHelpers } = ctx
|
|
|
|
|
2023-11-17 03:01:19 +08:00
|
|
|
let code = ''
|
2023-11-24 11:07:31 +08:00
|
|
|
let preamble = ''
|
2023-11-17 03:01:19 +08:00
|
|
|
|
2023-11-26 03:53:47 +08:00
|
|
|
ir.template.forEach((template, i) => {
|
|
|
|
if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
|
2023-12-01 05:18:20 +08:00
|
|
|
preamble += `const t${i} = ${vaporHelper('template')}(${JSON.stringify(
|
2023-11-26 03:53:47 +08:00
|
|
|
template.template,
|
|
|
|
)})\n`
|
|
|
|
} else {
|
|
|
|
// fragment
|
2023-12-01 05:18:20 +08:00
|
|
|
code += `const t0 = ${vaporHelper('fragment')}()\n`
|
2023-11-26 03:53:47 +08:00
|
|
|
}
|
|
|
|
})
|
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) {
|
2023-12-01 05:18:20 +08:00
|
|
|
code += `const ${children} = ${vaporHelper('children')}(n${
|
|
|
|
ir.dynamic.id
|
|
|
|
})\n`
|
2023-11-26 03:08:35 +08:00
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
|
2023-11-24 20:29:05 +08:00
|
|
|
for (const operation of ir.operation) {
|
2023-12-01 05:18:20 +08:00
|
|
|
code += genOperation(operation, ctx)
|
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-12-01 05:18:20 +08:00
|
|
|
let scope = `${vaporHelper('effect')}(() => {\n`
|
2023-11-24 20:29:05 +08:00
|
|
|
for (const operation of operations) {
|
2023-12-01 05:18:20 +08:00
|
|
|
scope += genOperation(operation, ctx)
|
2023-11-24 20:29:05 +08:00
|
|
|
}
|
|
|
|
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
|
|
|
|
2023-11-24 11:07:31 +08:00
|
|
|
if (vaporHelpers.size)
|
2023-12-01 08:26:01 +08:00
|
|
|
// TODO: extract
|
2023-11-24 11:07:31 +08:00
|
|
|
preamble =
|
2023-12-01 08:26:01 +08:00
|
|
|
`import { ${[...vaporHelpers]
|
|
|
|
.map((h) => `${h} as _${h}`)
|
|
|
|
.join(', ')} } from 'vue/vapor'\n` + preamble
|
2023-11-24 11:07:31 +08:00
|
|
|
if (helpers.size)
|
2023-12-01 08:26:01 +08:00
|
|
|
preamble =
|
|
|
|
`import { ${[...helpers]
|
|
|
|
.map((h) => `${h} as _${h}`)
|
|
|
|
.join(', ')} } from 'vue'\n` + preamble
|
2023-11-24 11:07:31 +08:00
|
|
|
|
2023-12-01 08:26:01 +08:00
|
|
|
const functionName = '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-12-01 05:18:20 +08:00
|
|
|
code = `${preamble}export function ${functionName}(_ctx) {\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-12-01 05:18:20 +08:00
|
|
|
}
|
2023-11-24 15:25:34 +08:00
|
|
|
|
2023-12-01 05:18:20 +08:00
|
|
|
function genOperation(oper: OperationNode, { vaporHelper }: CodegenContext) {
|
|
|
|
// TODO: cache old value
|
|
|
|
switch (oper.type) {
|
|
|
|
case IRNodeTypes.SET_PROP: {
|
|
|
|
return `${vaporHelper('setAttr')}(n${oper.element}, ${JSON.stringify(
|
|
|
|
oper.name,
|
|
|
|
)}, undefined, ${oper.value})\n`
|
|
|
|
}
|
2023-11-24 15:25:34 +08:00
|
|
|
|
2023-12-01 05:18:20 +08:00
|
|
|
case IRNodeTypes.SET_TEXT: {
|
|
|
|
return `${vaporHelper('setText')}(n${oper.element}, undefined, ${
|
|
|
|
oper.value
|
|
|
|
})\n`
|
|
|
|
}
|
2023-11-24 15:25:34 +08:00
|
|
|
|
2023-12-01 05:18:20 +08:00
|
|
|
case IRNodeTypes.SET_EVENT: {
|
|
|
|
let value = oper.value
|
|
|
|
if (oper.modifiers.length) {
|
|
|
|
value = `${vaporHelper('withModifiers')}(${value}, ${genArrayExpression(
|
|
|
|
oper.modifiers,
|
|
|
|
)})`
|
2023-11-24 15:25:34 +08:00
|
|
|
}
|
2023-12-01 05:18:20 +08:00
|
|
|
return `${vaporHelper('on')}(n${oper.element}, ${JSON.stringify(
|
|
|
|
oper.name,
|
|
|
|
)}, ${value})\n`
|
|
|
|
}
|
2023-11-24 15:25:34 +08:00
|
|
|
|
2023-12-01 05:18:20 +08:00
|
|
|
case IRNodeTypes.SET_HTML: {
|
|
|
|
return `${vaporHelper('setHtml')}(n${oper.element}, undefined, ${
|
|
|
|
oper.value
|
|
|
|
})\n`
|
|
|
|
}
|
2023-11-24 15:25:34 +08:00
|
|
|
|
2023-12-01 05:18:20 +08:00
|
|
|
case IRNodeTypes.CREATE_TEXT_NODE: {
|
|
|
|
return `const n${oper.id} = ${vaporHelper('createTextNode')}(${
|
|
|
|
oper.value
|
|
|
|
})\n`
|
2023-11-24 15:25:34 +08:00
|
|
|
}
|
|
|
|
|
2023-12-01 05:18:20 +08:00
|
|
|
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}]`
|
|
|
|
return `${vaporHelper('insert')}(${element}, n${
|
|
|
|
oper.parent
|
|
|
|
}${`, n${oper.anchor}`})\n`
|
|
|
|
}
|
|
|
|
case IRNodeTypes.PREPEND_NODE: {
|
|
|
|
return `${vaporHelper('prepend')}(n${oper.parent}, ${oper.elements
|
|
|
|
.map((el) => `n${el}`)
|
|
|
|
.join(', ')})\n`
|
|
|
|
}
|
|
|
|
case IRNodeTypes.APPEND_NODE: {
|
|
|
|
return `${vaporHelper('append')}(n${oper.parent}, ${oper.elements
|
|
|
|
.map((el) => `n${el}`)
|
|
|
|
.join(', ')})\n`
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
checkNever(oper)
|
2023-11-24 15:25:34 +08:00
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
}
|
|
|
|
|
2023-11-24 11:07:31 +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
|
2023-11-27 06:22:10 +08:00
|
|
|
if (child.ghost && child.placeholder === null && childrenLength === 0) {
|
|
|
|
offset--
|
2023-11-27 05:16:21 +08:00
|
|
|
continue
|
2023-11-27 06:22:10 +08:00
|
|
|
}
|
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
|
|
|
}
|
2023-11-30 05:11:59 +08:00
|
|
|
|
|
|
|
// TODO: other types (not only string)
|
|
|
|
function genArrayExpression(elements: string[]) {
|
|
|
|
return `[${elements.map((it) => `"${it}"`).join(', ')}]`
|
|
|
|
}
|