babel基本概念
- babel是把es5的代码转换向后兼容的浏览器可以解析的JavaScript代码
功能
- 语法转换, 通过polyfill方式在目标环境里面添加缺失的特性(core.js),源码转换
工作原理
- ES6代码输入 ==》 babylon进行解析 ==》 得到AST ==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树 ==》 用babel-generator通过AST树生成ES5代码
-
解析过程 : 其实本质就是通过词法分析生成令牌流(token),然后再通过语法分析把令牌流(token)生成AST
-
转换过程: 其实就是深度优先遍历,对节点进行增加,删除,更新等操作
-
生成过程: 其实就是通过深度优先遍历原则,将我们的AST树转换成代码字符串,生成es5代码
-
内部实现原理可以参考: juejin.cn/post/684490…
AST
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的方式重新定义
构建依赖包
- babel-core
- Babel 的核心,包含各个核心的 API,供 Babel 插件和打包工具使用
- babel-cli
- 命令行对 js 文件进行换码的工具
- babel-node
- 命令行 REPL,支持 ES6 的 js 执行环境
- babel-register
- Babel 的一个注册器,它在底层改写了 node 的require方法,所有通过require并以.es6、.es、.jsx和.js为后缀引入的模块都会经过 Babel 的转译
基础依赖库
-
core-js 标准库:提供 ES5、ES6 的 polyfills,包括 promises 、symbols、collections、iterators、typed arrays、ECMAScript 7+ proposals、setImmediate 等等
-
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属性
-
false: 不对polyfills做任何操作
-
entry: 根据target中浏览器版本的支持,将polyfills拆分引入,仅引入有浏览器不支持的polyfill
-
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,这里面包含的都是当年最新规范的草案,每年更新。
这里面还细分为
-
Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
-
Stage 1 - 提案: 初步尝试。
-
Stage 2 - 初稿: 完成初步规范。
-
Stage 3 - 候选: 完成规范和浏览器初步实现。
-
Stage 4 - 完成: 将被添加到下一年度发布。
执行顺序
很简单的几条原则:
Plugin 会运行在 Preset 之前。
Plugin 会从前到后顺序执行。
Preset 的顺序则 刚好相反(从后向前)。
preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是 ['es2015', 'stage-0']。这样必须先执行 stage-0 才能确保 babel 不报错。因此我们编排 preset 的时候,也要注意顺序,其实只要按照规范的时间顺序列出即可。
配置共有三种方式:
-
在package.json中设置babel字段。
-
.babelrc文件或babel.config.js文件。
-
webpack里面配置
- 在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"]
}
}
- .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
}]
]
}
- 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…