


-
parse:把代码解析为AST。
-
transform:对AST中的各个节点做相关操作,如新增、删除、替换、追加。业务开发 95%的代码都在这里。
-
generator:把AST转换为代码。


const sourceText = `function square(num) {return num * num;}`;
sourceText.replace(/num/g, 'n');
// 转换前function square(num) {return num * num;}console.log('param 2 result num is ' + square(2));// 转换后function square(n) {return n * n;}console.log('param 2 result n is ' + square(2));
module.exports = () => {return {visitor: {// 定义 visitor, 遍历 IdentifierIdentifier(path) {if (path.node.name === 'num') {path.node.name = 'n'; // 转换变量名}}}}};
// 转换前function square(num) {return num * num;}console.log('global num is ' + window.num);// 转换后function square(n) {return n * n;}console.log('global num is ' + window.n); // 出错了
module.exports = () => {return {visitor: {Identifier(path,) {// 三个前置判断if (path.node.name !== 'num') { // 变量需要为 numreturn;}if (path.parent.type !== 'FunctionDeclaration') { // 父级需要为函数return;}if (path.parent.id.name !== 'square') { // 函数名需要为 squarereturn;}const referencePaths = path.scope.bindings['num'].referencePaths; // 找到对应的引用referencePaths.forEach(path => path.node.name = 'n'); // 修改引用值path.node.name = 'n'; // 修改自身的值},}}};

// 转换前function square(num) {return num * num;}console.log('global num is ' + window.num);// 转换后function square(n) {return n * n;}console.log('global num is ' + window.num);

// 三剑客const parser = require('@babel/parser').parse;const traverse = require('@babel/traverse').default;const generate = require('@babel/generator').default;// 配套包const types = require('@babel/types');// 模板包const template = require('@babel/template').default;
const ast = parser(rawSource, {sourceType: 'module',plugins: ["jsx",],});
const ast = parse(`function square(num) {return num * num;}`);traverse(ast, { // 进行 ast 转换Identifier(path) { // 遍历变量的visitor// ...},// 其他的visitor遍历器})

const output = generate(ast, { /* options */ });
// is开头的用于判断节点types.isObjectProperty(node);types.isObjectMethod(node);// 创建 null 节点const nullNode = types.nullLiteral();// 创建 square 变量节点const squareNode = types.identifier('square');
// @babel/types// 创建节点需要查找对应的 API,传参需要匹配方法const types = require('@babel/types');const ast = types.importDeclaration([ types.importDefaultSpecifier(types.identifier('React')) ],types.stringLiteral('react'));// path.replaceWith(ast) // 节点替换
// 使用 @babel/template// 创建节点输入源代码即可,清晰易懂const template = require('@babel/template').default;const ast = template.ast(`import React from 'react'`);// path.replaceWith(ast) // 节点替换
// 定义插件const { declare } = require('@babel/helper-plugin-utils');module.exports = declare((api, options) => {return {name: 'your-plugin', // 定义插件名visitor: { // 编写业务 visitorIdentifier(path,) {// ...},}}});
// 配置 babel.config.jsmodule.exports = {presets: [require('@babel/preset-env'), // 可配合通用的 present],plugins: [require('your-plugin'),// require('./your-plugin') 也可以为相对目录]};
const core = require('@babel/core');const types = core.types; // const types = require('@babel/types');
// eslint-plugin-my-eslint-pluginmodule.exports.rules = {"var-length": context => ({ // 定义 var-length 规则,对变量长度进行检测VariableDeclarator: (node) => {if (node.id.name.length <= 1){context.report(node, '变量名长度需要大于1');}}})};
// .eslintrc.jsmodule.exports = {root: true,parserOptions: { ecmaVersion: 6 },plugins: ["my-eslint-plugin"],rules: {"my-eslint-plugin/var-length": "warn"}};


// 使用 react 编写的源码const name = 'John';const element = <div>Hello, {name}</div>;
// 通过 @babel/preset-react 转换后的代码const name = 'John';const element = React.createElement("div", null, "Hello, ", name);
export default (<view>hello <text style={{ fontWeight: 'bold' }}>world</text></view>);
<!-- 输出 Web HTML --><div>hello <span style="font-weight: bold;">world</span></div>
<!--输出小程序 axml --><view>hello <text style="font-weight: bold;">world</text></view>
// jsx 源码module.exports = function () {return (<viewvisibleonTap={e => console.log('clicked')}>ABC<button>login</button></view>);};// 目标:转后为更通用的 JavaScript 代码module.exports = function () {return {"type": "view","visible": true,"children": ["ABC",{"type": "button","children": ["login1"]}]};};
明确了目标后,我们要做的事为:
下面是实现的示例代码:
const { declare } = require('@babel/helper-plugin-utils');const jsx = require('@babel/plugin-syntax-jsx').default;const core = require('@babel/core');const t = core.types;/*遍历 JSX 标签,约定 node 为 JSXElement,如node = <view onTap={e => console.log('clicked')} visible>ABC<button>login</button></view>*/const handleJSXElement = (node) => {const tag = node.openingElement;const type = tag.name.name; // 获得表情名为 Viewconst propertyes = []; // 储存对象的属性propertyes.push( // 获得属性 type = 'ABC't.objectProperty(t.identifier('type'),t.stringLiteral(type)));const attributes = tag.attributes || []; // 标签上的属性attributes.forEach(jsxAttr => { // 遍历标签上的属性switch (jsxAttr.type) {case 'JSXAttribute': { // 处理 JSX 属性const key = t.identifier(jsxAttr.name.name); // 得到属性 onTap、visibleconst convertAttributeValue = (node) => {if (t.isJSXExpressionContainer(node)) { // 属性的值为表达式(如函数)return node.expression; // 返回表达式}// 空值转化为 true, 如将 <view visible /> 转化为 { type: 'view', visible: true }if (node === null) {return t.booleanLiteral(true);}return node;}const value = convertAttributeValue(jsxAttr.value);propertyes.push( // 获得 { type: 'view', onTap: e => console.log('clicked'), visible: true }t.objectProperty(key, value));break;}}});const children = node.children.map((e) => {switch(e.type) {case 'JSXElement': {return handleJSXElement(e); // 如果子元素有 JSX,便利 handleJSXElement 自身}case 'JSXText': {return t.stringLiteral(e.value); // 将字符串转化为字符}}return e;});propertyes.push( // 将 JSX 内的子元素转化为对象的 children 属性t.objectProperty(t.identifier('children'), t.arrayExpression(children)));const objectNode = t.objectExpression(propertyes); // 转化为 Object Node/* 最终转化为{"type": "view","visible": true,"children": ["ABC",{"type": "button","children": ["login"]}]}*/return objectNode;}module.exports = declare((api, options) => {return {inherits: jsx, // 继承 Babel 提供的 jsx 解析基础visitor: {JSXElement(path) { // 遍历 JSX 标签,如:<view />// 将 JSX 标签转化为 Objectpath.replaceWith(handleJSXElement(path.node));},}}});
-
需要基于你的基础设施进行二次编程开发的时候
-
有可视化编程操作的时候
-
有代码规范定制的时候
参考资料
[1]https://github.com/babel/babel/blob/main/packages/babel-core/src/index.js#L10-L14
[2]https://cn.eslint.org/docs/developer-guide/working-with-rules
[3]https://reactjs.bootcss.com/docs/introducing-jsx.html
技术公开课
《React 入门与实战》
React是一个用于构建用户界面的JavaScript库。本课程共54课时,带你全面深入学习React的基础知识,并通过案例掌握相关应用。
点击“阅读原文”开始学习吧~
文章评论