跳到主要内容

transform

2024年03月18日
柏拉文
越努力,越幸运

一、packages/compiler-core/src/transform.ts


import { isArray, isString } from '@vue/shared';
import { NodeTypes } from './ast';
import { TO_DISPLAY_STRING } from './runtimeHelper';
import { isSingleElementRoot } from './transforms/hoistStatic';

export function transform(root, options) {
const context = createTransformContext(root, options);
traverseNode(root, context);

createRootCodegen(root);

root.helpers = [...context.helpers.keys()];
root.components = [];
root.directives = [];
root.imports = [];
root.hoists = [];
root.temps = [];
root.cached = [];
}

export function createTransformContext(root, { nodeTransforms }) {
const context: { [key: string]: any } = {
nodeTransforms,
root,
helpers: new Map(),
currentNode: root,
parent: null,
childIndex: 0,
helper(name) {
const count = context.helpers.get(name) || 0;
context.helpers.set(name, count + 1);
return name;
},
replaceNode(node) {
context.parent.children[context.childIndex] = context.currentNode = node;
}
};

return context;
}

export function traverseNode(node, context) {
context.currentNode = node;
const { nodeTransforms } = context;
const exitFns: any[] = [];

for (let i = 0; i < nodeTransforms.length; i++) {
const onExit = nodeTransforms[i](node, context);
if (onExit) {
if (isArray(onExit)) {
exitFns.push(...onExit);
} else {
exitFns.push(onExit);
}
}

if (!context.currentNode) {
return;
} else {
node = context.currentNode;
}
}

switch (node.type) {
case NodeTypes.IF_BRANCH:
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:
traverseChildren(node, context);
break;
case NodeTypes.INTERPOLATION:
context.helper(TO_DISPLAY_STRING);
break;
case NodeTypes.IF:
for (let i = 0; i < node.branches.length; i++) {
traverseNode(node.branches[i], context);
}
break;
}

context.currentNode = node;

let i = exitFns.length;
while (i--) {
exitFns[i]();
}
}

export function traverseChildren(parent, context) {
parent.children.forEach((node, index) => {
context.parent = parent;
context.childIndex = index;
traverseNode(node, context);
});
}

export function createRootCodegen(root) {
const { children } = root;

if (children.length === 1) {
const child = children[0];
if (isSingleElementRoot(root, child)) {
root.codegenNode = child.codegenNode;
}
}
}

export function createStructuralDirectiveTransform(name, fn) {
const matches = isString(name) ? n => n === name : n => name.test(n);
return (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
const { props } = node;
const exitFns: any[] = [];
for (let i = 0; i < props.length; i++) {
const prop = props[i];
if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
props.splice(i, 1);
i--;
const onExit = fn(node, prop, context);
if (onExit) {
exitFns.push(onExit);
}
}
}
return exitFns;
}
};
}

二、packages/compiler-core/src/transforms/hoistStatic.ts


import { NodeTypes } from '../ast';

export function isSingleElementRoot(root, child) {
const { children } = root;
return children.length === 1 && child.type === NodeTypes.ELEMENT;
}

三、packages/compiler-core/src/transforms/transformElement.ts


import { NodeTypes, createVNodeCall } from '../ast';

export const transformElement = (node, context) => {
return function postTransformElement() {
node = context.currentNode;

if (node.type !== NodeTypes.ELEMENT) {
return;
}

const { tag } = node;
let vnodeTag = `"${tag}"`;
let vnodeProps = [];
let vnodeChildren = node.children;

node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren
);
};
};

四、packages/compiler-core/src/transforms/transformText.ts


import { isText } from '../utils';
import { NodeTypes, createCompoundExpression } from '../ast';

export const transformText = (node, context) => {
if (
node.type === NodeTypes.ROOT ||
node.type === NodeTypes.ELEMENT ||
node.type === NodeTypes.FOR ||
node.type === NodeTypes.IF_BRANCH
) {
return () => {
const children = node.children;
let currentContainer;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (isText(child)) {
for (let j = i + 1; j < children.length; j++) {
const next = children[j];

if (isText(next)) {
if (!currentContainer) {
currentContainer = children[i] = createCompoundExpression(
[child],
child.loc
);
}

currentContainer.children.push(` + `, next);
children.splice(j, 1);
j--;
} else {
currentContainer = undefined;
break;
}
}
}
}
};
}
};

五、packages/compiler-core/src/transforms/vif.ts


import { isString } from '@vue/shared';
import {
NodeTypes,
createCallExpression,
createConditionalExpression,
createObjectExpression,
createObjectProperty,
createSimpleExpression
} from '../ast';
import { createStructuralDirectiveTransform } from '../transform';
import { getMemoedVNodeCall, injectProp } from '../utils';
import { CREATE_COMMENT } from '../runtimeHelper';

export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
let key = 0;

return () => {
if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(branch, key, context);
}
};
});
}
);

export function processIf(node, dir, context, processCodegen) {
if (dir.name === 'if') {
const branch = createIfBranch(node, dir);
const ifNode = {
type: NodeTypes.IF,
loc: {},
branches: [branch]
};

context.replaceNode(ifNode);

if (processCodegen) {
return processCodegen(ifNode, branch, true);
}
}
}

function createIfBranch(node, dir) {
return {
type: NodeTypes.IF_BRANCH,
loc: {},
condition: dir.exp,
children: [node]
};
}

export function createCodegenNodeForBranch(branch, keyIndex, context) {
if (branch.condition) {
return createConditionalExpression(
branch.condition,
createChildrenCodegenNode(branch, keyIndex),
createCallExpression(context.helper(CREATE_COMMENT), ['"v-if"', 'true'])
);
} else {
return createChildrenCodegenNode(branch, keyIndex);
}
}

function createChildrenCodegenNode(branch, keyIndex) {
const keyProperty = createObjectProperty(
`key`,
createSimpleExpression(`${keyIndex}`, false)
);

const { children } = branch;
const firstChild = children[0];
const ret = firstChild.codegenNode;
const vnodeCall = getMemoedVNodeCall(ret);

injectProp(vnodeCall, keyProperty);
return ret;
}