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

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

362 lines
8.5 KiB
TypeScript
Raw Normal View History

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,
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'
import {
2023-11-24 15:02:47 +08:00
type OperationNode,
type RootIRNode,
2023-12-01 23:30:21 +08:00
type IRDynamicInfo,
type IRExpression,
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,
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
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
registerOperation(...operations: OperationNode[]): void
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,
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
increaseId: () => globalId++,
2023-11-27 05:16:21 +08:00
reference() {
if (this.dynamic.id !== null) return this.dynamic.id
this.dynamic.referenced = true
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
},
registerOperation(...node) {
2023-11-24 15:02:47 +08:00
operation.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
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: [],
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)
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
) {
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
}