2023-11-29 22:09:46 +08:00
|
|
|
import {
|
|
|
|
type RootNode,
|
|
|
|
type TemplateChildNode,
|
|
|
|
type ElementNode,
|
2023-12-01 07:34:18 +08:00
|
|
|
type TransformOptions as BaseTransformOptions,
|
|
|
|
type ParentNode,
|
|
|
|
type AllNode,
|
2023-12-01 08:05:43 +08:00
|
|
|
type CompilerCompatOptions,
|
2023-11-24 11:07:31 +08:00
|
|
|
NodeTypes,
|
2023-12-01 08:05:43 +08:00
|
|
|
defaultOnError,
|
|
|
|
defaultOnWarn,
|
2023-11-17 03:01:19 +08:00
|
|
|
} from '@vue/compiler-dom'
|
2023-12-02 16:59:43 +08:00
|
|
|
import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared'
|
2023-11-24 11:07:31 +08:00
|
|
|
import {
|
2023-11-24 15:02:47 +08:00
|
|
|
type OperationNode,
|
2023-11-24 11:07:31 +08:00
|
|
|
type RootIRNode,
|
2023-12-01 23:30:21 +08:00
|
|
|
type IRDynamicInfo,
|
|
|
|
type IRExpression,
|
2023-11-24 11:07:31 +08:00
|
|
|
IRNodeTypes,
|
|
|
|
} from './ir'
|
2023-12-06 00:15:57 +08:00
|
|
|
import type { VaporDirectiveNode, HackOptions } from './ir'
|
2023-11-23 23:42:08 +08:00
|
|
|
|
2023-12-01 07:34:18 +08:00
|
|
|
export type NodeTransform = (
|
|
|
|
node: RootNode | TemplateChildNode,
|
2023-12-02 16:59:43 +08:00
|
|
|
context: TransformContext<RootNode | TemplateChildNode>,
|
2023-12-01 07:34:18 +08:00
|
|
|
) => void | (() => void) | (() => void)[]
|
|
|
|
|
2023-12-03 01:40:26 +08:00
|
|
|
export type DirectiveTransform = (
|
2023-12-06 00:15:57 +08:00
|
|
|
dir: VaporDirectiveNode,
|
2023-12-03 01:40:26 +08:00
|
|
|
node: ElementNode,
|
2023-12-03 03:49:44 +08:00
|
|
|
context: TransformContext<ElementNode>,
|
2023-12-03 01:40:26 +08:00
|
|
|
) => void
|
|
|
|
|
2023-12-01 07:34:18 +08:00
|
|
|
export type TransformOptions = HackOptions<BaseTransformOptions>
|
|
|
|
|
|
|
|
export interface TransformContext<T extends AllNode = AllNode> {
|
2023-11-23 23:42:08 +08:00
|
|
|
node: T
|
2023-12-01 07:34:18 +08:00
|
|
|
parent: TransformContext<ParentNode> | null
|
2023-11-23 23:42:08 +08:00
|
|
|
root: TransformContext<RootNode>
|
|
|
|
index: number
|
2023-12-01 07:34:18 +08:00
|
|
|
options: Required<
|
|
|
|
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
|
|
|
>
|
2023-11-27 05:16:21 +08:00
|
|
|
|
2023-11-23 23:42:08 +08:00
|
|
|
template: string
|
2023-12-02 16:59:43 +08:00
|
|
|
childrenTemplate: string[]
|
2023-12-01 23:30:21 +08:00
|
|
|
dynamic: IRDynamicInfo
|
2023-11-27 05:16:21 +08:00
|
|
|
|
2023-12-01 07:34:18 +08:00
|
|
|
inVOnce: boolean
|
2023-11-23 23:42:08 +08:00
|
|
|
|
2023-11-27 05:16:21 +08:00
|
|
|
reference(): number
|
2023-11-29 20:54:45 +08:00
|
|
|
increaseId(): number
|
2023-11-23 23:42:08 +08:00
|
|
|
registerTemplate(): number
|
2023-12-01 23:30:21 +08:00
|
|
|
registerEffect(
|
|
|
|
expressions: Array<IRExpression | null | undefined>,
|
|
|
|
operation: OperationNode[],
|
|
|
|
): void
|
2023-11-29 20:54:45 +08:00
|
|
|
registerOperation(...operations: OperationNode[]): void
|
2023-11-24 11:07:31 +08:00
|
|
|
helper(name: string): string
|
2023-11-23 23:42:08 +08:00
|
|
|
}
|
|
|
|
|
2023-12-01 07:34:18 +08:00
|
|
|
// TODO use class for better perf
|
2023-11-23 23:42:08 +08:00
|
|
|
function createRootContext(
|
|
|
|
ir: RootIRNode,
|
|
|
|
node: RootNode,
|
2023-12-01 07:34:18 +08:00
|
|
|
options: TransformOptions = {},
|
2023-11-23 23:42:08 +08:00
|
|
|
): TransformContext<RootNode> {
|
2023-11-26 03:08:35 +08:00
|
|
|
let globalId = 0
|
2023-11-24 15:02:47 +08:00
|
|
|
const { effect, operation: operation, helpers, vaporHelpers } = ir
|
2023-11-23 23:42:08 +08:00
|
|
|
|
|
|
|
const ctx: TransformContext<RootNode> = {
|
|
|
|
node,
|
|
|
|
parent: null,
|
|
|
|
index: 0,
|
2023-11-27 07:16:24 +08:00
|
|
|
root: null!, // set later
|
2023-12-01 07:34:18 +08:00
|
|
|
options: {
|
|
|
|
filename: '',
|
|
|
|
prefixIdentifiers: false,
|
|
|
|
hoistStatic: false,
|
|
|
|
hmr: false,
|
|
|
|
cacheHandlers: false,
|
|
|
|
nodeTransforms: [],
|
|
|
|
directiveTransforms: {},
|
|
|
|
transformHoist: null,
|
|
|
|
isBuiltInComponent: NOOP,
|
|
|
|
isCustomElement: NOOP,
|
|
|
|
expressionPlugins: [],
|
|
|
|
scopeId: null,
|
|
|
|
slotted: true,
|
|
|
|
ssr: false,
|
|
|
|
inSSR: false,
|
|
|
|
ssrCssVars: ``,
|
|
|
|
bindingMetadata: EMPTY_OBJ,
|
|
|
|
inline: false,
|
|
|
|
isTS: false,
|
|
|
|
onError: defaultOnError,
|
|
|
|
onWarn: defaultOnWarn,
|
|
|
|
...options,
|
|
|
|
},
|
2023-11-27 05:16:21 +08:00
|
|
|
dynamic: ir.dynamic,
|
2023-12-01 07:34:18 +08:00
|
|
|
inVOnce: false,
|
2023-11-23 23:42:08 +08:00
|
|
|
|
2023-11-29 20:54:45 +08:00
|
|
|
increaseId: () => globalId++,
|
2023-11-27 05:16:21 +08:00
|
|
|
reference() {
|
|
|
|
if (this.dynamic.id !== null) return this.dynamic.id
|
|
|
|
this.dynamic.referenced = true
|
2023-11-29 20:54:45 +08:00
|
|
|
return (this.dynamic.id = this.increaseId())
|
2023-11-26 03:08:35 +08:00
|
|
|
},
|
2023-12-01 23:30:21 +08:00
|
|
|
registerEffect(expressions, operations) {
|
|
|
|
if (
|
|
|
|
this.inVOnce ||
|
|
|
|
(expressions = expressions.filter(Boolean)).length === 0
|
|
|
|
) {
|
|
|
|
return this.registerOperation(...operations)
|
2023-11-27 05:16:21 +08:00
|
|
|
}
|
2023-12-07 01:09:03 +08:00
|
|
|
const existing = effect.find((e) =>
|
|
|
|
isSameExpression(e.expressions, expressions as IRExpression[]),
|
|
|
|
)
|
|
|
|
if (existing) {
|
|
|
|
existing.operations.push(...operations)
|
|
|
|
} else {
|
|
|
|
effect.push({
|
|
|
|
expressions: expressions as IRExpression[],
|
|
|
|
operations,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function isSameExpression(a: IRExpression[], b: IRExpression[]) {
|
|
|
|
if (a.length !== b.length) return false
|
|
|
|
return a.every(
|
|
|
|
(exp, i) => identifyExpression(exp) === identifyExpression(b[i]),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function identifyExpression(exp: IRExpression) {
|
|
|
|
return typeof exp === 'string' ? exp : exp.content
|
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
template: '',
|
2023-12-02 16:59:43 +08:00
|
|
|
childrenTemplate: [],
|
2023-11-23 23:42:08 +08:00
|
|
|
registerTemplate() {
|
|
|
|
if (!ctx.template) return -1
|
|
|
|
|
2023-11-26 03:53:47 +08:00
|
|
|
const idx = ir.template.findIndex(
|
|
|
|
(t) =>
|
|
|
|
t.type === IRNodeTypes.TEMPLATE_FACTORY &&
|
|
|
|
t.template === ctx.template,
|
|
|
|
)
|
2023-11-23 23:42:08 +08:00
|
|
|
if (idx !== -1) return idx
|
|
|
|
|
|
|
|
ir.template.push({
|
2023-11-26 03:53:47 +08:00
|
|
|
type: IRNodeTypes.TEMPLATE_FACTORY,
|
2023-11-23 23:42:08 +08:00
|
|
|
template: ctx.template,
|
|
|
|
loc: node.loc,
|
|
|
|
})
|
|
|
|
return ir.template.length - 1
|
|
|
|
},
|
2023-11-29 20:54:45 +08:00
|
|
|
registerOperation(...node) {
|
2023-11-24 15:02:47 +08:00
|
|
|
operation.push(...node)
|
2023-11-24 11:07:31 +08:00
|
|
|
},
|
|
|
|
// 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
|
2023-11-27 05:16:21 +08:00
|
|
|
ctx.reference()
|
2023-11-23 23:42:08 +08:00
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
function createContext<T extends TemplateChildNode>(
|
|
|
|
node: T,
|
2023-12-01 07:34:18 +08:00
|
|
|
parent: TransformContext<ParentNode>,
|
2023-11-23 23:42:08 +08:00
|
|
|
index: number,
|
|
|
|
): TransformContext<T> {
|
|
|
|
const ctx: TransformContext<T> = {
|
|
|
|
...parent,
|
|
|
|
node,
|
|
|
|
parent,
|
|
|
|
index,
|
2023-11-27 05:16:21 +08:00
|
|
|
|
|
|
|
template: '',
|
2023-12-02 16:59:43 +08:00
|
|
|
childrenTemplate: [],
|
2023-11-27 05:16:21 +08:00
|
|
|
dynamic: {
|
|
|
|
id: null,
|
|
|
|
referenced: false,
|
|
|
|
ghost: false,
|
|
|
|
placeholder: null,
|
|
|
|
children: {},
|
2023-11-24 15:25:34 +08:00
|
|
|
},
|
2023-11-23 23:42:08 +08:00
|
|
|
}
|
|
|
|
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,
|
2023-12-01 23:30:21 +08:00
|
|
|
node: root,
|
2023-12-01 22:12:19 +08:00
|
|
|
source: root.source,
|
2023-11-17 03:01:19 +08:00
|
|
|
loc: root.loc,
|
2023-11-23 23:42:08 +08:00
|
|
|
template: [],
|
2023-11-27 05:16:21 +08:00
|
|
|
dynamic: {
|
|
|
|
id: null,
|
|
|
|
referenced: true,
|
|
|
|
ghost: true,
|
|
|
|
placeholder: null,
|
|
|
|
children: {},
|
|
|
|
},
|
2023-12-01 23:30:21 +08:00
|
|
|
effect: [],
|
2023-11-24 15:02:47 +08:00
|
|
|
operation: [],
|
2023-11-24 11:07:31 +08:00
|
|
|
helpers: new Set([]),
|
|
|
|
vaporHelpers: new Set([]),
|
2023-11-17 03:01:19 +08:00
|
|
|
}
|
2023-11-28 18:17:41 +08:00
|
|
|
|
2023-11-23 23:42:08 +08:00
|
|
|
const ctx = createRootContext(ir, root, options)
|
2023-12-01 07:34:18 +08:00
|
|
|
transformNode(ctx)
|
2023-12-02 16:59:43 +08:00
|
|
|
|
|
|
|
if (ctx.node.type === NodeTypes.ROOT) {
|
|
|
|
ctx.registerTemplate()
|
|
|
|
}
|
2023-11-26 03:53:47 +08:00
|
|
|
if (ir.template.length === 0) {
|
|
|
|
ir.template.push({
|
|
|
|
type: IRNodeTypes.FRAGMENT_FACTORY,
|
|
|
|
loc: root.loc,
|
|
|
|
})
|
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
|
|
|
|
return ir
|
2023-11-17 03:01:19 +08:00
|
|
|
}
|
|
|
|
|
2023-12-01 07:34:18 +08:00
|
|
|
function transformNode(
|
|
|
|
context: TransformContext<RootNode | TemplateChildNode>,
|
2023-11-24 14:44:57 +08:00
|
|
|
) {
|
2023-12-03 14:52:11 +08:00
|
|
|
let { node } = context
|
2023-12-01 07:34:18 +08:00
|
|
|
|
|
|
|
// apply transform plugins
|
|
|
|
const { nodeTransforms } = context.options
|
|
|
|
const exitFns = []
|
|
|
|
for (const nodeTransform of nodeTransforms) {
|
2023-12-02 16:59:43 +08:00
|
|
|
const onExit = nodeTransform(node, context)
|
2023-12-01 07:34:18 +08:00
|
|
|
if (onExit) {
|
|
|
|
if (isArray(onExit)) {
|
|
|
|
exitFns.push(...onExit)
|
|
|
|
} else {
|
|
|
|
exitFns.push(onExit)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!context.node) {
|
|
|
|
// node was removed
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
// node may have been replaced
|
|
|
|
node = context.node
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (node.type) {
|
2023-12-02 16:59:43 +08:00
|
|
|
case NodeTypes.ROOT:
|
2023-12-01 07:34:18 +08:00
|
|
|
case NodeTypes.ELEMENT: {
|
2023-12-02 16:59:43 +08:00
|
|
|
transformChildren(context as TransformContext<RootNode | ElementNode>)
|
2023-12-01 07:34:18 +08:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case NodeTypes.TEXT: {
|
|
|
|
context.template += node.content
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case NodeTypes.COMMENT: {
|
|
|
|
context.template += `<!--${node.content}-->`
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// exit transforms
|
|
|
|
context.node = node
|
|
|
|
let i = exitFns.length
|
|
|
|
while (i--) {
|
|
|
|
exitFns[i]()
|
|
|
|
}
|
2023-12-02 16:59:43 +08:00
|
|
|
|
|
|
|
if (context.node.type === NodeTypes.ROOT)
|
|
|
|
context.template += context.childrenTemplate.join('')
|
2023-12-01 07:34:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function transformChildren(ctx: TransformContext<RootNode | ElementNode>) {
|
2023-12-02 16:59:43 +08:00
|
|
|
const { children } = ctx.node
|
|
|
|
let i = 0
|
|
|
|
// const nodeRemoved = () => {
|
|
|
|
// i--
|
|
|
|
// }
|
|
|
|
for (; i < children.length; i++) {
|
|
|
|
const child = children[i]
|
|
|
|
const childContext = createContext(child, ctx, i)
|
2023-12-01 07:34:18 +08:00
|
|
|
transformNode(childContext)
|
2023-12-02 16:59:43 +08:00
|
|
|
ctx.childrenTemplate.push(childContext.template)
|
2023-12-01 07:34:18 +08:00
|
|
|
if (
|
|
|
|
childContext.dynamic.ghost ||
|
|
|
|
childContext.dynamic.referenced ||
|
|
|
|
childContext.dynamic.placeholder ||
|
|
|
|
Object.keys(childContext.dynamic.children).length
|
|
|
|
) {
|
2023-12-02 16:59:43 +08:00
|
|
|
ctx.dynamic.children[i] = childContext.dynamic
|
2023-11-27 14:16:05 +08:00
|
|
|
}
|
|
|
|
}
|
2023-12-02 16:59:43 +08:00
|
|
|
|
|
|
|
processDynamicChildren(ctx)
|
2023-11-17 03:01:19 +08:00
|
|
|
}
|
|
|
|
|
2023-12-02 16:59:43 +08:00
|
|
|
function processDynamicChildren(ctx: TransformContext<RootNode | ElementNode>) {
|
2023-11-23 23:42:08 +08:00
|
|
|
const { node } = ctx
|
|
|
|
|
2023-12-02 16:59:43 +08:00
|
|
|
let prevChildren: IRDynamicInfo[] = []
|
|
|
|
let hasStatic = false
|
|
|
|
|
|
|
|
for (let index = 0; index < node.children.length; index++) {
|
|
|
|
const child = ctx.dynamic.children[index]
|
|
|
|
|
|
|
|
if (!child || !child.ghost) {
|
|
|
|
if (prevChildren.length) {
|
|
|
|
if (hasStatic) {
|
|
|
|
ctx.childrenTemplate[index - prevChildren.length] = `<!>`
|
|
|
|
const anchor = (prevChildren[0].placeholder = ctx.increaseId())
|
2023-11-23 23:42:08 +08:00
|
|
|
|
2023-12-02 16:59:43 +08:00
|
|
|
ctx.registerOperation({
|
|
|
|
type: IRNodeTypes.INSERT_NODE,
|
|
|
|
loc: node.loc,
|
|
|
|
element: prevChildren.map((child) => child.id!),
|
|
|
|
parent: ctx.reference(),
|
|
|
|
anchor,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
ctx.registerOperation({
|
|
|
|
type: IRNodeTypes.PREPEND_NODE,
|
|
|
|
loc: node.loc,
|
|
|
|
elements: prevChildren.map((child) => child.id!),
|
|
|
|
parent: ctx.reference(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hasStatic = true
|
|
|
|
prevChildren = []
|
|
|
|
continue
|
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
|
2023-12-02 16:59:43 +08:00
|
|
|
prevChildren.push(child)
|
2023-11-24 11:39:49 +08:00
|
|
|
|
2023-12-02 16:59:43 +08:00
|
|
|
if (index === node.children.length - 1) {
|
|
|
|
ctx.registerOperation({
|
|
|
|
type: IRNodeTypes.APPEND_NODE,
|
|
|
|
loc: node.loc,
|
|
|
|
elements: prevChildren.map((child) => child.id!),
|
|
|
|
parent: ctx.reference(),
|
|
|
|
})
|
|
|
|
}
|
2023-11-24 14:59:10 +08:00
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
}
|