2019-09-18 07:08:47 +08:00
|
|
|
import {
|
|
|
|
RootNode,
|
|
|
|
NodeTypes,
|
|
|
|
ParentNode,
|
|
|
|
ChildNode,
|
|
|
|
ElementNode,
|
|
|
|
DirectiveNode
|
|
|
|
} from './ast'
|
|
|
|
import { isString } from '@vue/shared'
|
2019-09-21 00:16:19 +08:00
|
|
|
import { CompilerError, defaultOnError } from './errors'
|
2019-09-18 07:08:47 +08:00
|
|
|
|
|
|
|
export type Transform = (node: ChildNode, context: TransformContext) => void
|
|
|
|
|
|
|
|
export type DirectiveTransform = (
|
|
|
|
node: ElementNode,
|
|
|
|
dir: DirectiveNode,
|
|
|
|
context: TransformContext
|
|
|
|
) => false | void
|
|
|
|
|
|
|
|
export interface TransformOptions {
|
2019-09-21 00:16:19 +08:00
|
|
|
transforms?: Transform[]
|
2019-09-18 07:08:47 +08:00
|
|
|
onError?: (error: CompilerError) => void
|
|
|
|
}
|
|
|
|
|
2019-09-21 00:16:19 +08:00
|
|
|
interface TransformContext {
|
|
|
|
transforms: Transform[]
|
|
|
|
emitError: (error: CompilerError) => void
|
2019-09-18 07:08:47 +08:00
|
|
|
parent: ParentNode
|
|
|
|
ancestors: ParentNode[]
|
|
|
|
childIndex: number
|
2019-09-20 03:41:17 +08:00
|
|
|
currentNode: ChildNode | null
|
2019-09-18 07:08:47 +08:00
|
|
|
replaceNode(node: ChildNode): void
|
2019-09-20 03:41:17 +08:00
|
|
|
removeNode(node?: ChildNode): void
|
|
|
|
onNodeRemoved: () => void
|
2019-09-18 07:08:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function transform(root: RootNode, options: TransformOptions) {
|
|
|
|
const context = createTransformContext(root, options)
|
|
|
|
traverseChildren(root, context, context.ancestors)
|
|
|
|
}
|
|
|
|
|
|
|
|
function createTransformContext(
|
|
|
|
root: RootNode,
|
|
|
|
options: TransformOptions
|
|
|
|
): TransformContext {
|
|
|
|
const context: TransformContext = {
|
2019-09-21 00:16:19 +08:00
|
|
|
transforms: options.transforms || [],
|
|
|
|
emitError: options.onError || defaultOnError,
|
2019-09-18 07:08:47 +08:00
|
|
|
parent: root,
|
2019-09-20 00:20:59 +08:00
|
|
|
ancestors: [],
|
2019-09-18 07:08:47 +08:00
|
|
|
childIndex: 0,
|
2019-09-20 03:41:17 +08:00
|
|
|
currentNode: null,
|
2019-09-18 07:08:47 +08:00
|
|
|
replaceNode(node) {
|
2019-09-20 03:41:17 +08:00
|
|
|
if (__DEV__ && !context.currentNode) {
|
|
|
|
throw new Error(`node being replaced is already removed.`)
|
2019-09-20 00:20:59 +08:00
|
|
|
}
|
2019-09-20 03:41:17 +08:00
|
|
|
context.parent.children[context.childIndex] = context.currentNode = node
|
2019-09-18 07:08:47 +08:00
|
|
|
},
|
2019-09-20 03:41:17 +08:00
|
|
|
removeNode(node) {
|
|
|
|
const list = context.parent.children
|
|
|
|
const removalIndex = node
|
|
|
|
? list.indexOf(node)
|
|
|
|
: context.currentNode
|
|
|
|
? context.childIndex
|
|
|
|
: -1
|
|
|
|
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)
|
2019-09-18 07:08:47 +08:00
|
|
|
},
|
2019-09-20 03:41:17 +08:00
|
|
|
onNodeRemoved: () => {}
|
2019-09-18 07:08:47 +08:00
|
|
|
}
|
|
|
|
return context
|
|
|
|
}
|
|
|
|
|
|
|
|
function traverseChildren(
|
|
|
|
parent: ParentNode,
|
|
|
|
context: TransformContext,
|
|
|
|
ancestors: ParentNode[]
|
|
|
|
) {
|
|
|
|
ancestors = ancestors.concat(parent)
|
2019-09-20 03:41:17 +08:00
|
|
|
let i = 0
|
|
|
|
const nodeRemoved = () => {
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
for (; i < parent.children.length; i++) {
|
2019-09-18 07:08:47 +08:00
|
|
|
context.parent = parent
|
|
|
|
context.ancestors = ancestors
|
|
|
|
context.childIndex = i
|
2019-09-20 03:41:17 +08:00
|
|
|
context.onNodeRemoved = nodeRemoved
|
|
|
|
traverseNode((context.currentNode = parent.children[i]), context, ancestors)
|
2019-09-18 07:08:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function traverseNode(
|
|
|
|
node: ChildNode,
|
|
|
|
context: TransformContext,
|
|
|
|
ancestors: ParentNode[]
|
|
|
|
) {
|
|
|
|
// apply transform plugins
|
2019-09-21 00:16:19 +08:00
|
|
|
const { transforms } = context
|
2019-09-18 07:08:47 +08:00
|
|
|
for (let i = 0; i < transforms.length; i++) {
|
2019-09-20 00:20:59 +08:00
|
|
|
const plugin = transforms[i]
|
|
|
|
plugin(node, context)
|
2019-09-20 03:41:17 +08:00
|
|
|
if (!context.currentNode) {
|
2019-09-18 07:08:47 +08:00
|
|
|
return
|
|
|
|
} else {
|
|
|
|
// node may have been replaced
|
2019-09-20 03:41:17 +08:00
|
|
|
node = context.currentNode
|
2019-09-18 07:08:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// further traverse downwards
|
|
|
|
switch (node.type) {
|
|
|
|
case NodeTypes.IF:
|
|
|
|
for (let i = 0; i < node.branches.length; i++) {
|
|
|
|
traverseChildren(node.branches[i], context, ancestors)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case NodeTypes.FOR:
|
|
|
|
case NodeTypes.ELEMENT:
|
|
|
|
traverseChildren(node, context, ancestors)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const identity = <T>(_: T): T => _
|
|
|
|
|
|
|
|
export function createDirectiveTransform(
|
|
|
|
name: string | RegExp,
|
|
|
|
fn: DirectiveTransform
|
|
|
|
): Transform {
|
|
|
|
const matches = isString(name)
|
|
|
|
? (n: string) => n === name
|
|
|
|
: (n: string) => name.test(n)
|
|
|
|
|
|
|
|
return (node, context) => {
|
|
|
|
if (node.type === NodeTypes.ELEMENT) {
|
|
|
|
const dirs = node.directives
|
|
|
|
let didRemove = false
|
|
|
|
for (let i = 0; i < dirs.length; i++) {
|
|
|
|
if (matches(dirs[i].name)) {
|
|
|
|
const res = fn(node, dirs[i], context)
|
|
|
|
// Directives are removed after transformation by default. A transform
|
|
|
|
// returning false means the directive should not be removed.
|
|
|
|
if (res !== false) {
|
|
|
|
;(dirs as any)[i] = undefined
|
|
|
|
didRemove = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (didRemove) {
|
|
|
|
node.directives = dirs.filter(identity)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|