mkdir zhufeng-jsx-transformer
cd zhufeng-jsx-transformer
yarn add @babel/core @babel/plugin-syntax-jsx @babel/plugin-transform-react-jsx @babel/types --dev
yarn add react
<h1 id="title" key="title" ref="title">hello</h1>
let babel = require('@babel/core');
let types = require('@babel/types');
let traverse = require("@babel/traverse").default;
let generate = require("@babel/generator").default;
const code = `function ast() {}`;
const ast = babel.parse(code);
let indent = 0;
const padding = ()=>" ".repeat(indent);
traverse(ast, {
enter(path){
console.log(padding()+path.node.type+'进入');
indent+=2;
if(types.isFunctionDeclaration(path.node)){
path.node.id.name = 'newAst';
}
},
exit(path){
indent-=2;
console.log(padding()+path.node.type+'离开');
}
});
const output = generate(ast,{},code);
console.log(output.code);
const babel = require("@babel/core");
const sourceCode = `<h1 id="title" key="title" ref="title">hello</h1>`;
const result = babel.transform(sourceCode, {
plugins: [['@babel/plugin-transform-react-jsx',{runtime:'classic'}]]
});
console.log(result.code);
let React = require('react');
React.createElement("h1", {
id: "title",
key: "title",
ref: "title"
}, "hello");
console.log(JSON.stringify(element,replacer,2));
function replacer(key,value){
if(!['_owner','_store'].includes(key))
return value;
}
{
"type": "h1",
"key": "title",
"ref": "title",
"props": {
"id": "title",
"children": "hello"
}
}
const babel = require("@babel/core");
const sourceCode = `<h1 id="title" key="title" ref="title">hello</h1>`;
const result = babel.transform(sourceCode, {
+ plugins: [['@babel/plugin-transform-react-jsx',{runtime:'automatic'}]]
});
console.log(result.code);
let {jsx:_jsx} = require("react/jsx-runtime");
//import { jsx as _jsx } from "react/jsx-runtime";
let element = _jsx("h1", {id: "title",key:"title",ref:"title",children: "hello"}, "title");
console.log(JSON.stringify(element,replacer,2));
function replacer(key,value){
if(!['_owner','_store'].includes(key))
return value;
}
{
"type": "h1",
"key": "title",
"ref": "title",
"props": {
"id": "title",
"children": "hello"
}
}
replaceWith
可以用于替换节点get
用于查找特定类型的子路径find
用于向上查找一个指定条件的路径unshiftContainer
用于把AST节点插入到类似于body这样的数组中generateUidIdentifier
会生成一个不会和任何本地定义的变量冲突的标识符const babel = require("@babel/core");
const pluginTransformReactJsx = require('./plugin-transform-react-jsx');
const sourceCode = `<h1 id="title" key="title" ref="title">hello</h1>`;
const result = babel.transform(sourceCode, {
plugins: [pluginTransformReactJsx]
});
console.log(result.code);
const types = require('@babel/types');
const pluginSyntaxJsx = require('@babel/plugin-syntax-jsx').default;
const pluginTransformReactJsx = {
inherits:pluginSyntaxJsx,
visitor: {
JSXElement(path) {
let callExpression = buildJSXElementCall(path);
path.replaceWith(callExpression);
}
}
}
function buildJSXElementCall(path) {
const args = [];
return call(path,"jsx", args);
}
function call(path,name, args) {
const callee = types.identifier('_jsx');
const node = types.callExpression(callee, args);
return node;
}
module.exports = pluginTransformReactJsx;
plugin-transform-react-jsx.js
const types = require('@babel/types');
const pluginSyntaxJsx = require('@babel/plugin-syntax-jsx').default;
const pluginTransformReactJsx = {
inherits:pluginSyntaxJsx,
visitor: {
JSXElement(path) {
let callExpression = buildJSXElementCall(path);
path.replaceWith(callExpression);
}
}
}
function buildJSXElementCall(path) {
+ const openingPath = path.get("openingElement");
+ const {name} = openingPath.node.name;
+ const tag = types.stringLiteral(name);
+ const args = [tag];
+ let attributes = [];
+ for (const attrPath of openingPath.get("attributes")) {
+ attributes.push(attrPath.node);
+ }
+ const children = buildChildren(path.node);
+ const props = attributes.map(convertAttribute);
+ if (children.length > 0) {
+ props.push(buildChildrenProperty(children));
+ }
+ const attributesObject = types.objectExpression(props);
+ args.push(attributesObject);
return call(path,"jsx", args);
}
+function buildChildren(node) {
+ const elements = [];
+ for (let i = 0; i < node.children.length; i++) {
+ let child = node.children[i];
+ if (types.isJSXText(child)) {
+ elements.push(types.stringLiteral(child.value));
+ }
+ }
+ return elements;
+}
+function buildChildrenProperty(children) {
+ let childrenNode;
+ if (children.length === 1) {
+ childrenNode = children[0];
+ } else if (children.length > 1) {
+ childrenNode = types.arrayExpression(children);
+ } else {
+ return undefined;
+ }
+ return types.objectProperty(types.identifier("children"), childrenNode);
+}
+function convertAttribute(node) {
+ const value = node.value;
+ node.name.type = "Identifier";
+ return types.objectProperty(node.name, value);
+}
function call(path,name, args) {
const callee = types.identifier('_jsx');
const node = types.callExpression(callee, args);
return node;
}
module.exports = pluginTransformReactJsx;
plugin-transform-react-jsx.js
const types = require('@babel/types');
const pluginSyntaxJsx = require('@babel/plugin-syntax-jsx').default;
const pluginTransformReactJsx = {
inherits:pluginSyntaxJsx,
visitor: {
JSXElement(path) {
let callExpression = buildJSXElementCall(path);
path.replaceWith(callExpression);
}
}
}
function buildJSXElementCall(path) {
const openingPath = path.get("openingElement");
const {name} = openingPath.node.name;
const tag = types.stringLiteral(name);
const args = [tag];
let attributes = [];
for (const attrPath of openingPath.get("attributes")) {
attributes.push(attrPath.node);
}
const children = buildChildren(path.node);
const props = attributes.map(convertAttribute);
if (children.length > 0) {
props.push(buildChildrenProperty(children));
}
const attributesObject = types.objectExpression(props);
args.push(attributesObject);
return call(path,"jsx", args);
}
function buildChildren(node) {
const elements = [];
for (let i = 0; i < node.children.length; i++) {
let child = node.children[i];
if (types.isJSXText(child)) {
elements.push(types.stringLiteral(child.value));
}
}
return elements;
}
function buildChildrenProperty(children) {
let childrenNode;
if (children.length === 1) {
childrenNode = children[0];
} else if (children.length > 1) {
childrenNode = types.arrayExpression(children);
} else {
return undefined;
}
return types.objectProperty(types.identifier("children"), childrenNode);
}
function convertAttribute(node) {
const value = node.value;
node.name.type = "Identifier";
return types.objectProperty(node.name, value);
}
function call(path,name, args) {
+ const importedSource = 'react/jsx-runtime';
+ const callee = addImport(path,name,importedSource);
const node = types.callExpression(callee, args);
return node;
}
+function addImport(path,importName,importedSource){
+ const programPath = path.find(p => p.isProgram());
+ const scope = programPath.scope;
+ const localName = scope.generateUidIdentifier(importName);
+ const specifiers = [types.importSpecifier(localName, types.identifier(importName))];
+ let statement = types.importDeclaration(specifiers, types.stringLiteral(importedSource));
+ programPath.unshiftContainer("body", [statement]);
+ return localName;
+}
module.exports = pluginTransformReactJsx;