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

329 lines
7.2 KiB
TypeScript
Raw Normal View History

import type {
NodeTypes,
2023-11-17 03:01:19 +08:00
RootNode,
2023-11-23 23:42:08 +08:00
Node,
2023-11-17 03:01:19 +08:00
TemplateChildNode,
ElementNode,
AttributeNode,
2023-11-20 14:16:36 +08:00
InterpolationNode,
2023-11-23 23:42:08 +08:00
TransformOptions,
DirectiveNode,
2023-11-17 03:01:19 +08:00
} from '@vue/compiler-dom'
import {
type DynamicChildren,
type EffectNode,
type OprationNode,
type RootIRNode,
IRNodeTypes,
} from './ir'
2023-11-23 23:42:08 +08:00
export interface TransformContext<T extends Node = Node> {
node: T
parent: TransformContext | null
root: TransformContext<RootNode>
index: number
options: TransformOptions
// ir: RootIRNode
2023-11-23 23:42:08 +08:00
template: string
children: DynamicChildren
store: boolean
ghost: boolean
getElementId(): number
registerTemplate(): number
registerEffect(expr: string, effectNode: EffectNode): void
registerOpration(...oprations: OprationNode[]): void
helper(name: string): string
2023-11-23 23:42:08 +08:00
}
function createRootContext(
ir: RootIRNode,
node: RootNode,
options: TransformOptions,
): TransformContext<RootNode> {
let i = 0
const { effect, opration, helpers, vaporHelpers } = ir
2023-11-23 23:42:08 +08:00
const ctx: TransformContext<RootNode> = {
node,
parent: null,
index: 0,
root: undefined as any, // set later
options,
children: {},
store: false,
ghost: false,
getElementId: () => i++,
registerEffect(expr, effectNode) {
if (!effect[expr]) effect[expr] = []
effect[expr].push(effectNode)
2023-11-23 23:42:08 +08:00
},
template: '',
registerTemplate() {
if (!ctx.template) return -1
const idx = ir.template.findIndex((t) => t.template === ctx.template)
if (idx !== -1) return idx
ir.template.push({
type: IRNodeTypes.TEMPLATE_GENERATOR,
template: ctx.template,
loc: node.loc,
})
return ir.template.length - 1
},
registerOpration(...node) {
opration.push(...node)
},
// TODO not used yet
helper(name, vapor = true) {
;(vapor ? vaporHelpers : helpers).add(name)
return name
},
2023-11-23 23:42:08 +08:00
}
ctx.root = ctx
return ctx
}
function createContext<T extends TemplateChildNode>(
node: T,
parent: TransformContext,
index: number,
): TransformContext<T> {
let id: number | undefined
const getElementId = () => {
if (id !== undefined) return id
return (id = parent.root.getElementId())
}
const children = {}
const ctx: TransformContext<T> = {
...parent,
node,
parent,
index,
get template() {
return parent.template
},
set template(t) {
parent.template = t
},
getElementId,
children,
store: false,
}
return ctx
}
2023-11-17 03:01:19 +08:00
// AST -> IR
export function transform(
root: RootNode,
2023-11-23 23:42:08 +08:00
options: TransformOptions = {},
2023-11-17 03:01:19 +08:00
): RootIRNode {
2023-11-23 23:42:08 +08:00
const ir: RootIRNode = {
2023-11-17 03:01:19 +08:00
type: IRNodeTypes.ROOT,
loc: root.loc,
2023-11-23 23:42:08 +08:00
template: [],
children: {},
effect: Object.create(null),
opration: [],
helpers: new Set([]),
vaporHelpers: new Set([]),
2023-11-17 03:01:19 +08:00
}
2023-11-23 23:42:08 +08:00
const ctx = createRootContext(ir, root, options)
transformChildren(ctx, true)
ctx.registerTemplate()
ir.children = ctx.children
return ir
2023-11-17 03:01:19 +08:00
}
2023-11-23 23:42:08 +08:00
function transformChildren(
ctx: TransformContext<RootNode | ElementNode>,
root?: boolean,
) {
const {
node: { children },
} = ctx
let index = 0
children.forEach((child, i) => walkNode(child, i))
function walkNode(node: TemplateChildNode, i: number) {
const child = createContext(node, ctx, index)
const isFirst = i === 0
const isLast = i === children.length - 1
2023-11-17 03:01:19 +08:00
switch (node.type) {
case 1 satisfies NodeTypes.ELEMENT: {
2023-11-23 23:42:08 +08:00
transformElement(child as TransformContext<ElementNode>)
2023-11-17 03:01:19 +08:00
break
}
2023-11-23 23:42:08 +08:00
case 2 satisfies NodeTypes.TEXT: {
ctx.template += node.content
2023-11-17 03:01:19 +08:00
break
2023-11-23 23:42:08 +08:00
}
case 3 satisfies NodeTypes.COMMENT: {
ctx.template += `<!--${node.content}-->`
2023-11-17 17:35:49 +08:00
break
2023-11-23 23:42:08 +08:00
}
case 5 satisfies NodeTypes.INTERPOLATION: {
transformInterpolation(
child as TransformContext<InterpolationNode>,
isFirst,
isLast,
)
2023-11-17 03:01:19 +08:00
break
2023-11-23 23:42:08 +08:00
}
default: {
ctx.template += `[type: ${node.type}]`
}
2023-11-17 03:01:19 +08:00
}
2023-11-23 23:42:08 +08:00
if (Object.keys(child.children).length > 0 || child.store)
ctx.children[index] = {
id: child.store ? child.getElementId() : null,
store: child.store,
children: child.children,
}
if (!child.ghost) index++
2023-11-17 03:01:19 +08:00
}
}
2023-11-23 23:42:08 +08:00
function transformElement(ctx: TransformContext<ElementNode>) {
const { node } = ctx
const { tag, props, children } = node
ctx.template += `<${tag}`
props.forEach((prop) => transformProp(prop, ctx))
ctx.template += node.isSelfClosing ? '/>' : `>`
if (children.length > 0) {
transformChildren(ctx)
}
if (!node.isSelfClosing) ctx.template += `</${tag}>`
}
function transformInterpolation(
ctx: TransformContext<InterpolationNode>,
isFirst: boolean,
isLast: boolean,
) {
const { node } = ctx
2023-11-17 03:01:19 +08:00
if (node.content.type === (4 satisfies NodeTypes.SIMPLE_EXPRESSION)) {
2023-11-23 23:42:08 +08:00
const expr = processExpression(ctx, node.content.content)
const parent = ctx.parent!
const parentId = parent.getElementId()
parent.store = true
if (isFirst && isLast) {
ctx.registerEffect(expr, {
type: IRNodeTypes.SET_TEXT,
loc: node.loc,
element: parentId,
})
} else {
let id: number
let anchor: number | 'first' | 'last'
if (!isFirst && !isLast) {
id = ctx.root.getElementId()
anchor = ctx.getElementId()
ctx.template += '<!>'
ctx.store = true
} else {
id = ctx.getElementId()
ctx.ghost = true
anchor = isFirst ? 'first' : 'last'
}
ctx.registerOpration(
2023-11-23 23:42:08 +08:00
{
type: IRNodeTypes.TEXT_NODE,
loc: node.loc,
id,
content: expr,
},
{
type: IRNodeTypes.INSERT_NODE,
loc: node.loc,
element: id,
parent: parentId,
anchor,
},
)
ctx.registerEffect(expr, {
type: IRNodeTypes.SET_TEXT,
loc: node.loc,
element: id,
})
}
} else {
// TODO
2023-11-17 03:01:19 +08:00
}
2023-11-23 23:42:08 +08:00
// TODO
2023-11-17 03:01:19 +08:00
}
2023-11-23 23:42:08 +08:00
function transformProp(
node: DirectiveNode | AttributeNode,
ctx: TransformContext<ElementNode>,
): void {
const { name } = node
2023-11-17 03:01:19 +08:00
2023-11-23 23:42:08 +08:00
if (node.type === (6 satisfies NodeTypes.ATTRIBUTE)) {
if (node.value) {
ctx.template += ` ${name}="${node.value.content}"`
} else {
ctx.template += ` ${name}`
}
return
2023-11-17 03:01:19 +08:00
}
2023-11-23 23:42:08 +08:00
if (!node.exp) {
// TODO
return
} else if (node.exp.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) {
// TODO
return
} else if (
!node.arg ||
node.arg.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)
) {
// TODO
return
}
2023-11-17 03:01:19 +08:00
2023-11-23 23:42:08 +08:00
const expr = processExpression(ctx, node.exp.content)
ctx.store = true
if (name === 'bind') {
ctx.registerEffect(expr, {
type: IRNodeTypes.SET_PROP,
loc: node.loc,
element: ctx.getElementId(),
name: node.arg.content,
})
} else if (name === 'on') {
ctx.registerEffect(expr, {
type: IRNodeTypes.SET_EVENT,
loc: node.loc,
element: ctx.getElementId(),
name: node.arg.content,
})
}
2023-11-17 03:01:19 +08:00
}
// TODO: reference packages/compiler-core/src/transforms/transformExpression.ts
2023-11-23 23:42:08 +08:00
function processExpression(ctx: TransformContext, expr: string) {
if (ctx.options.bindingMetadata?.[expr] === 'setup-ref') {
expr += '.value'
}
return expr
2023-11-17 03:01:19 +08:00
}