webpack
和Lint
等很多的工具和库的核心都是通过Abstract Syntax Tree
抽象语法树这个概念来实现对代码的检查、分析等操作的JavaScript Parser
把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作JavaScript Parser
是把JavaScript源码转化为抽象语法树的解析器。cnpm i esprima estraverse- S
let esprima = require('esprima');//把JS源代码转成AST语法树
let estraverse = require('estraverse');///遍历语法树,修改树上的节点
let escodegen = require('escodegen');//把AST语法树重新转换成代码
let code = `function ast(){}`;
let ast = esprima.parse(code);
let indent = 0;
const padding = ()=>" ".repeat(indent);
estraverse.traverse(ast,{
enter(node){
console.log(padding()+node.type+'进入');
if(node.type === 'FunctionDeclaration'){
node.id.name = 'newAst';
}
indent+=2;
},
leave(node){
indent-=2;
console.log(padding()+node.type+'离开');
}
});
Program进入
FunctionDeclaration进入
Identifier进入
Identifier离开
BlockStatement进入
BlockStatement离开
FunctionDeclaration离开
Program离开
ECMAScript 2015+
的代码,使它在旧的浏览器或者环境中也能够运行type
命名的方法,当遍历 AST 的时候,如果匹配上 type,就会执行对应的方法转换前
const sum = (a,b)=>{
console.log(this);
return a+b;
}
转换后
var _this = this;
const sum = function (a, b) {
console.log(_this);
return a + b;
};
npm i @babel/core babel-types -D
实现
let core = require('@babel/core');
let types = require('babel-types');
let BabelPluginTransformEs2015ArrowFunctions = require('babel-plugin-transform-es2015-arrow-functions');
const sourceCode = `
const sum = (a,b)=>{
console.log(this);
return a+b;
}
`;
//babel插件其实是一个对象,它会有一个visitor访问器
let BabelPluginTransformEs2015ArrowFunctions2 = {
//每个插件都会有自己的访问器
visitor:{
//属性就是节点的类型,babel在遍历到对应类型的节点的时候会调用此函数
ArrowFunctionExpression(nodePath){//参数是节点的数据
let node = nodePath.node;//获取 当前路径上的节点
//处理this指针的问题
hoistFunctionEnvironment(nodePath);
node.type = 'FunctionExpression';
}
}
}
function hoistFunctionEnvironment(fnPath){
const thisEnvFn = fnPath.findParent(p=>{
//是一个函数,不能是箭头函数 或者 是根节点也可以
return (p.isFunction() && !p.isArrowFunctionExpression())||p.isProgram()
});
//找一找当前作用域哪些地方用到了this的路径
let thisPaths = getScopeInfoInformation(fnPath);
//声明了一个this的别名变量,默认是_this __this
let thisBinding = '_this';
if(thisPaths.length>0){
//在thisEnvFn的作用域内添加一个变量,变量名_this,初始化的值为this
thisEnvFn.scope.push({
id:types.identifier(thisBinding),
init:types.thisExpression()
});
thisPaths.forEach(item=>{
//创建一个_this的标识符
let thisBindingRef = types.identifier(thisBinding);
//把老的路径 上的节点替换成新节点
item.replaceWith(thisBindingRef);
});
}
}
function getScopeInfoInformation(fnPath){
let thisPaths = [];
//遍历当前path所有的子节点路径,
//告诉 babel我请帮我遍历fnPath的子节点,遇到ThisExpression节点就执行函数,并且把对应的路径传进去
fnPath.traverse({
ThisExpression(thisPath){
thisPaths.push(thisPath);
}
});
return thisPaths;
}
let targetCode = core.transform(sourceCode,{
plugins:[BabelPluginTransformEs2015ArrowFunctions2]
});
console.log(targetCode.code);
es6
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
es5
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
实现
//核心库,提供了语法树的生成和遍历的功能
let babel = require('@babel/core');
//工具类,可能帮我们生成相应的节点
let t = require('babel-types');
//babel_plugin-transform-classes
let TransformClasses = require('@babel/plugin-transform-classes');
let es6Code = `class Person{
constructor(name){
this.name = name;
}
getName(){
return this.name;
}
}
`;
let transformClasses2 = {
visitor: {
ClassDeclaration(nodePath) {
let {node} = nodePath;
let id = node.id;//{type:'Identifier',name:'Person'}
console.log(id);
let methods = node.body.body;
let nodes = [];
methods.forEach(classMethod=>{
if(classMethod.kind === 'constructor'){
let constructorFunction = t.functionDeclaration(
id, classMethod.params, classMethod.body,
classMethod.generator, classMethod.async);
nodes.push(constructorFunction);
}else{
let prototypeMemberExpression = t.memberExpression(id, t.identifier('prototype'));
let keyMemberExpression = t.memberExpression(prototypeMemberExpression, classMethod.key);
let memberFunction = t.functionExpression(
id, classMethod.params, classMethod.body,
classMethod.generator, classMethod.async);
let assignmentExpression=t.assignmentExpression("=",
keyMemberExpression,
memberFunction);
nodes.push(assignmentExpression);
}
});
if(nodes.length==1){
nodePath.replaceWith(nodes[0]);
}else if(nodes.length>1){
nodePath.replaceWithMultiple(nodes);
}
}
}
}
let es5Code = babel.transform(es6Code,{
plugins:[transformClasses2]
});
console.log(es5Code.code);
var babel = require("@babel/core");
let { transform } = require("@babel/core");
import { flatten, concat } from "lodash";
转换为
import flatten from "lodash/flatten";
import concat from "lodash/flatten";
cnpm i webpack webpack-cli -D
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: path.resolve("dist"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options:{
plugins:[
[
path.resolve(__dirname,'plugins/babel-plugin-import.js'),
{
libraryName:'lodash'
}
]
]
}
},
},
],
},
};
编译顺序为首先
plugins
从左往右,然后presets
从右往左
plugins\babel-plugin-import.js
let babel = require("@babel/core");
let types = require("babel-types");
const visitor = {
ImportDeclaration: {
enter(path, state = { opts }) {
const specifiers = path.node.specifiers;
const source = path.node.source;
if (
state.opts.libraryName == source.value &&
!types.isImportDefaultSpecifier(specifiers[0])
) {
const declarations = specifiers.map((specifier, index) => {
return types.ImportDeclaration(
[types.importDefaultSpecifier(specifier.local)],
types.stringLiteral(`${source.value}/${specifier.local.name}`)
);
});
path.replaceWithMultiple(declarations);
}
},
},
};
module.exports = function (babel) {
return {
visitor,
};
};
babel-plugin-transform-runtime
后,Babel 就会使用 babel-runtime 下的工具函数