AST介绍
定义
抽象语法树 (Abstract Syntax Tree),简称 AST,它是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构
作用
- 编辑器的错误提示、代码格式化、代码高亮、代码自动补全
elint
、pretiier
对代码错误或风格的检查webpack
通过babel
转译javascript
语法 js编译编译运行的第一步就是生成AST树
词法分析
词法分析,也称之为扫描(scanner),简单来说就是调用 next() 方法,一个一个字母的来读取字符,然后与定义好的 JavaScript 关键字符做比较,生成对应的Token。Token 是一个不可分割的最小单元。 词法分析器里,每个关键字是一个 Token ,每个标识符是一个 Token,每个操作符是一个 Token,每个标点符号也都是一个 Token。除此之外,还会过滤掉源程序中的注释和空白字符(换行符、空格、制表符等。 最终,整个代码将被分割进一个tokens列表(或者说一维数组)
语法分析
语法分析会将词法分析出来的 Token 转化成有语法含义的抽象语法树结构。同时,验证语法,语法如果有错的话,抛出语法错误
AST展示
具体属性展示可参考:astexplorer.net/
Babel介绍
定义
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中
作用
- 语法转换 (es6转换为es5)
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js)
- 源码转换 (将源码进行统一的变化升级)
Babel的配置方式
- babel.config.js (注意需要module.export)
- .babelrc (qrn采用的方式)
使用方式
1.通过shell指令
babel --plugins @babel/plugin-transform-arrow-functions script.js
2.使用api
require("@babel/core").transformSync("code", { plugins: ["@babel/plugin-transform-arrow-functions"], });
Plugin与Presets
1.执行顺序:
- 插件在Presets前运行
- 插件顺序从前往后
- 顺序是颠倒的 2.插件是具体功能的体现,粒度越小使用起来越方便。Preset是一组插件的集合
Babel插件开发常用插件预览
开发流程演示
需转换的代码
import React, { Component } from 'react';
const fs = require('fs');
class ClassComponent extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log('componentDidMount');
// const a = 2;
}
render() {
return (
<div>
<span>demo</span>
</div>
)
}
}
export default ClassComponent;
function fun() {
console.log("data");
}
const x: number = 123;
type Props = $ReadOnly<{||}>;
type State = {|clicked: string|};
源代码转换片段
"use strict";
const generator = require("@babel/generator");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse");
const babel = require('@babel/core');
const types = require("@babel/types");
const fs = require('fs');
const path = require('path');
const logger = require("../logger");
const REG = /.js$|.jsx$|.ts$|.tsx$/;
const FILTER_DIR = ['node_modules', '.git'];
function compile(code, fileName) {
const visitor = {
CallExpression(path) {
const node = path.node;
//处理require引用文件路径变化
if (node.callee?.name === 'require' && node?.arguments[0]?.value === 'fs') {
node.arguments[0].value = 'path';
node.arguments[0].raw = 'path';
}
},
ReturnStatement(path) { //处理组件名和属性变化
const node = path.node;
// const argument = types.expre
if (node?.argument?.type === 'JSXElement') {
node.argument.openingElement = types.jsxOpeningElement(types.jsxIdentifier('Fragment'), [types.jsxAttribute(types.jsxIdentifier('id'), types.stringLiteral('aaa'))], false);
node.argument.closingElement = types.jsxClosingElement(types.jsxIdentifier('Fragment'));
}
},
ClassBody(path) { //处理组件生命周期发生变化
const node = path.node;
node?.body?.forEach((item) => {
if (item.key.name === 'componentDidMount') {
item.key.name = 'componentDidUpdate';
}
})
},
ImportDeclaration(path) { //处理import应用路径发生变化
const node = path.node;
if (node?.source?.value === 'react') {
node.source.value = 'react-dom';
node.source.raw = 'react-dom';
}
}
}
// method 1
// const ast = parser.parse(code, {
// sourceType: "module",
// plugins: [
// "jsx",
// ["flow"]
// ]
// });
// traverse.default(ast, visitor);
// const result = generator.default(ast, {}, code);
// return result;
// method 2
const result = babel.transform(code, {
plugins: [
{visitor},
'@babel/plugin-syntax-jsx',
'@babel/plugin-syntax-flow',
// ['@babel/plugin-syntax-typescript', {isTSX: true}],
]
})
return result;
}
function traverseFile(dir) {
fs.readdirSync(dir).forEach((item) => {
const filePath = path.join(dir,item);
const stat = fs.lstatSync(filePath);
if (stat.isDirectory()) {
if (!FILTER_DIR.includes(item)) {
if (fs.existsSync(filePath)) {
traverseFile(filePath);
}
}
} else {
if (REG.test(item)) {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8');
const newCode = compile(content,filePath)
fs.writeFile(filePath,newCode.code, err => {
if (err) {
logger.error(err,`更新代码写入错误`);
} else {
logger.success(`升级文件${filePath}的代码成功`);
}
})
} else {
logger.error(`${filePath}文件不存在`);
}
}
}
})
}
module.exports = (options) => {
const args = options.args;
if (args.length === 0) {
traverseFile(process.cwd());
} else {
args.forEach((item) => {
//绝对路径
if (fs.existsSync(item)) {
traverseFile(item);
} else {
//相对路径
const filePath = path.join(process.cwd(),item);
if (fs.existsSync(filePath)) {
traverseFile(filePath);
} else {
logger.error(`路径${item}和${filePath}都不存在`);
}
}
})
}
// traverseFile(process.cwd())
// const filePath = path.join(process.cwd(), './src/lib/demo.ts');
// if (fs.existsSync(filePath)) {
// const content = fs.readFileSync(filePath, 'utf-8');
// const newCode = compile(content, 'demo.ts');
// console.log(newCode, 'new_code');
// fs.writeFile('./test.ts',newCode.code, err => {
// if (err) throw err;
// logger.success(`升级文件${filePath}的代码成功`);
// })
// } else {
// logger.error(`${filePath}文件不存在`);
// }
};
转换后的代码
import React, { Component } from "react-dom";
const fs = require("path");
class ClassComponent extends Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log('componentDidMount'); // const a = 2;
}
render() {
return <Fragment id="aaa">
<span>demo</span>
</Fragment>;
}
}
export default ClassComponent;
function fun() {
console.log("data");
}
const x: number = 123;
type Props = $ReadOnly<{||}>;
type State = {|
clicked: string
|};
参考
-Babel插件开发手册
-Babel用户手册
-Babel官网
-生成AST节点参考文档
-AST转换
-Babel Github