前言
有时间学习研究了一下 babel
,扒了官网和搜了很多的资料,没有一个资料写了很详细的实践,都偏重于理论。故整理一篇实践操作的 babel
配置,以供学习。
babel是什么?
Babel 是一个 JS 编译器
Babel 是一个工具链,主要用于将 ECMAScript 2015+
(又可称为ES6
,ES7
,ES8
等)版本的代码转换为向后兼容的 JavaScript
语法,以便能够运行在当前和旧版本的浏览器或其他环境中(如低版本的 node
环境中)。
Babel
具体做了什么
- 语法转换
- 通过
Polyfill
方式在目标环境中添加缺失的特性 (通过@babel/polyfill
模块) - 源码转换 (codemods)
通俗点
ES2015+
的语法转化(如箭头函数转成普通函数)ES2015+
新增的方法转化(如数组新增的includes
方法转化兼容低版本浏览器)
本文我们会搞懂如何配置Babel7
,以及为什么要这样配置。@babel/runtime
,@babel/polyfill
,@babel/plugin-transform-runtime
,这些是干什么的,如何使用。什么是插件
什么是预设
,怎么配置。
或许你有如下的几个疑问
@babel/cli 与 babel-cli 区别?
在网上找资料的时候,经常看到有的babel
配置的是 babel-cli
有的是配置 @babel/cli
。为什么会不一样呢?
因为babel
升级到了babel7
。原先babel6
的时候用的包都是如bable-cli
这类的。升级babel7
以后,用的包都是以 @
开头的如@babel/cli
,@babel/core
这样的包。所以有@开头的是babel7
没有@开头的是babel6
。所以安装包的时候别安装错了。
插件?
Babel
是一个编译器(输入源码 => 输出编译后的代码)。就像其他编译器一样,编译过程分为三个阶段:解析、转换和打印输出。
现在,Babel
虽然开箱即用,但是什么动作都不做。它基本上类似于 const babel = code => code;
,将代码解析之后再输出同样的代码。如果想要 Babel
做一些实际的工作,就需要为其添加插件。
通俗点就是
babel
如果没有插件,啥事也干不了,没法编译代码在低版本浏览器运行。需要插件在中间转化下,以达到我们的需求
预设?
预设(preset
)?这是啥啥啥。。。
babel
提供了一个叫做 preset
的概念,说好听点叫预设
,直白点就是插件包
的意思,意味着babel
会预先替我们做好了一系列的插件包
插件包
babel
认为程序员会用到的常用的插件包
@babel/preset-env
@babel/preset-flow
@babel/preset-react
@babel/preset-typescript
注意:除了以上的插件包,还有很多很多插件包哦。可以去
官网
查
了解了一些概念后,来点实践吧 搞起来
项目
新建项目目录xxx(我建的是babel-config
目录),进入目录命令行执行
npm init -y
生成package.json
再新建 app.js
内容如下
let func = () => { }
babel配置
@babel/cli @babel/core
Babel
自带了一个内置的 CLI
命令行工具,可通过命令行编译文件
命令行安装
npm install -D @babel/cli @babel/core
@babel/cli 是babel提供的命令行工具,主要是提供babel这个命令。 官网推荐安装在项目中,而不是安装在全局环境,因为每个项目用的babel的版本不一样。可以单独管理和升级。更主要是为了方便以后项目的迁移。
Babel
的核心功能包含在 @babel/core
模块中。看到 core
这个词了吧,意味着核心,没有它,在 babel
的世界里注定寸步难行。不安装 @babel/core
,无法使用 babel
进行编译
修改 package.json
, 在 script
里面新增
"babel": "babel app.js -o ./dist/app.js"
然后命令行运行
npm run babel
编译成功,赶紧看下,dist
目录下的 app.js
文件
let func = () => {};
这啥啥啥,怎么一点变化都没有?
这是因为 Babel
虽然开箱即用,但是什么动作也不做,如果想要 Babel
做一些实际的工作,就需要为其添加插件(plugin
)(上面说过了)
插件的使用
npm install --save @babel/plugin-transform-arrow-functions
再新建.babelrc
babel
开发者为配置文件提供了多种形式,babel7
官方推荐用babel.config.js
的形式。也可以用.babelrc
,.babelrc.js
或者放到package.json
中
{
"plugins": [
"@babel/plugin-transform-arrow-functions"
]
}
再次运行命令
npm run babel
查看dist/app.js
文件 完美,执行成功
let func = function () {};
那为什么
let
没有被转化呢?
因为我们刚引入的插件是专门用来转化箭头函数的,所以 let
并没有被转化。那如果想转化 let
就需要引
入新的插件。
那 es6
的新增的东西多了,我得一个一个引入?疯了....
当然可以不用一个一个引入。这个时候上面说的(预设 preset
)插件包就用到了。
babel
开发者早就考虑到这些,为我们提供了很多插件包。最常用的就是 @babel/preset-env
npm install --save-dev @babel/preset-env
再次运行命令
"use strict";
var func = function func() {};
转化成功
看似很完美,那我们在app.js加点代码吧
let func = () => { }
let arr = [1, 2, 4]
arr.includes(3)
再次运行命令,结果
"use strict";
var func = function func() {};
var arr = [1, 2, 4];
arr.includes(3);
includes并没有转化啊,那在低版本的浏览器不还是用不起来。
babel
只是转化了语法, es6
新增的方法 入 includes
以上安装的是转化不了的
@babel/polyfill
这个时候 @babel/polyfill
就要用到了。
@babel/polyfill
模块包括 core-js
和一个自定义的 regenerator runtime
模块,可以模拟完整的 ES2015+
环境(不包含第4阶段前的提议)。
这意味着可以使用诸如 Promise
和 WeakMap
之类的新的内置组件、 Array.from
或 Object.assign
之类的静态方法、Array.prototype.includes
之类的实例方法以及生成器函数(前提是使用了 @babel/plugin-transform-regenerator
插件)。为了添加这些功能,polyfill
将添加到全局范围和类似 String
这样的内置原型中
引用别人的解释
polyfill
我们又称垫片,见名知意,所谓垫片也就是垫平不同浏览器或者不同环境下的差异,因为有的环境支持这个函数,有的环境不支持这种函数,解决的是有与没有的问题,这个是靠单纯的@babel/preset-env
不能解决的,因为@babel/preset-env
解决的是将高版本写法转化成低版本写法的问题,因为不同环境下低版本的写法有可能不同而已。
npm install --save @babel/polyfill
然后在我们代码的最前面引入它。app.js 如下:
import '@babel/polyfill'; // 这就是@babel/polyfill的用法
let func = () => { }
let arr = [1, 2, 4]
arr.includes(3)
我们接着 执行 编译命令 npm run babel
执行结果
"use strict";
require("@babel/polyfill");
var func = function func() {};
var arr = [1, 2, 4];
arr.includes(3);
编译后的代码,我们发现好像有问题。我引入了 @babel/polyfill
为什么编译成了require,这个在node端可以用,浏览器用不起来啊?
原因
Babel
所做的只是帮你把ES6
模块化语法转化为CommonJS
模块化语法,其中的require exports
等是CommonJS
在具体实现中所提供的变量。
任何实现
CommonJS
规范的环境(如node
环境)可以直接运行这样的代码,而浏览器环境并没有实现对CommonJS
规范的支持,所以我们需要使用打包工具(bundler
)来进行打包,说的直观一点就是把所有的模块组装起来,为我们的代码做一些包裹,让它能在浏览器端使用。形成一个常规的js
文件。打包工具有 比如Browserify
,Webpack
等
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
代码了。
优化 (按需加载
)
@babel/preset-env
提供了一个 useBuiltIns
参数,设置值为 usage
时,就只会包含代码需要的 polyfill
。有一点需要注意:配置此参数的值为 usage
,必须要同时设置 corejs
(如果不设置,会给出警告,默认使用的是"corejs": 2) ,注意: 这里仍然需要安装 @babel/polyfill
(当前 @babel/polyfill
版本默认会安装 "corejs": 2):
首先说一下使用
core-js@3
的原因,core-js@2
分支中已经不会再添加新特性,新特性都会添加到core-js@3
。例如你使用了Array.prototype.flat()
,如果你使用的是core-js@2
,那么其不包含此新特性。为了可以使用更多的新特性,建议大家使用core-js@3
npm install --save core-js@3
修改 .babelrc 配置
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"> 1%",
"last 2 versions"
]
},
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
去掉代码中引入的 import '@babel/polyfill'
, 可以卸载掉 @babel/polyfill
包
npm uninstall @babel/polyfill
package.json 如下
"dependencies": {
"core-js": "^3.6.5"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"babel-loader": "^8.1.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
},
压缩编译后大小只有10.1k
减少了太多了,到现在已经很完美了。
但是
我们来修改一些源码
新建一个文件 app2.js
class BBB {
}
修改 app.js
import './app2';
let func = () => { }
let arr = [1, 2, 4]
arr.includes(3)
class AAAA {
}
这个我们再去编译(去掉webpack
的压缩打包)
查看我们 打包后的app.js
发现
编译后的代码,_classCallCheck
这个方法定义了两次。一个 js
文件就定义一次。那项目中有很多文件,岂不是定义很多次。
@babel/plugin-transform-runtime
这个时候,就是
@babel/plugin-transform-runtime
插件大显身手的时候了,使用@babel/plugin-transform-runtime
插件,所有帮助程序都将引用模块@babel/runtime
,这样就可以避免编译后的代码中出现重复的帮助程序,有效减少包体积
@babel/plugin-transform-runtime
是一个可以重复使用Babel
注入的帮助程序,以节省代码大小的插件。
@babel/plugin-transform-runtime
需要和@babel/runtime
配合使用
首先安装依赖,@babel/plugin-transform-runtime
通常仅在开发时使用,但是运行时最终代码需要依赖 @babel/runtime
,所以 @babel/runtime
必须要作为生产依赖被安装,如下 :
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
修改.babelrc
配置文件
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime"
]
]
}
运行打包命令
已经作为公共方法提出来了。很棒。
总体配置
webpack配置
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/',
}
]
}
}
.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"> 1%",
"last 2 versions"
]
},
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
package.json
{
"name": "es8",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "webpack",
"babel": "babel app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"babel-loader": "^8.1.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
},
"dependencies": {
"@babel/runtime": "^7.9.2",
"core-js": "^3.6.5"
}
}
app.js
import './app2';
let func = () => { }
let arr = [1, 2, 4]
arr.includes(3)
class AAAA {
}
app2.js
class BBB {
}
总结
以上,是我对babel的配置的理解和配置,文中难免有很多问题,如发现请指出,谢谢。
参考文章(真的很棒)
附完整配置 git
地址:
github地址可以直接下载,喜欢帮忙
star
下