|
|
@@ -11,10 +11,11 @@ import type {
|
|
|
ExpressionNode,
|
|
|
} from '@vue/compiler-dom'
|
|
|
import {
|
|
|
- type DynamicChildren,
|
|
|
type OperationNode,
|
|
|
type RootIRNode,
|
|
|
IRNodeTypes,
|
|
|
+ DynamicInfo,
|
|
|
+ InsertAnchor,
|
|
|
} from './ir'
|
|
|
import { isVoidTag } from '@vue/shared'
|
|
|
|
|
|
@@ -24,14 +25,13 @@ export interface TransformContext<T extends Node = Node> {
|
|
|
root: TransformContext<RootNode>
|
|
|
index: number
|
|
|
options: TransformOptions
|
|
|
+
|
|
|
template: string
|
|
|
- children: DynamicChildren
|
|
|
- store: boolean
|
|
|
- ghost: boolean
|
|
|
+ dynamic: DynamicInfo
|
|
|
+
|
|
|
once: boolean
|
|
|
- id: number | null
|
|
|
|
|
|
- getId(): number
|
|
|
+ reference(): number
|
|
|
incraseId(): number
|
|
|
registerTemplate(): number
|
|
|
registerEffect(expr: string, operation: OperationNode): void
|
|
|
@@ -53,18 +53,19 @@ function createRootContext(
|
|
|
index: 0,
|
|
|
root: undefined as any, // set later
|
|
|
options,
|
|
|
- children: {},
|
|
|
- store: false,
|
|
|
- ghost: false,
|
|
|
+ dynamic: ir.dynamic,
|
|
|
once: false,
|
|
|
|
|
|
- id: null,
|
|
|
incraseId: () => globalId++,
|
|
|
- getId() {
|
|
|
- if (this.id !== null) return this.id
|
|
|
- return (this.id = this.incraseId())
|
|
|
+ reference() {
|
|
|
+ if (this.dynamic.id !== null) return this.dynamic.id
|
|
|
+ this.dynamic.referenced = true
|
|
|
+ return (this.dynamic.id = this.incraseId())
|
|
|
},
|
|
|
registerEffect(expr, operation) {
|
|
|
+ if (this.once) {
|
|
|
+ return this.registerOpration(operation)
|
|
|
+ }
|
|
|
if (!effect[expr]) effect[expr] = []
|
|
|
effect[expr].push(operation)
|
|
|
},
|
|
|
@@ -97,6 +98,7 @@ function createRootContext(
|
|
|
},
|
|
|
}
|
|
|
ctx.root = ctx
|
|
|
+ ctx.reference()
|
|
|
return ctx
|
|
|
}
|
|
|
|
|
|
@@ -105,27 +107,19 @@ function createContext<T extends TemplateChildNode>(
|
|
|
parent: TransformContext,
|
|
|
index: number,
|
|
|
): TransformContext<T> {
|
|
|
- const children = {}
|
|
|
-
|
|
|
const ctx: TransformContext<T> = {
|
|
|
...parent,
|
|
|
- id: null,
|
|
|
node,
|
|
|
parent,
|
|
|
index,
|
|
|
- get template() {
|
|
|
- return parent.template
|
|
|
- },
|
|
|
- set template(t) {
|
|
|
- parent.template = t
|
|
|
- },
|
|
|
- children,
|
|
|
- store: false,
|
|
|
- registerEffect(expr, operation) {
|
|
|
- if (ctx.once) {
|
|
|
- return ctx.registerOpration(operation)
|
|
|
- }
|
|
|
- return parent.registerEffect(expr, operation)
|
|
|
+
|
|
|
+ template: '',
|
|
|
+ dynamic: {
|
|
|
+ id: null,
|
|
|
+ referenced: false,
|
|
|
+ ghost: false,
|
|
|
+ placeholder: null,
|
|
|
+ children: {},
|
|
|
},
|
|
|
}
|
|
|
return ctx
|
|
|
@@ -140,23 +134,22 @@ export function transform(
|
|
|
type: IRNodeTypes.ROOT,
|
|
|
loc: root.loc,
|
|
|
template: [],
|
|
|
- children: {} as any,
|
|
|
+ dynamic: {
|
|
|
+ id: null,
|
|
|
+ referenced: true,
|
|
|
+ ghost: true,
|
|
|
+ placeholder: null,
|
|
|
+ children: {},
|
|
|
+ },
|
|
|
effect: Object.create(null),
|
|
|
operation: [],
|
|
|
helpers: new Set([]),
|
|
|
vaporHelpers: new Set([]),
|
|
|
}
|
|
|
const ctx = createRootContext(ir, root, options)
|
|
|
- const rootId = ctx.getId()
|
|
|
|
|
|
// TODO: transform presets, see packages/compiler-core/src/transforms
|
|
|
transformChildren(ctx, true)
|
|
|
- ir.children = {
|
|
|
- id: rootId,
|
|
|
- store: true,
|
|
|
- ghost: false,
|
|
|
- children: ctx.children,
|
|
|
- }
|
|
|
if (ir.template.length === 0) {
|
|
|
ir.template.push({
|
|
|
type: IRNodeTypes.FRAGMENT_FACTORY,
|
|
|
@@ -174,15 +167,57 @@ function transformChildren(
|
|
|
const {
|
|
|
node: { children },
|
|
|
} = ctx
|
|
|
- let index = 0
|
|
|
+ const childrenTemplate: string[] = []
|
|
|
children.forEach((child, i) => walkNode(child, i))
|
|
|
|
|
|
+ const dynamicChildren = Object.values(ctx.dynamic.children)
|
|
|
+ const dynamicCount = dynamicChildren.reduce(
|
|
|
+ (prev, child) => prev + (child.ghost ? 1 : 0),
|
|
|
+ 0,
|
|
|
+ )
|
|
|
+ if (dynamicCount === children.length) {
|
|
|
+ // all dynamic node
|
|
|
+ ctx.registerOpration({
|
|
|
+ type: IRNodeTypes.APPEND_NODE,
|
|
|
+ loc: ctx.node.loc,
|
|
|
+ elements: dynamicChildren.map((child) => child.id!),
|
|
|
+ parent: ctx.reference(),
|
|
|
+ })
|
|
|
+ } else if (dynamicCount > 0 && dynamicCount < children.length) {
|
|
|
+ // mixed
|
|
|
+ for (const [indexString, child] of Object.entries(ctx.dynamic.children)) {
|
|
|
+ if (!child.ghost) continue
|
|
|
+
|
|
|
+ const index = Number(indexString)
|
|
|
+ let anchor: InsertAnchor
|
|
|
+ if (index === 0) {
|
|
|
+ anchor = 'first'
|
|
|
+ } else if (index === children.length - 1) {
|
|
|
+ anchor = 'last'
|
|
|
+ } else {
|
|
|
+ childrenTemplate[index] = `<!>`
|
|
|
+ anchor = child.placeholder = ctx.incraseId()
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.registerOpration({
|
|
|
+ type: IRNodeTypes.INSERT_NODE,
|
|
|
+ loc: ctx.node.loc,
|
|
|
+ element: child.id!,
|
|
|
+ parent: ctx.reference(),
|
|
|
+ anchor,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.template += childrenTemplate.join('')
|
|
|
+
|
|
|
+ // finalize template
|
|
|
if (root) ctx.registerTemplate()
|
|
|
|
|
|
- function walkNode(node: TemplateChildNode, i: number) {
|
|
|
+ function walkNode(node: TemplateChildNode, index: number) {
|
|
|
const child = createContext(node, ctx, index)
|
|
|
- const isFirst = i === 0
|
|
|
- const isLast = i === children.length - 1
|
|
|
+ const isFirst = index === 0
|
|
|
+ const isLast = index === children.length - 1
|
|
|
|
|
|
switch (node.type) {
|
|
|
case 1 satisfies NodeTypes.ELEMENT: {
|
|
|
@@ -190,11 +225,11 @@ function transformChildren(
|
|
|
break
|
|
|
}
|
|
|
case 2 satisfies NodeTypes.TEXT: {
|
|
|
- ctx.template += node.content
|
|
|
+ child.template += node.content
|
|
|
break
|
|
|
}
|
|
|
case 3 satisfies NodeTypes.COMMENT: {
|
|
|
- ctx.template += `<!--${node.content}-->`
|
|
|
+ child.template += `<!--${node.content}-->`
|
|
|
break
|
|
|
}
|
|
|
case 5 satisfies NodeTypes.INTERPOLATION: {
|
|
|
@@ -214,19 +249,20 @@ function transformChildren(
|
|
|
// IfNode
|
|
|
// IfBranchNode
|
|
|
// ForNode
|
|
|
- ctx.template += `[type: ${node.type}]`
|
|
|
+ child.template += `[type: ${node.type}]`
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (Object.keys(child.children).length > 0 || child.store)
|
|
|
- ctx.children[index] = {
|
|
|
- id: child.store ? child.getId() : null,
|
|
|
- store: child.store,
|
|
|
- children: child.children,
|
|
|
- ghost: child.ghost,
|
|
|
- }
|
|
|
+ childrenTemplate.push(child.template)
|
|
|
|
|
|
- if (!child.ghost) index++
|
|
|
+ if (
|
|
|
+ child.dynamic.ghost ||
|
|
|
+ child.dynamic.referenced ||
|
|
|
+ child.dynamic.placeholder ||
|
|
|
+ Object.keys(child.dynamic.children).length
|
|
|
+ ) {
|
|
|
+ ctx.dynamic.children[index] = child.dynamic
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -254,62 +290,38 @@ function transformInterpolation(
|
|
|
) {
|
|
|
const { node } = ctx
|
|
|
|
|
|
- if (node.content.type === (4 satisfies NodeTypes.SIMPLE_EXPRESSION)) {
|
|
|
- const expr = processExpression(ctx, node.content)!
|
|
|
-
|
|
|
- const parent = ctx.parent!
|
|
|
- const parentId = parent.getId()
|
|
|
- parent.store = true
|
|
|
-
|
|
|
- if (isFirst && isLast) {
|
|
|
- ctx.registerEffect(expr, {
|
|
|
- type: IRNodeTypes.SET_TEXT,
|
|
|
- loc: node.loc,
|
|
|
- element: parentId,
|
|
|
- value: expr,
|
|
|
- })
|
|
|
- } else {
|
|
|
- let id: number
|
|
|
- let anchor: number | 'first' | 'last'
|
|
|
-
|
|
|
- if (!isFirst && !isLast) {
|
|
|
- id = ctx.incraseId()
|
|
|
- anchor = ctx.getId()
|
|
|
- ctx.template += '<!>'
|
|
|
- ctx.store = true
|
|
|
- } else {
|
|
|
- id = ctx.getId()
|
|
|
- ctx.ghost = true
|
|
|
- anchor = isFirst ? 'first' : 'last'
|
|
|
- }
|
|
|
-
|
|
|
- ctx.registerOpration(
|
|
|
- {
|
|
|
- type: IRNodeTypes.CREATE_TEXT_NODE,
|
|
|
- loc: node.loc,
|
|
|
- id,
|
|
|
- value: 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,
|
|
|
- value: expr,
|
|
|
- })
|
|
|
- }
|
|
|
+ if (node.content.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) {
|
|
|
+ // TODO: CompoundExpressionNode: {{ count + 1 }}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // TODO: CompoundExpressionNode: {{ count + 1 }}
|
|
|
+ const expr = processExpression(ctx, node.content)!
|
|
|
+
|
|
|
+ if (isFirst && isLast) {
|
|
|
+ const parent = ctx.parent!
|
|
|
+ const parentId = parent.reference()
|
|
|
+ ctx.registerEffect(expr, {
|
|
|
+ type: IRNodeTypes.SET_TEXT,
|
|
|
+ loc: node.loc,
|
|
|
+ element: parentId,
|
|
|
+ value: expr,
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const id = ctx.reference()
|
|
|
+ ctx.dynamic.ghost = true
|
|
|
+ ctx.registerOpration({
|
|
|
+ type: IRNodeTypes.CREATE_TEXT_NODE,
|
|
|
+ loc: node.loc,
|
|
|
+ id,
|
|
|
+ value: expr,
|
|
|
+ })
|
|
|
+ ctx.registerEffect(expr, {
|
|
|
+ type: IRNodeTypes.SET_TEXT,
|
|
|
+ loc: node.loc,
|
|
|
+ element: id,
|
|
|
+ value: expr,
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function transformProp(
|
|
|
@@ -327,7 +339,6 @@ function transformProp(
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- ctx.store = true
|
|
|
const expr = processExpression(ctx, node.exp)
|
|
|
switch (name) {
|
|
|
case 'bind': {
|
|
|
@@ -348,7 +359,7 @@ function transformProp(
|
|
|
ctx.registerEffect(expr, {
|
|
|
type: IRNodeTypes.SET_PROP,
|
|
|
loc: node.loc,
|
|
|
- element: ctx.getId(),
|
|
|
+ element: ctx.reference(),
|
|
|
name: node.arg.content,
|
|
|
value: expr,
|
|
|
})
|
|
|
@@ -372,7 +383,7 @@ function transformProp(
|
|
|
ctx.registerEffect(expr, {
|
|
|
type: IRNodeTypes.SET_EVENT,
|
|
|
loc: node.loc,
|
|
|
- element: ctx.getId(),
|
|
|
+ element: ctx.reference(),
|
|
|
name: node.arg.content,
|
|
|
value: expr,
|
|
|
})
|
|
|
@@ -383,7 +394,7 @@ function transformProp(
|
|
|
ctx.registerEffect(value, {
|
|
|
type: IRNodeTypes.SET_HTML,
|
|
|
loc: node.loc,
|
|
|
- element: ctx.getId(),
|
|
|
+ element: ctx.reference(),
|
|
|
value,
|
|
|
})
|
|
|
break
|
|
|
@@ -393,7 +404,7 @@ function transformProp(
|
|
|
ctx.registerEffect(value, {
|
|
|
type: IRNodeTypes.SET_TEXT,
|
|
|
loc: node.loc,
|
|
|
- element: ctx.getId(),
|
|
|
+ element: ctx.reference(),
|
|
|
value,
|
|
|
})
|
|
|
break
|