babel理解

256 阅读7分钟

babel基本概念

  • babel是把es5的代码转换向后兼容的浏览器可以解析的JavaScript代码

功能

  • 语法转换, 通过polyfill方式在目标环境里面添加缺失的特性(core.js),源码转换

工作原理

  • ES6代码输入 ==》 babylon进行解析 ==》 得到AST ==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树 ==》 用babel-generator通过AST树生成ES5代码

babel.png

  • 解析过程 : 其实本质就是通过词法分析生成令牌流(token),然后再通过语法分析把令牌流(token)生成AST

  • 转换过程: 其实就是深度优先遍历,对节点进行增加,删除,更新等操作

  • 生成过程: 其实就是通过深度优先遍历原则,将我们的AST树转换成代码字符串,生成es5代码

  • 内部实现原理可以参考: juejin.cn/post/684490…

AST

ast.png

Babylon

“Babylon 是 Babel的解析器。最初是从Acorn项目fork出来的。Acorn非常快,易于使用,并且针对非标准特性(以及那些未来的标准特性) 设计了一个基于插件的架构。”。这里直接引用了手册里的说明,可以说Babylon定义了把代码解析成AST的一套规范。引用一个例子:

import * as babylon from "babylon"; const code = `function square(n) { return n * n; }`; babylon.parse(code);
// Node { // type: "File", // start: 0, // end: 38, // loc: SourceLocation {...}, // program: Node {...}, // comments: [], // tokens: [...] // }

babel-traverse

babel-traverse用于维护操作AST的状态,定义了更新、添加和移除节点的操作方法。之前也说到,path参数里面的属性和方法都是在babel-traverse里面定义的。

在项目中实际使用

  • 实际开发中涉及到编译就两种: 其一就是关于let const class 箭头函数这些在构建层面的编译,转换成我们es5方面的,其二就是关于Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义

构建依赖包

  1. babel-core
  • Babel 的核心,包含各个核心的 API,供 Babel 插件和打包工具使用
  1. babel-cli
  • 命令行对 js 文件进行换码的工具
  1. babel-node
  • 命令行 REPL,支持 ES6 的 js 执行环境
  1. babel-register
  • Babel 的一个注册器,它在底层改写了 node 的require方法,所有通过require并以.es6、.es、.jsx和.js为后缀引入的模块都会经过 Babel 的转译

基础依赖库

  1. core-js 标准库:提供 ES5、ES6 的 polyfills,包括 promises 、symbols、collections、iterators、typed arrays、ECMAScript 7+ proposals、setImmediate 等等

  2. regenerator 运行时库:实现 ES6/ES7 中 generators、yield、async 及 await 等相关的 polyfills

插件

@babel/preset-env

  • 是一个灵活的预设,你可以无需管理目标环境需要的语法转换或浏览器polyfill,就可以使用最新的 JavaScript。这将让你的生活更简单,也会让 JavaScript 打包文件更小。

  • 注意@babel/preset-env不支持stage-x的插件。

参考: blog.windstone.cc/es6/babel/@…

babel-polyfill

  • 让新的内置函数、实例方法等在低版本浏览器中也可以使用。
//编译前

const p = new Promise((resolve, reject) => {

resolve(100);

});

//编译后

var p = new Promise(function (resolve, reject) {

resolve(100);

});

//实际是可以在低版浏览器里面运行,但是其实promise还是没有编译成功的

  • useBuiltIns属性
  1. false: 不对polyfills做任何操作

  2. entry: 根据target中浏览器版本的支持,将polyfills拆分引入,仅引入有浏览器不支持的polyfill

  3. usage(新):检测代码中ES6/7/8等的使用情况,仅仅加载代码中用到的polyfills


//.babelrc

{

"presets": [

["@babel/preset-env",{

"useBuiltIns": "usage"

}]

]

}

  • 在配置以后需要安装core.js@3


//.babelrc

{

"presets": [

["@babel/preset-env",{

"useBuiltIns": "usage",

"corejs":3

}]

]

}

  • 编译结果如下
"use strict";

require("core-js/modules/es.promise");

\


var p = new Promise(function (resolve, reject) {

resolve(100);

});
  • 问题: 每次引入都会有_classCallCheck这个,导致代码冗余
require("core-js/modules/es.function.name");

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Student = function Student(name, age) {

_classCallCheck(this, Student);

this.name = name;

this.age = age;

};

安装@babel/plugin-transform-runtime解决代码冗余

  • 安装: npm i --save-dev @babel/plugin-transform-runtime @babel/runtime
"use strict";

require("core-js/modules/es.function.name");


var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));


var Student = function Student(name, age) {

(0, _classCallCheck2["default"])(this, Student);

this.name = name;

this.age = age;

};

  • 此时还存在一个问题: 因为bolyfill是会以类似 Array.prototype.includes() 的方式去注入污染原型,造成全局对象的污染(preset-env在处理例如Promise这种的api时,只是引入了core-js中的相关的js库,这些库重新定义了Promise,然后将其挂载到了全局),如果不把core.js引入,每个文件中都会出现封装的,会造成一个代码冗余

  • 解决方案: 将core-js交给transform-runtime处理。添加一个配置即可

  • 修改配置

{

"presets": [

["@babel/preset-env",{

"useBuiltIns":"usage"

}]

],

"plugins": [

["@babel/plugin-transform-runtime",{

"corejs":3

}]

]

}

  • 注意: corejs: 2仅支持全局变量(例如Promise)和静态属性(例如Array.from),corejs: 3还支持实例属性(例如[].includes)。
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise");


new _promise["default"](function (resolve, reject) {

resolve(100);

})
  • @babel/plugin-transform-runtime缺点: 每个特性都会经历检测和替换,随着应用增大,可能会造成转译效率不高

配置babel.config.js/ .babelrc

preset

基本知识

  • 是对 Babel 构建时的配置,而 Babel 构建时只转换新语法,而不转换新的内建对象或者新的 API。

  • 目前包括 env, react, flow, minify 等,主要是env

  • stage-x,这里面包含的都是当年最新规范的草案,每年更新。

这里面还细分为

  1. Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。

  2. Stage 1 - 提案: 初步尝试。

  3. Stage 2 - 初稿: 完成初步规范。

  4. Stage 3 - 候选: 完成规范和浏览器初步实现。

  5. Stage 4 - 完成: 将被添加到下一年度发布。

执行顺序

很简单的几条原则:

Plugin 会运行在 Preset 之前。

Plugin 会从前到后顺序执行。

Preset 的顺序则 刚好相反(从后向前)。

preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是 ['es2015', 'stage-0']。这样必须先执行 stage-0 才能确保 babel 不报错。因此我们编排 preset 的时候,也要注意顺序,其实只要按照规范的时间顺序列出即可。

配置共有三种方式:

  • 在package.json中设置babel字段。

  • .babelrc文件或babel.config.js文件。

  • webpack里面配置

  1. 在package.json中设置babel字段。

{

"name":"babel-test",

"version":"1.0.0",

"devDependencies": {

"@babel/core":"^7.4.5",

"@babel/cli":"^7.4.4",

"@babel/preset-env":"^7.4.5"

// ...

},

"babel": {

"presets": ["@babel/preset-env"]

}

}

  1. .babelrc文件或.babelrc.js文件。

{

"presets": [

// "@babel/env",

// {

// targets: {

// edge: "17",

// edge: "10",

// firefox: "60",

// chrome: "67",

// safari: "11.1",

// },

// useBuiltIns: "usage"

// },

["@babel/preset-env"]

],

"plugins": [

["@babel/plugin-transform-runtime",{

"corejs":2

}]

]

}

  1. webpack里面配置

让我们来安装webpack

npm install --save-dev webpack webpack-cli babel-loader

修改 package.json 配置 新增 "dev": "webpack"

"scripts": {
    "babel": "babel app.js -o ./dist/app.js",
    "dev": "webpack"
}

新增 webpack.config.js

const path = require('path');

module.exports = {
    // 模式为生产模式
    mode: 'production',
    entry: {
        app: './app.js'
    },
    // 打包后的文件
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    // 关闭webpack 自动压缩 混淆 代码
    optimization: {
        minimize: false, // <---- 禁用 uglify.
    },
    module: {
        rules: [
            {
                // 所有的js 都进过 babel-loader 处理
                test: /.js$/,
                use: 'babel-loader',
                exclude: '/node_modules/',
            }
        ]
    }
}

运行新的编译命令 npm run dev 编译成功,可以在游览器端正常运行了。但是发现没有压缩后的代码有259k

开启压缩也有 88.9k

我只写了几行代码,怎么编译成了这么大的js。 因为 @babel/polyfill 是垫片(理解:每个游览器对 es6 方法支持不一样。就跟路上有凹槽一样,垫片就是给你把路的凹槽给填平),会把所有你代码中用到的 es6 方法引入,没有用到的也引入。

好吧,这样也能用。可以愉快的去写 es6 代码了。

env

env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。例如目标浏览器支持 es2015,那么 es2015 这个 preset 其实是不需要的,于是代码就可以小一点(一般转化后的代码总是更长),构建时间也可以缩短一些。

plugins

  • 插件和 preset 都可以接受参数,参数由插件名和参数对象组成一个数组。preset 设置参数也是这种格式。

  • 插件的短名称

如果插件名称为 @babel/plugin-XXX,可以使用短名称@babel/XXX :


{

"plugins": [

"@babel/transform-arrow-functions" //同 "@babel/plugin-transform-arrow-functions"

]}

如果插件名称为 babel-plugin-XXX,可以使用短名称 XXX,该规则同样适用于带有 scope 的插件:


{    "plugins": [      
"newPlugin"//同 "babel-plugin-newPlugin"       
"@scp/myPlugin" //同 "@scp/babel-plugin-myPlugin"    
]}

babel7.0

官方中文文档: www.babeljs.cn

参考文档: juejin.cn/post/684490…