babel归纳总结

github: babel归纳总结
在前端的发展过程中,javascript的兼容性,一直是前端头痛的问题,在以前的一些有些项目中,为解决浏览器兼容而花费的时间甚至还要多余实际的业务逻辑开发时间,babel就是其中处理兼容的转译工具(或者叫平台)。
babel是什么
javascript在不断发展,新的提案标准每年都会有,在得到广泛普及之前,Babel 把用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本
babel的编译过程分为3步,解析(parse),转换(transform),生成(generate),对应的三个插件分别是Babylon、babel-traverse、babel-generator。babylon将源码转换为抽象语法树(AST);babel-traverse通过AST生成一个便于操作、转换的path对象,供我们的babel插件处理;babel-generator读取AST并将其转换为代码和源码映射。这些过程不是本文的关注点,我们关注的是结果,哪些插件与我们的生产息息相关,我们如何去使用babel的插件。
通过vue中的babel配置来了解babel
vue脚手架生成的项目在.babelrc文件中的配置:
{
“presets”: [
[“env”, {
“modules”: false,
“targets”: {
“browsers”: [“> 1%”, “last 2 versions”, “not ie <= 8”]
}
}],
“stage-2”
],
“plugins”: [“transform-vue-jsx”, “transform-runtime”]
}
plugin配置项
babel插件推崇的是功能的单一性,就是每个插件的功能尽可能的单一,比如我要使用es6的箭头函数,那就可以装一个转换插件npm i -D @babel/plugin-transform-arrow-functions,将其写进.babelrc文件里就行了:详情
{
“presets”: [],
“plugins”: [“@babel/plugin-transform-arrow-functions”]
}
这样,我们写的:
(a) => […a]
会被该插件转换为:
function (a) {
return […a]
}
这个插件也只解决箭头函数的转换,就算函数内部用了其它新语法也不管,这个好处是很明显的,就跟咱写项目推崇组件的细腻度一个道理
presets配置项
然而呢,js发展有点快,想一下那个es2015(es6)一下加了多少东西,我们要使用还得一个一个的npm i -D xxx,这个有点小麻烦,所以就可以采用presets配置项。npm i -D babel-preset-es2015,然后配置.babelrc。详情
为了承接上文,这里暂时先用babel6的写法,babel7里也可以用babel-preset-es2015,但是文档里去掉了,es2015、es2016、es2017(2018年的东西直接写在env里了,7月份2019年的新标准就要来罗@_@)等都被放在env里面了,以后这几个preset会不会砍掉就不知道咯
{
“presets”: [“es2015”],
“plugins”: []
}
这样我们就可以使用包括箭头函数在内的es6的新语法而不用去担心兼容问题。这下这两个的关系也就清晰了,presets里面配置的是一些plugins集合
在babel 7.3.0里面,presets — 对应插件有这些:

env — @babel/preset-env

stage-0 — @babel/preset-stage-0

stage-1 — @babel/preset-stage-1

stage-2 — @babel/preset-stage-2

stage-3 — @babel/preset-stage-3

flow — @babel/preset-flow

react — @babel/preset-react

minify — babel-preset-minify

typescript — @babel/preset-typescript

env
在presets配置里面,我们看到了:
[“env”, {
“modules”: false,
“targets”: {
“browsers”: [“> 1%”, “last 2 versions”, “not ie <= 8″]
}
}]
这个env是@babel/preset-env这个集合插件配置项,这里的配置项:

modules:”amd” | “umd” | “systemjs” | “commonjs” | “cjs” | “auto” | false, defaults to “auto”.
意思就是让babel把es6 模块转化为其它模块化类型。如果选择 false 就不进行模块化转,我们的目标是浏览器,es6以前js是没有模块化的,commonjs、amd等只是社区方案,没有浏览器支持的,所以我们设置为false,如果我们写node上运行的代码,就要设置为”commonjs”

target:就是告诉babel你的js要兼容哪些环境,它会帮你将你写的js转译成目标环境兼容的js语法,这个具体配置可以看browserslist

那就是说,js无论用什么新玩意,@babel/preset-env都能跟我兼容到我想要的环境?带着问题,我们再看看官网的介绍,What is Babel:

Transform syntax(转换语法)
Polyfill features that are missing in your target environment (Ployfill新特性–也就是Api)
Source code transformations(源码转换)
And more!

所以呢,我们的.babelrc文件里面配置的只是转换语法的,所以,对于上面的问题答案是“js无论用什么新语法,@babel/preset-env都能帮你兼容到目标环境”。比如:
a => a
// 转为
function (a) {return a}
至于第二点,api的polyfill,我们在下面介绍
@babel/preset-env具体可以帮我们转化哪些呢?看这儿JavaScript新特性和Babel插件的映射关系,这个是@babel/preset-env集合插件所包含的插件列表,每个插件对应转换一个新特性,至于没有的,比如promise,请往下看。
stage-2
在上面的配置中,我们看到env下面有个stage-2。stage-x,这里面包含的都是当年最新规范的草案,每年更新。细分为如下几步

Stage 0 – 稻草人: 只是一个想法,经过 TC39 成员提出即可。
Stage 1 – 提案: 初步尝试。
Stage 2 – 初稿: 完成初步规范。
Stage 3 – 候选: 完成规范和浏览器初步实现。
Stage 4 – 完成: 将被添加到下一年度发布。

官网里有一句话It is important to note that @babel/preset-env does not support stage-x plugins.,就是说@babel/preset-env中不包含在草案阶段的新属性
其实我们通过plugin-features,以及proposals/finished-proposals(其中2019就是今年的stage-4),可以发现@babel/preset-env是包含了stage-4阶段的plugins的。
比如写react的同学比较熟悉的decorators目前就处于stage-2阶段,我们要用这些处于草案阶段的新属性,可以安装npm i -D @babel/preset-stage-2,然后在presets里写上stage-2,babel就会通过那些处于草案阶段的新属性的插件将我们代码中的用到的新属性转译成为es5
此外,低一级的 stage 会包含所有高级 stage 的内容,例如 stage-2 会包含 stage-2, stage-3 的所有内容。
babel-ployfill
Babel 几乎可以编译所有时新的 JavaScript 语法,但对于 APIs 来说却并非如此。比如说:Promise、WeakMap、Array.from 、Object.assign、Array.prototype.includes、generator等。为了解决这个问题,我们使用一种叫做 Polyfill(代码填充,也可译作兼容性补丁) 的技术。 简单地说,polyfill 即是在当前运行环境中用来复制(意指模拟性的复制,而不是拷贝)尚不存在的原生 api 的代码。 能让你提前使用还不可用的 APIs。
引入它很简单,我们npm i -S @babel/polyfill,

在vue中的入口文件main.js文件的最上面:
import “@babel/polyfill”;

或者在webpack入口里引入:
module.exports = {
entry: [“@babel/polyfill”, “./main.js”],
};

两者任选其一
上面这两种方式是将整个polyfill都引入了,很多代码其实对我们是没有用的,比如,我们的env配置的是不需要兼容ie9以下的浏览器,这种引入方式把所有兼容ie的代码都引入了,包含ie8以下,所以,一般我们会在.babelrc文件里的env里配置下useBuiltIns参数为true,这样babel在引入的时候会根据我们env环境去加载相应的polyfill:详细
// .babelrc
{
[“env”, {
“modules”: false,
“targets”: {
“browsers”: [“> 1%”, “last 2 versions”, “not ie <= 8”]
},
// 是否自动引入polyfill,开启此选项必须保证已经安装了babel-polyfill
// 在这里设置自动引入后,babel会根据你配置的兼容的环境,去按需加载polyfill的代码,这样能保证代码量最少
// 参数:Boolean,默认为false.
“useBuiltIns”: true
}]
}
@babel/plugin-transform-runtime
我们看到上面的配置中有个transform-runtime,这个是配置@babel/plugin-transform-runtime,它是做什么的呢?官网说:一个插件,通过重复使用babel注入的助手(helper)代码,来减少代码体积,我们看看它是如何工作的。
npm i -D @babel/plugin-transform-runtime

// .babelrc
{
“plugins”: [
“@babel/plugin-transform-runtime”,
// 默认配置
{
“absoluteRuntime”: false,
“corejs”: false,
“helpers”: true,
“regenerator”: true,
“useESModules”: false
}
]
}
比如这个es6的class类:
class Person {
}
在没有使用transform-runtime时,每个使用class函数处,Babel 会生成class的helper函数放置在文件顶部,就是说多个文件使用了class, babel就会在每个文件里面生成一个相同的helper:
“use strict”;

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

var Person = function Person() {
_classCallCheck(this, Person);
};
这样不必要的重复会使我们的代码体积非常雍肿,transform-runtime就是来解决这个重复生成helper的问题的,它会将这个es6的class语法的转译函数放在babel-runtime/helpers/classCallCheck里,然后在使用处通过require引入,这样将转译函数放在一起,就可以通过webpack将其打包成一个文件,浏览器加载一次就行了。
“use strict”;

var _classCallCheck2 = require(“babel-runtime/helpers/classCallCheck”);

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var Person = function Person() {
(0, _classCallCheck3.default)(this, Person);
};
@babel/plugin-transform-runtime && @babel/runtime 对比 babel-polyfill
官方推荐我们将@babel/plugin-transform-runtime 和 @babel/runtime结合使用,有必要吗?按照官网的原话,这个@babel/runtime就是个以模块化方式包含函数实现的包,我们从名字上来看,plugin-transform-runtime就是通过插件将我们的新语法转换polyfill包,而runtime就是个现成的polyfill库。有点绕了,直接给结果:在我们开发前端项目中,不需要@babel/runtime,它作用在node项目中。
tranform-runtime和babel-polyfill的区别

babel-polyfill 是当前环境注入这些 es6+ 标准的垫片,好处是引用一次,不再担心兼容,而且它就是全局下的包,代码的任何地方都可以使用。缺点也很明显,它会污染原生的一些方法,polyfill 把原生的方法重写了,如果当前项目已经有一个 polyfill 的包了,那你只能保留其一。而且一次性引入这么一个包,会大大增加体积。如果你只是用几个特性,就没必要了,如果你是开发较大的应用,而且会频繁使用新特性并考虑兼容,那就直接引入吧。
transform-runtime 是利用 plugin 自动识别并替换代码中的新特性,你不需要再引入,只需要装好 babel-runtime 和 配好 plugin 就可以了。好处是按需替换,检测到你需要哪个,就引入哪个 polyfill,如果只用了一部分,打包完的文件体积对比 babel-polyfill 会小很多。而且 transform-runtime 不会污染原生的对象,方法,也不会对其他 polyfill 产生影响。所以 transform-runtime 的方式更适合开发工具包,库,一方面是体积够小,另一方面是用户(开发者)不会因为引用了我们的工具,包而污染了全局的原生方法,产生副作用,还是应该留给用户自己去选择。缺点是随着应用的增大,相同的 polyfill 每个模块都要做重复的工作(检测,替换),虽然 polyfill 只是引用,编译效率不够高效。另外,instance 上新添加的一些方法,babel-plugin-transform-runtime 是没有做处理的,比如 数组的 includes, filter, fill 等,这个算是一个关键问题吧,直接推荐用 polyfill

几个常用的babel插件
babel-cil
Babel 的 CLI 是一种在命令行下使用 Babel 编译文件的简单方法。有时候我们只是写一个插件,需要用babel转一下我们代码中的高阶语法,因为项目可能不太大,用不到构建工具,就可以用babel-cil。转换依据我们的.babelrc文件或者package.json中babel选项

编译一个文件
babel my-file.js

如果我们想要把一个目录整个编译成一个新的目录,可以使用 –out-dir 或者 -d。.
$ babel src –out-dir lib
# 或
$ babel src -d lib

babel-loader
babel-loader是什么呢?前面说了,我们可以通过babel-cil在命令行里告诉babel转译哪些js,也可以通过babel-register,在代码里通过require来转,但是,现在前端开发是一个工程化过程,依赖关系比较复杂,在一个稍微大点儿的项目中还真没法手动告诉babel要处理哪些文件,比如一个.vue文件,里面还包含html、css,还有一些不是js的鬼语法,这时候就要借助其它插件先提前处理下,所以,webpack根据依赖关系,加载文件的时候遇到js文件后,会将文件内容的js字符串根据loader配置的先后顺序,挨个儿传递给它们处理,babel-loader就是其中之一
总结
什么是babel
babel就是将目标环境(浏览器)通过打补丁升级成支持最新javascript语法的环境。
用vue脚手架生成的项目,js怎么兼容到ie9
// .babelrc
{
“presets”: [
[“env”, {
// 这里默认是false,不用再写一遍
– // “modules”: false,
// 一般不单独写出来,babel/preset-env会自个读取package里面的browserslist,与css兼容环境保持一致
// https://github.com/browserslist/browserslist
– // “targets”: {
– // “browsers”: [“> 1%”, “last 2 versions”, “not ie <= 8”]
– // },
+ “useBuiltIns”: true
}],
“stage-2”
],
“plugins”: [“transform-vue-jsx”, “transform-runtime”]
}

// webpack.base.conf.js
module.exports = {
entry: [“@babel/polyfill”, “./main.js”],
};
插件快照

名称
作用
备注

babel/cli
允许命令行使用 babel 命令转译文件
一般在写插件时使用

babel/polyfill
为所有 API 增加兼容方法
需要在所有代码之前 require,且体积比较大

babel/plugin-transform-runtime
把帮助类方法从每次使用前定义改为统一 require,精简代码

babel/runtime
helper库
需要安装为依赖,而不是开发依赖,node环境使用,web环境不需要

babel/loader
babel插件在webpack项目中的一个入口

babel/core
babel的polyfill库

babel/preset-env
babel预制环境的集合插件,通过配置目标环境,转换标准上的新特性
只转新特性,不转api

babel/preset-stage-2
转换草案stage-2以及stage-3阶段的的新属性

参考:

babel
Babel 用户手册
Babel 插件手册
babel-vs-babel-core-vs-babel-loader
Babel 入门教程
一口(很长的)气了解 babel
tc39草案
你真的会用 babel 吗?
babel-preset-env/data/plugin-features.js
babel-plugin-transform-runtime 和 babel-runtime 的区别?

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理