跳到主要内容

transformer.js

transformer 功能是将 parser 编译器生成的 AST 抽象语法树进一步转换,为后续 codeGenerator 使用

Demo示例一


分析

<h1 id="title"><span>hello</span>world</h1>转换为JSX语句React.createElement("h1",{id:"title"},React.createElement("span",null,"hello"),"world")

分析

<h1 id="title"><span>hello</span>world</h1> 经过 parser 编译器转换成的抽象语法树ASt为:

{
"type": "Program",
"start": 0,
"end": 43,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 43,
"expression": {
"type": "JSXElement",
"start": 0,
"end": 43,
"openingElement": {
"type": "JSXOpeningElement",
"start": 0,
"end": 15,
"attributes": [
{
"type": "JSXAttribute",
"start": 4,
"end": 14,
"name": {
"type": "JSXIdentifier",
"start": 4,
"end": 6,
"name": "id"
},
"value": {
"type": "Literal",
"start": 7,
"end": 14,
"value": "title",
"raw": "\"title\""
}
}
],
"name": {
"type": "JSXIdentifier",
"start": 1,
"end": 3,
"name": "h1"
},
"selfClosing": false
},
"closingElement": {
"type": "JSXClosingElement",
"start": 38,
"end": 43,
"name": {
"type": "JSXIdentifier",
"start": 40,
"end": 42,
"name": "h1"
}
},
"children": [
{
"type": "JSXElement",
"start": 15,
"end": 33,
"openingElement": {
"type": "JSXOpeningElement",
"start": 15,
"end": 21,
"attributes": [],
"name": {
"type": "JSXIdentifier",
"start": 16,
"end": 20,
"name": "span"
},
"selfClosing": false
},
"closingElement": {
"type": "JSXClosingElement",
"start": 26,
"end": 33,
"name": {
"type": "JSXIdentifier",
"start": 28,
"end": 32,
"name": "span"
}
},
"children": [
{
"type": "JSXText",
"start": 21,
"end": 26,
"value": "hello",
"raw": "hello"
}
]
},
{
"type": "JSXText",
"start": 33,
"end": 38,
"value": "world",
"raw": "world"
}
]
}
}
],
"sourceType": "module"
}

抽象语法树AST 经过 transformer 转换器转换后的结果为:

{
"type": "Program",
"start": 0,
"end": 87,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 87,
"expression": {
"type": "CallExpression",
"start": 0,
"end": 87,
"callee": {
"type": "MemberExpression",
"start": 0,
"end": 19,
"object": {
"type": "Identifier",
"start": 0,
"end": 5,
"name": "React"
},
"property": {
"type": "Identifier",
"start": 6,
"end": 19,
"name": "createElement"
},
"computed": false,
"optional": false
},
"arguments": [
{
"type": "Literal",
"start": 20,
"end": 24,
"value": "h1",
"raw": "\"h1\""
},
{
"type": "ObjectExpression",
"start": 25,
"end": 37,
"properties": [
{
"type": "Property",
"start": 26,
"end": 36,
"method": false,
"shorthand": false,
"computed": false,
"key": {
"type": "Identifier",
"start": 26,
"end": 28,
"name": "id"
},
"value": {
"type": "Literal",
"start": 29,
"end": 36,
"value": "title",
"raw": "\"title\""
},
"kind": "init"
}
]
},
{
"type": "CallExpression",
"start": 38,
"end": 78,
"callee": {
"type": "MemberExpression",
"start": 38,
"end": 57,
"object": {
"type": "Identifier",
"start": 38,
"end": 43,
"name": "React"
},
"property": {
"type": "Identifier",
"start": 44,
"end": 57,
"name": "createElement"
},
"computed": false,
"optional": false
},
"arguments": [
{
"type": "Literal",
"start": 58,
"end": 64,
"value": "span",
"raw": "\"span\""
},
{
"type": "Literal",
"start": 65,
"end": 69,
"value": null,
"raw": "null"
},
{
"type": "Literal",
"start": 70,
"end": 77,
"value": "hello",
"raw": "\"hello\""
}
],
"optional": false
},
{
"type": "Literal",
"start": 79,
"end": 86,
"value": "world",
"raw": "\"world\""
}
],
"optional": false
}
}
],
"sourceType": "module"
}

所以,transformer 的作用就是将 AST 抽象语法树进一步转换,替换 AST 中的一些代码

实现

const nodeTypes = require("./nodeTypes.md");
const traverse = require("./traverse.js");

class T {
static nullLiteral() {
return { type: nodeTypes.NullLiteral };
}
static stringLiteral(value) {
return { type: nodeTypes.StringLiteral, value };
}
static identifier(name) {
return { type: nodeTypes.Identifier, name };
}
static objectExpression(properties) {
return {
type: nodeTypes.ObjectExpression,
properties,
};
}
static property(key, value) {
return {
type: nodeTypes.Property,
key,
value,
};
}
static callExpression(callee, _arguments) {
return {
type: nodeTypes.CallExpression,
callee,
arguments: _arguments,
};
}
static memberExpression(object, property) {
return {
type: nodeTypes.MemberExpression,
object,
property,
};
}
static isJSXElement(node) {
return node.type === nodeTypes.JSXElement;
}
static isJSXText(node) {
return node.type === nodeTypes.JSXText;
}
}

function transformer(ast) {
traverse(ast, {
JSXElement(nodePath, parent) {
function transform(node) {
if (node == null) {
return T.nullLiteral();
}
if (T.isJSXElement(node)) {
let memberExpression = T.memberExpression(
T.identifier("React"),
T.identifier("createElement")
);
let elementType = T.stringLiteral(node.openingElement.name.name);
let attributes = node.openingElement.attributes;
let objectExpression;
if (attributes.length > 0) {
objectExpression = T.objectExpression(
attributes.map((attr) =>
T.property(
T.identifier(attr.name.name),
T.stringLiteral(attr.value.value)
)
)
);
} else {
objectExpression = T.nullLiteral();
}
let _arguments = [
elementType,
objectExpression,
...node.children.map((child) => transform(child)),
];
return T.callExpression(memberExpression, _arguments);
} else if (T.isJSXText(node)) {
return T.stringLiteral(node.value);
}
}
let newNode = transform(nodePath.node);
nodePath.replaceWith(newNode);
},
});
}

module.exports = transformer;

测试

const parser = require("./parser");
const transformer = require("./transformer");

const sourceCode = '<h1 id="title"><span>hello</span>world</h1>';
const ast = parser(sourceCode);
transformer(ast);
console.log(JSON.stringify(ast,null,2));