带你一步一步配置Babel7

5,559 阅读7分钟

前言

有时间学习研究了一下 babel,扒了官网和搜了很多的资料,没有一个资料写了很详细的实践,都偏重于理论。故整理一篇实践操作的 babel 配置,以供学习。

babel是什么?

Babel 是一个 JS 编译器

Babel 是一个工具链,主要用于将 ECMAScript 2015+ (又可称为ES6ES7ES8等)版本的代码转换为向后兼容的 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阶段前的提议)。

这意味着可以使用诸如 PromiseWeakMap 之类的新的内置组件、 Array.fromObject.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