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.

298 lines
7.2 KiB
TypeScript
Raw Normal View History

2023-11-29 22:09:46 +08:00
import {
2023-12-01 07:34:18 +08:00
type AllNode,
2023-12-29 22:05:33 +08:00
type TransformOptions as BaseTransformOptions,
type CommentNode,
2023-12-01 08:05:43 +08:00
type CompilerCompatOptions,
2023-12-29 22:05:33 +08:00
type ElementNode,
ElementTypes,
NodeTypes,
2023-12-29 22:05:33 +08:00
type RootNode,
type SimpleExpressionNode,
2023-12-29 22:05:33 +08:00
type TemplateChildNode,
2023-12-01 08:05:43 +08:00
defaultOnError,
defaultOnWarn,
isVSlot,
2023-11-17 03:01:19 +08:00
} from '@vue/compiler-dom'
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
import {
2024-01-29 22:08:57 +08:00
type BlockIRNode,
DynamicFlag,
type HackOptions,
2023-12-01 23:30:21 +08:00
type IRDynamicInfo,
IRNodeTypes,
2023-12-29 22:05:33 +08:00
type OperationNode,
type RootIRNode,
2024-01-29 22:08:57 +08:00
type VaporDirectiveNode,
} from './ir'
import { isConstantExpression } from './utils'
2024-02-09 02:40:01 +08:00
import { genDefaultDynamic } from './transforms/utils'
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>,
) => DirectiveTransformResult | void
export interface DirectiveTransformResult {
key: SimpleExpressionNode
value: SimpleExpressionNode
modifier?: '.' | '^'
runtimeCamelize?: boolean
}
2023-12-03 01:40:26 +08:00
// A structural directive transform is technically also a NodeTransform;
// Only v-if and v-for fall into this category.
export type StructuralDirectiveTransform = (
node: ElementNode,
dir: VaporDirectiveNode,
context: TransformContext<ElementNode>,
) => void | (() => 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
parent: TransformContext<RootNode | ElementNode> | null
2023-11-23 23:42:08 +08:00
root: TransformContext<RootNode>
index: number
block: BlockIRNode
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
childrenTemplate: (string | null)[]
2023-12-01 23:30:21 +08:00
dynamic: IRDynamicInfo
2023-11-27 05:16:21 +08:00
comment: CommentNode[]
2023-12-01 07:34:18 +08:00
inVOnce: boolean
2023-11-23 23:42:08 +08:00
enterBlock(ir: TransformContext['block']): () => void
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: SimpleExpressionNode[],
2023-12-01 23:30:21 +08:00
operation: OperationNode[],
): void
registerOperation(...operations: OperationNode[]): void
}
const defaultOptions = {
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,
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(
root: RootIRNode,
2023-11-23 23:42:08 +08:00
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-23 23:42:08 +08:00
const context: TransformContext<RootNode> = {
2023-11-23 23:42:08 +08:00
node,
parent: null,
index: 0,
root: null!, // set later
block: root.block,
enterBlock(ir) {
const { block, template, dynamic, childrenTemplate } = this
this.block = ir
this.dynamic = ir.dynamic
this.template = ''
this.childrenTemplate = []
return () => {
// exit
this.block = block
this.template = template
this.dynamic = dynamic
this.childrenTemplate = childrenTemplate
}
},
options: extend({}, defaultOptions, options),
dynamic: root.block.dynamic,
2023-12-01 07:34:18 +08:00
inVOnce: false,
comment: [],
2023-11-23 23:42:08 +08:00
increaseId: () => globalId++,
2023-11-27 05:16:21 +08:00
reference() {
if (this.dynamic.id !== undefined) return this.dynamic.id
this.dynamic.flags |= DynamicFlag.REFERENCED
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) {
expressions = expressions.filter(exp => !isConstantExpression(exp))
if (this.inVOnce || expressions.length === 0) {
2023-12-01 23:30:21 +08:00
return this.registerOperation(...operations)
2023-11-27 05:16:21 +08:00
}
2024-01-29 03:11:30 +08:00
const existing = this.block.effect.find(e =>
isSameExpression(e.expressions, expressions),
2023-12-07 01:09:03 +08:00
)
if (existing) {
existing.operations.push(...operations)
} else {
this.block.effect.push({
expressions,
2023-12-07 01:09:03 +08:00
operations,
})
}
function isSameExpression(
a: SimpleExpressionNode[],
b: SimpleExpressionNode[],
) {
2023-12-07 01:09:03 +08:00
if (a.length !== b.length) return false
return a.every((exp, i) => exp.content === b[i].content)
2023-12-07 01:09:03 +08:00
}
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 (!this.template) {
return -1
}
2023-11-23 23:42:08 +08:00
const existing = root.template.findIndex(
template => template === this.template,
)
if (existing !== -1) {
return (this.dynamic.template = existing)
}
2023-11-23 23:42:08 +08:00
root.template.push(this.template)
return (this.dynamic.template = root.template.length - 1)
2023-11-23 23:42:08 +08:00
},
registerOperation(...node) {
this.block.operation.push(...node)
},
2023-11-23 23:42:08 +08:00
}
context.root = context
return context
2023-11-23 23:42:08 +08:00
}
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-23 23:42:08 +08:00
template: [],
block: {
type: IRNodeTypes.BLOCK,
node: root,
dynamic: extend(genDefaultDynamic(), {
flags: DynamicFlag.REFERENCED,
} satisfies Partial<IRDynamicInfo>),
effect: [],
operation: [],
returns: [],
},
2023-11-17 03:01:19 +08:00
}
const context = createRootContext(ir, root, options)
2023-12-02 16:59:43 +08:00
transformNode(context)
2023-11-23 23:42:08 +08:00
return ir
2023-11-17 03:01:19 +08:00
}
export function transformNode(
2023-12-01 07:34:18 +08:00
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
}
}
2024-01-29 22:08:57 +08:00
2023-12-01 07:34:18 +08:00
// 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.registerTemplate()
}
2023-12-01 07:34:18 +08:00
}
export function createStructuralDirectiveTransform(
name: string | string[],
fn: StructuralDirectiveTransform,
): NodeTransform {
const matches = (n: string) =>
isString(name) ? n === name : name.includes(n)
return (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
const { props } = node
// structural directive transforms are not concerned with slots
// as they are handled separately in vSlot.ts
if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
return
}
const exitFns = []
2024-01-28 03:28:27 +08:00
for (const prop of props) {
if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
const onExit = fn(
node,
prop as VaporDirectiveNode,
context as TransformContext<ElementNode>,
)
if (onExit) exitFns.push(onExit)
}
}
return exitFns
}
}
}