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

205 lines
5.2 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,
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 = ''
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
if (vaporHelpers.size)
2023-12-01 08:26:01 +08:00
// TODO: extract
preamble =
2023-12-01 08:26:01 +08:00
`import { ${[...vaporHelpers]
.map((h) => `${h} as _${h}`)
.join(', ')} } from 'vue/vapor'\n` + preamble
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-12-01 08:26:01 +08:00
const functionName = 'render'
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
}
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
}
// TODO: other types (not only string)
function genArrayExpression(elements: string[]) {
return `[${elements.map((it) => `"${it}"`).join(', ')}]`
}