前言
有时间学习研究了一下 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下