feat(compiler): element transform

This commit is contained in:
Evan You
2019-09-21 17:42:12 -04:00
parent 93440bba97
commit baa8954884
13 changed files with 349 additions and 160 deletions

View File

@@ -4,27 +4,45 @@ import {
ParentNode,
ChildNode,
ElementNode,
DirectiveNode
DirectiveNode,
Property
} from './ast'
import { isString } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
export type Transform = (node: ChildNode, context: TransformContext) => void
// 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
// - 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
) => false | void
) => void
export interface TransformOptions {
transforms?: Transform[]
nodeTransforms?: NodeTransform[]
directiveTransforms?: { [name: string]: DirectiveTransform }
onError?: (error: CompilerError) => void
}
export interface TransformContext {
transforms: Transform[]
emitError: (error: CompilerError) => void
export interface TransformContext extends Required<TransformOptions> {
parent: ParentNode
ancestors: ParentNode[]
childIndex: number
@@ -44,8 +62,9 @@ function createTransformContext(
options: TransformOptions
): TransformContext {
const context: TransformContext = {
transforms: options.transforms || [],
emitError: options.onError || defaultOnError,
nodeTransforms: options.nodeTransforms || [],
directiveTransforms: options.directiveTransforms || {},
onError: options.onError || defaultOnError,
parent: root,
ancestors: [],
childIndex: 0,
@@ -109,9 +128,9 @@ function traverseNode(
ancestors: ParentNode[]
) {
// apply transform plugins
const { transforms } = context
for (let i = 0; i < transforms.length; i++) {
const plugin = transforms[i]
const { nodeTransforms } = context
for (let i = 0; i < nodeTransforms.length; i++) {
const plugin = nodeTransforms[i]
plugin(node, context)
if (!context.currentNode) {
return
@@ -135,34 +154,27 @@ function traverseNode(
}
}
const identity = <T>(_: T): T => _
export function createDirectiveTransform(
export function createStructuralDirectiveTransform(
name: string | RegExp,
fn: DirectiveTransform
): Transform {
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 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
}
const { props } = node
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
fn(node, prop, context)
// structural directives are removed after being processed
// to avoid infinite recursion
props.splice(i, 1)
i--
}
}
if (didRemove) {
node.directives = dirs.filter(identity)
}
}
}
}