webpack
和Lint
等很多的工具和库的核心都是通过Abstract Syntax Tree
抽象语法树这个概念来实现对代码的检查、分析等操作的
这些工具的原理都是通过JavaScript Parser
把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作
在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。
Javascript的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解。所以需要转化为AST来使之更适合程序分析,浏览器编译器一般会把源码转化为AST来进行进一步的分析等其他操作。
JavaScript Parser,把js源码转化为抽象语法树的解析器。
浏览器会把js源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。
一般来说每个js引擎都会有自己的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。
mkdir zhufengast
cd zhufengast
cnpm i esprima estraverse escodegen- S
let esprima = require('esprima');
var estraverse = require('estraverse');
var escodegen = require("escodegen");
let code = 'function ast(){}';
let ast=esprima.parse(code);
let indent=0;
function pad() {
return ' '.repeat(indent);
}
estraverse.traverse(ast,{
enter(node) {
console.log(pad()+node.type);
if(node.type == 'FunctionDeclaration'){
node.id.name = 'ast_rename';
}
indent+=2;
},
leave(node) {
indent-=2;
console.log(pad()+node.type);
}
});
let generated = escodegen.generate(ast);
console.log(generated);
Program
FunctionDeclaration
Identifier
Identifier
BlockStatement
BlockStatement
FunctionDeclaration
Program
转换前
const sum = (a,b)=>a+b
转换后
var sum = function sum(a, b) {
return a + b;
};
npm i @babel/core babel-types -D
实现
let babel = require('@babel/core');
let t = require('babel-types');
const code = `const sum = (a,b)=>a+b`;
let transformArrowFunctions = {
visitor: {
ArrowFunctionExpression: (path) => {
let node = path.node;
let id = path.parent.id;
let params = node.params;
let body=t.blockStatement([
t.returnStatement(node.body)
]);
let functionExpression = t.functionExpression(id,params,body,false,false);
path.replaceWith(functionExpression);
}
}
}
const result = babel.transform(code, {
plugins: [transformArrowFunctions]
});
console.log(result.code);
转换前
const result = 1 + 2;
转换后
const result = 3;
let babel = require('@babel/core');
let t=require('babel-types');
let preCalculator={
visitor: {
BinaryExpression(path) {
let node=path.node;
let left=node.left;
let operator=node.operator;
let right=node.right;
if (!isNaN(left.value) && !isNaN(right.value)) {
let result=eval(left.value+operator+right.value);
path.replaceWith(t.numericLiteral(result));
if (path.parent&& path.parent.type == 'BinaryExpression') {
preCalculator.visitor.BinaryExpression.call(null,path.parentPath);
}
}
}
}
}
const result = babel.transform('const sum = 1+2+3',{
plugins:[
preCalculator
]
});
console.log(result.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');
let source=`
class Person {
constructor(name) {
this.name=name;
}
getName() {
return this.name;
}
}
`;
let ClassPlugin={
visitor: {
ClassDeclaration(path) {
let node=path.node;
let id=node.id;
let constructorFunction = t.functionDeclaration(id,[],t.blockStatement([]),false,false);
let methods=node.body.body;
let functions = [];
methods.forEach(method => {
if (method.kind == 'constructor') {
constructorFunction = t.functionDeclaration(id,method.params,method.body,false,false);
functions.push(constructorFunction);
} else {
let memberObj=t.memberExpression(t.memberExpression(id,t.identifier('prototype')),method.key);
let memberFunction = t.functionExpression(id,method.params,method.body,false,false);
let assignment = t.assignmentExpression('=',memberObj,memberFunction);
functions.push(assignment);
}
});
if (functions.length ==0) {
path.replaceWith(constructorFunction);
}else if (functions.length ==1) {
path.replaceWith(functions[0]);
} else {
path.replaceWithMultiple(functions);
}
}
}
}
const result = babel.transform(source,{
plugins:[
ClassPlugin
]
});
console.log(result.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:[['import',{library:'lodash'}]]
}
}
}
]
}
}
编译顺序为首先
plugins
从左往右,然后presets
从右往左
babel-plugin-import.js
放置在node_modules目录下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.library == 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
}
}
AST整个解析过程分为两个步骤
Javascript 代码中的语法单元主要包括以下这么几种
const
、let
、var
等let jsx = `let element=<h1>hello</h1>`;
function lexical(code) {
const tokens=[];
for (let i=0;i<code.length;i++){
let char=code.charAt(i);
if (char == '=') {
tokens.push({
type: 'operator',
value:char
});
}
if (char=='<') {
const token={
type: 'JSXElement',
value:char
}
tokens.push(token);
let isClose = false;
for (i++;i<code.length;i++){
char=code.charAt(i);
token.value+=char;
if (char=='>') {
if (isClose) {
break;
} else {
isClose=true;
}
}
}
continue;
}
if (/[a-zA-Z\$\_]/.test(char)) {
const token={
type: 'Identifier',
value:char
}
tokens.push(token);
for (i++;i<code.length;i++){
char=code.charAt(i);
if (/[a-zA-Z\$\_]/.test(char)) {
token.value+=char;
} else {
i--;
break;
}
}
continue;
}
if (/\s/.test(char)) {
const token={
type: 'whitespace',
value:char
}
tokens.push(token);
for (i++;i<code.length;i++){
char=code.charAt[i];
if (/\s/.test(char)) {
token.value+=char;
} else {
i--;
break;
}
}
continue;
}
}
return tokens;
}
let result=lexical(jsx);
console.log(result);
[
{ type: 'Identifier', value: 'let' },
{ type: 'whitespace', value: ' ' },
{ type: 'Identifier', value: 'element' },
{ type: 'operator', value: '=' },
{ type: 'JSXElement', value: '<h1>hello</h1>' }
]
// babylon7 https://astexplorer.net/
// babylon7 https://astexplorer.net/
function parse(tokens) {
const ast={
type: 'Program',
body: [],
sourceType:'script'
}
let i=0;//标示当前位置
let currentToken;//当前的符号
while ((currentToken = tokens[i])) {
if (currentToken.type == 'Identifier' && (currentToken.value == 'let'||currentToken.value == 'var')) {
const VariableDeclaration={
type: 'VariableDeclaration',
declarations:[]
}
i+=2;
currentToken=tokens[i];
let VariableDeclarator = {
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name:currentToken.value
}
};
VariableDeclaration.declarations.push(VariableDeclarator);
i+=2;
currentToken=tokens[i];
if (currentToken.type=='JSXElement') {
let value=currentToken.value;
let [,type,children]=value.match(/([^<]+?)>([^<]+)<\/\1>/);
VariableDeclarator.init={
type: 'JSXElement',
openingElement:{
type:'JSXOpeningElement',
name:{
type:'JSXIdentifier',
name:'h1'
}
},
closingElement:{
type:'JSXClosingElement',
name:{
type:'JSXIdentifier',
name:'h1'
}
},
name: type,
children:[
{
type:'JSXText',
value:'hello'
}
]
}
} else {
VariableDeclarator.init={
type: 'Literal',
value:currentToken.value
}
}
ast.body.push(VariableDeclaration);
}
i++;
}
return ast;
}
let tokens=[
{type: 'Identifier',value: 'let'},
{type: 'whitespace',value: ' '},
{type: 'Identifier',value: 'element'},
{type: 'operator',value: '='},
{type: 'JSXElement',value: '<h1>hello</h1>'}
];
let result = parse(tokens);
console.log(result);
console.log(JSON.stringify(result));
{
"type": "Program",
"body": [{
"type": "VariableDeclaration",
"declarations": [{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "element"
},
"init": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXIdentifier",
"name": "h1"
}
},
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXIdentifier",
"name": "h1"
}
},
"name": "h1",
"children": [{
"type": "JSXText",
"value": "hello"
}]
}
}]
}],
"sourceType": "script"
}
babel-plugin-transform-runtime
后,Babel 就会使用 babel-runtime 下的工具函数