| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import {
- RootNode,
- NodeTypes,
- ParentNode,
- ChildNode,
- ElementNode,
- DirectiveNode,
- Property,
- ExpressionNode
- } from './ast'
- import { isString, isArray } from '@vue/shared'
- import { CompilerError, defaultOnError } from './errors'
- import { TO_STRING, COMMENT, CREATE_VNODE } from './runtimeConstants'
- // There are two types of transforms:
- //
- // - NodeTransform:
- // Transforms that operate directly on a ChildNode. NodeTransforms may mutate,
- // replace or remove the node being processed.
- export type NodeTransform = (
- node: ChildNode,
- context: TransformContext
- ) => void | (() => void) | (() => void)[]
- // - DirectiveTransform:
- // Transforms that handles a single directive attribute on an element.
- // It translates the raw directive into actual props for the VNode.
- export type DirectiveTransform = (
- dir: DirectiveNode,
- context: TransformContext
- ) => {
- props: Property | Property[]
- needRuntime: boolean
- }
- // A structural directive transform is a techically a NodeTransform;
- // Only v-if and v-for fall into this category.
- export type StructuralDirectiveTransform = (
- node: ElementNode,
- dir: DirectiveNode,
- context: TransformContext
- ) => void | (() => void)
- export interface TransformOptions {
- nodeTransforms?: NodeTransform[]
- directiveTransforms?: { [name: string]: DirectiveTransform }
- prefixIdentifiers?: boolean
- onError?: (error: CompilerError) => void
- }
- export interface TransformContext extends Required<TransformOptions> {
- root: RootNode
- imports: Set<string>
- statements: string[]
- identifiers: { [name: string]: number | undefined }
- parent: ParentNode
- childIndex: number
- currentNode: ChildNode | null
- replaceNode(node: ChildNode): void
- removeNode(node?: ChildNode): void
- onNodeRemoved: () => void
- addIdentifier(exp: ExpressionNode): void
- removeIdentifier(exp: ExpressionNode): void
- }
- function createTransformContext(
- root: RootNode,
- {
- prefixIdentifiers = false,
- nodeTransforms = [],
- directiveTransforms = {},
- onError = defaultOnError
- }: TransformOptions
- ): TransformContext {
- const context: TransformContext = {
- root,
- imports: new Set(),
- statements: [],
- identifiers: {},
- prefixIdentifiers,
- nodeTransforms,
- directiveTransforms,
- onError,
- parent: root,
- childIndex: 0,
- currentNode: null,
- replaceNode(node) {
- /* istanbul ignore if */
- if (__DEV__ && !context.currentNode) {
- throw new Error(`node being replaced is already removed.`)
- }
- context.parent.children[context.childIndex] = context.currentNode = node
- },
- removeNode(node) {
- const list = context.parent.children
- const removalIndex = node
- ? list.indexOf(node as any)
- : context.currentNode
- ? context.childIndex
- : -1
- /* istanbul ignore if */
- if (__DEV__ && removalIndex < 0) {
- throw new Error(`node being removed is not a child of current parent`)
- }
- if (!node || node === context.currentNode) {
- // current node removed
- context.currentNode = null
- context.onNodeRemoved()
- } else {
- // sibling node removed
- if (context.childIndex > removalIndex) {
- context.childIndex--
- context.onNodeRemoved()
- }
- }
- context.parent.children.splice(removalIndex, 1)
- },
- onNodeRemoved: () => {},
- addIdentifier({ content }) {
- const { identifiers } = context
- if (identifiers[content] === undefined) {
- identifiers[content] = 0
- }
- ;(identifiers[content] as number)++
- },
- removeIdentifier({ content }) {
- ;(context.identifiers[content] as number)--
- }
- }
- return context
- }
- export function transform(root: RootNode, options: TransformOptions) {
- const context = createTransformContext(root, options)
- traverseChildren(root, context)
- root.imports = [...context.imports]
- root.statements = context.statements
- }
- export function traverseChildren(
- parent: ParentNode,
- context: TransformContext
- ) {
- let i = 0
- const nodeRemoved = () => {
- i--
- }
- for (; i < parent.children.length; i++) {
- const child = parent.children[i]
- if (isString(child)) continue
- context.currentNode = child
- context.parent = parent
- context.childIndex = i
- context.onNodeRemoved = nodeRemoved
- traverseNode(child, context)
- }
- }
- export function traverseNode(node: ChildNode, context: TransformContext) {
- // apply transform plugins
- const { nodeTransforms } = context
- const exitFns = []
- for (let i = 0; i < nodeTransforms.length; i++) {
- const plugin = nodeTransforms[i]
- const onExit = plugin(node, context)
- if (onExit) {
- if (isArray(onExit)) {
- exitFns.push(...onExit)
- } else {
- exitFns.push(onExit)
- }
- }
- if (!context.currentNode) {
- // node was removed
- return
- } else {
- // node may have been replaced
- node = context.currentNode
- }
- }
- switch (node.type) {
- case NodeTypes.COMMENT:
- context.imports.add(CREATE_VNODE)
- // inject import for the Comment symbol, which is needed for creating
- // comment nodes with `createVNode`
- context.imports.add(COMMENT)
- break
- case NodeTypes.EXPRESSION:
- // no need to traverse, but we need to inject toString helper
- if (node.isInterpolation) {
- context.imports.add(TO_STRING)
- }
- break
- // for container types, further traverse downwards
- case NodeTypes.IF:
- for (let i = 0; i < node.branches.length; i++) {
- traverseChildren(node.branches[i], context)
- }
- break
- case NodeTypes.FOR:
- case NodeTypes.ELEMENT:
- traverseChildren(node, context)
- break
- }
- // exit transforms
- for (let i = 0; i < exitFns.length; i++) {
- exitFns[i]()
- }
- }
- export function createStructuralDirectiveTransform(
- name: string | RegExp,
- fn: StructuralDirectiveTransform
- ): NodeTransform {
- const matches = isString(name)
- ? (n: string) => n === name
- : (n: string) => name.test(n)
- return (node, context) => {
- if (node.type === NodeTypes.ELEMENT) {
- const { props } = node
- const exitFns = []
- for (let i = 0; i < props.length; i++) {
- const prop = props[i]
- if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
- // structural directives are removed to avoid infinite recursion
- // also we remove them *before* applying so that it can further
- // traverse itself in case it moves the node around
- props.splice(i, 1)
- i--
- const onExit = fn(node, prop, context)
- if (onExit) exitFns.push(onExit)
- }
- }
- return exitFns
- }
- }
- }
|