Open sunyongjian opened 7 years ago
写的不错哦
@jiangtao 谢谢大佬。
transform-runtime中的"helpers"和"moduleName"到底设置了什么,我是用webpack打包的,感觉没什么影响
@liruilong119 你试一下 helpers 为 false 和 true 的情况,最后包的大小应该不一样。开了 helpers 跟 babel-external-helpers 的效果是一样的啊。 moduleName 就是在转译的时候,用哪个包去换语法啊,不过都是用 babel-runtime 就是了,包含 core-js,regenerator,helpers 三个静态包。
@sunyongjian 我试了分别helpers 为 false 和 true ,打包后没区别,连chunkhash都没变
这是我的配置
目录结构
.
├── dist
│ └── main-c9017c04383c5e9a1924.js
├── package-lock.json
├── package.json
├── src
│ └── main.js
└── webpack.config.js
{
"presets": ["env"],
"plugins": [
["transform-runtime", {
"helpers": false,
"polyfill": true,
"regenerator": false,
"moduleName": "babel-runtime"
}]
]
}
webpack.config.js
var path = require("path");
module.exports = {
entry: {
main: "./src/main.js"
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name]-[chunkhash].js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: [path.resolve(__dirname, "node_modules")],
include: [path.resolve(__dirname, "src")],
use: ["babel-loader"]
}
]
}
};
js就写了个Promise
var p = new Promise(function (reslove, reject) {
reslove("hello");
});
p.then(function (succ) {
console.log(succ);
});
麻烦看下有什么问题
@liruilong119 promise 是用不到辅助函数的。你首先得了解,什么时候用得到 helper 函数,https://github.com/babel/babel/blob/master/packages/babel-helpers/src/helpers.js
我测试过,在另一个仓库里有,你去看看。https://github.com/sunyongjian/babel-usage/tree/master/runtime
@sunyongjian 谢谢,了解了
支持,写得真不错
@ron0115 谢谢。
您好,关于这一段 step3: 然后... 我又试了一下 env 下,使用 transform-runtime。在不加 useBuiltIns,不引入 babel-polyfill 的情况下。build.js 体积234k,执行通过。 我想请教一下这实现了新的原型和类上的静态方法吗?如果实现了,是修改了全局吗?
@RalfZhang 没有啊,使用 transform-runtime 这个插件,就是识别到你需要什么垫片,就在当前模块引入垫片,不会修改全局的。另外 polyfill 并不一定是会修改全局的,是先判断当前环境有没有,再重写。
请问可以不引入 babel-polyfill吗? 代码里面我用到了 Array的findIndex方法? 如果不引入babel-polyfill,在IE下直接报错?
@whucj 对,IE 下会直接报错,所以需要 polyfill 啊。不过如果你只用到了这一个方法,可以只对这个方法做兼容...
这里是我理解错了吗? 好像反了?
@Elainelele 感谢细心的阅读,的确是表格第一行写反了。结论是没错的~
那其实是不是有transfrom-runtime就可以不用babel-polyfill呢?
@L-Chris 从编译完的效果、目的来说,是这样的。不过数组的 includes()、filter() 等方法,babel-plugin-transform-runtime 是不会进行扩展的。如果遇到类似的问题,需要单独对 includes 打补丁
请问使用 babel-runtime的话,对于实例方法你是怎么处理的?文档说babel-runtime并不处理实例方法
感觉目前最好好的方案还是直接确定好浏览器范围,然后直接babel-polyfill + useBuiltIns 要么尝试下polyfill.io,自动根据浏览器user-agent返回polyfill,但肯定不能做到百分百正确
@L-Chris 嗯,赞同,项目中的确是这样搞的。
@liruilong119 嗯,原型上新添加的方法 babel-plugin-transform-runtime 没法处理。最好的办法就是引入 babel-polyfill 吧,并打开 env 的 useBuiltIns。
@sunyongjian 使用useBuiltIns的话,transform-runtime的polyfill、regenerator的就要关闭吧,感觉这两种polyfill方式各有优缺点,我更想使用babel-time,实例方法可以手动引入polyfill,但感觉太麻烦,不知道有没有自动化的方式
@liruilong119 babel-preset-env
打酱油的路过,忍不住点赞
请问使用 babel-runtime的话,对于实例方法你是怎么处理的?文档说babel-runtime并不处理实例方法
现在7.4版本的runtime已经支持实例方法了。指定corejs: 3
非常清晰!
好棒
引入
这个问题是对自己的发问,但我相信会有很多跟我一样的同学。 对于 babel 的使用,近半年来一直停留在与 webpack 结合使用,以及在浏览器开发环境下。导致很多 babel 的包,我都不清楚他们是干嘛的。比如 babel-register,还有 babel-runtime,各种 presets 的区别,transform-runtime 和 babel-polyfill 的区别,helpers 是干嘛的。尽管网上的 babel 的教程很多了,但是解答自己的一些疑问,还是要花费一些功夫。所以抽出时间来总结一下。如果你对于以上概念已经比较清楚了,就不需要往下看了。
本次的 example 代码都在 github 上,而且每个文件夹都有详细的 README,说明我的使用方式。可以去参照一下用例的使用,并 clone 下来自己研究一下。
版本变化
说实话,从我做前端的时候,接触 babel 的时候,就已经是 babel 6 了,但是这不妨碍了解一下它的重大版本变化。 上一个版本 babel 5 是全家桶,包括各种package, plugins,尽可能的想通过你的一次安装,达到全能的效果。不过你现在安装
npm install babel
,会得到一个 warning。babel 6 是 2015年10月30号发布,主要做了以下更新:babel-core
,babel-node
,babel-cli
...差不多了,我感觉其他的也不需要了解了。
包
babel 里面有好多的包,所以必须搞清楚他们都是干嘛的,才能让我们更好的使用这个工具。
babel-core
可以看做 babel 的编译器。babel 的核心 api 都在这里面,比如 transform,主要都是处理转码的。它会把我们的 js 代码,抽象成 ast,即 abstract syntax tree 的缩写,是源代码的抽象语法结构的树状表现形式。我们可以理解为,它定义的一种分析 js 语法的树状结构。也就是说 es6 的新语法,跟老语法是不一样的,那我们怎么去定义这个语法呢。所以必须要先转成 ast,去发现这个语法的 kind,分别做对应的处理,才能转化成 es5.
主要 api:
反转,你把 ast 传入,解析为 code 代码。
options
babel-cli
提供命令行运行 babel。也就是你可以
babel filename
去对文件转码。 安装的话使用对应就是
具体使用还是看官方文档吧,我就不搬文档了。
babel-external-helpers
babel-cli 中的一个command,用来生成一段代码,包含 babel 所有的 helper 函数。
首先我们需要了解什么是 helpers。babel 有很多帮助函数,例如 toArray函数, jsx转化函数。这些函数是 babel transform 的时候用的,都放在
babel-helpers
这个包中。如果 babe 编译的时候检测到某个文件需要这些 helpers,在编译成模块的时候,会放到模块的顶部。 像这样但是如果多个文件都需要提供,会重复引用这些 helpers,会导致每一个模块都定义一份,代码冗余。所以 babel 提供了这个命令,用于生成一个包含了所有 helpers 的 js 文件,用于直接引用。然后再通过一个 plugin,去检测全局下是否存在这个模块,存在就不需要重新定义了。
使用:
注意:示例代码的包都是装到项目中的,也就是本地。同样你可以全局安装直接执行。
这样就可以啦,还是可以减少很多代码量的。另外如果使用了 transform-runtime,就不需要生成 helpers.js 文件了,这个在后面的 babel-runtime 再说。
babel-node
也是 babel-cli 下面的一个 command,主要是实现了 node 执行脚本和命令行写代码的能力。举两个栗子就清楚了。
执行脚本
默认的node 环境肯定是不支持 jsx 的
执行 test.js,会报错,不认识这个语法。
但是使用 babel-node 就可以。
--presets react 是参数,等同于
执行正常。
node 命令行写代码
注意: 本文所有代码示例,均在 node 版本 4.8.4 下执行。
写个解构赋值的,直接运行 node,不支持。
运行
node_modules/.bin/babel-node --presets env
得到 a 的 value 是 1。
通过栗子基本已经介绍了 babel-node 的用法了,就是方便我们平常开发时候,写一些脚本的。所以它不适用于生产环境。另外,babel-node 已经内置了 polyfill,并依赖 babel-register 来编译脚本。好,那 babel-register 是什么呢
babel-register
babel-node 可以通过它编译代码,可以了解到,它其实就是一个编译器。我们同样可以在代码中引入它
require('babel-register')
,并通过 node 执行我们的代码。它的原理是通过改写 node 本身的 require,添加钩子,然后在 require 其他模块的时候,就会触发 babel 编译。也就是你引入
require('babel-register')
的文件代码,是不会被编译的。只有通过 require 引入的其他代码才会。我们是不是可以理解,babel-node 就是在内存中写入一个临时文件,在顶部引入 babel-register,然后再引入我们的脚本或者代码?举个栗子,还是 node 中执行 jsx,要通过 babel 编译。我们可以把 jsx 的代码 a.js 编译完输出到一个 b.js,然后
node b.js
也是可以执行的。但是太麻烦,不利于开发。让我们看一下通过 register 怎么用:它的特点就是实时编译,不需要输出文件,执行的时候再去编译。所以它很适用于开发。总结一下就是,多用在 node 跑程序,做实时编译用的,通常会结合其他插件作编译器使用,比如 mocha 做测试的时候。
值得一提的是,babel-register 这个包之前是在 babel-core 下面的,所以也可以
require('babel-core/register')
去引入,跟require('babel-register')
是一样的。但是,babel 的团队把 register 独立出来了,而且未来的某一天(升 7.0)会从 babel-core 中废除,所以我们现在最好还是使用 babel-register 吧。babel-core/register.jsbabel-runtime
这个包很简单,就是引用了 core-js 和 regenerator,然后生产环境把它们编译到 dist 目录下,做了映射,供使用。那么什么是 core-js 和 regenerator 呢。 首先我们要知道上面提到的 babel-core 是对语法进行 transform 的,但是它不支持 build-ints(Eg: promise,Set,Map),prototype function(Eg: array.reduce,string.trim),class static function (Eg:Array.form,Object.assgin),regenerator (Eg:generator,async)等等拓展的编译。所以才要用到 core-js 和 regenerator。
core-js
core-js 是用于 JavaScript 的组合式标准化库,它包含 es5 (e.g: object.freeze), es6的 promise,symbols, collections, iterators, typed arrays, es7+提案等等的 polyfills 实现。也就是说,它几乎包含了所有 JavaScript 最新标准的垫片。不过为什么它不把 generator 也实现了... 😁
regenerator
它是来自于 facebook 的一个库,链接。主要就是实现了 generator/yeild, async/await。
所以 babel-runtime 是单纯的实现了 core-js 和 regenerator 引入和导出,比如这里是 filter 函数的定义,做了一个中转并处理了 esModule 的兼容。
helpers
还记得提 babel-external-helpers 的时候,介绍 helpers 了吗。babel-runtime 里面的 helpers 就相当于我们上面通过 babel-external-helpers 生成的 helpers.js。只不过它把每个 helper 都单独放到一个文件夹里。这样,配合 transform-runtime 使用的时候,需要用 helper 转化的时候,就从 babel-runtime 中直接引用了。
文件结构:
使用
可以单独引入
require('babel-runtime/core-js/object/values');
不过这些模块都做了 esModule 的兼容处理,也就是上面引入的模块是
{ "default": require("core-js/library/fn/array/filter"), __esModule: true }
这样的,要使用还得加上.default
。所以我们期待的是,最好能有帮我们自动处理的插件,babel-plugin-transform-runtime
就是用来做这个的。这个我们放到 plugin 去讲。babel-polyfill
babel-runtime 已经是一堆 polyfill 了,为什么这里还有一个类似的包,它同样是引用了 core-js 和 regenerator,垫片支持是一样的。官网是这么说的,babel-polyfill 是为了模拟一个完整的ES2015 +环境,旨在用于应用程序而不是库/工具。并且使用babel-node时,这个polyfill会自动加载(这个我们在介绍 babel-node 的最后已经说了)。
也就是说,它会让我们程序的执行环境,模拟成完美支持 es6+ 的环境,毕竟无论是浏览器环境还是 node 环境对 es6+ 的支持都不一样。它是以重载全局变量 (E.g: Promise),还有原型和类上的静态方法(E.g:Array.prototype.reduce/Array.form),从而达到对 es6+ 的支持。不同于 babel-runtime 的是,babel-polyfill 是一次性引入你的项目中的,就像是 React 包一样,同项目代码一起编译到生产环境。
使用
我们结合 babel-register 去使用一下
完美运行。
注意:babel-polyfill 只是为当前环境全局下注入垫片,ES6 语法(E.g: arrow func,esModules)还是要加入 plugins 去 transform 的。
plugins
要说 plugins 就不得不提 babel 编译的过程。babel 编译分为三步:
所以 plugins 是在第二步加强转译的,所以假如我们自己写个 plugin,应该就是对 ast 结构做一个遍历,操作。
babel-plugin-transform-runtime
上面我们知道,transform-runtime 是为了方便使用 babel-runtime 的,它会分析我们的 ast 中,是否有引用 babel-rumtime 中的垫片(通过映射关系),如果有,就会在当前模块顶部插入我们需要的垫片。试一下:
另外,它还有几个配置
如果你只需要用 regenerator,不需要 core-js 里面的 polyfill 那你就可以在 options 中把 polyfill 设为 false。helpers 设为 false,就相当于没有启用
babel-plugin-external-helpers
的效果,比如翻译 async 的时候,用到了 asyncToGenerator 函数,每个文件还会重新定义一下。moduleName 的话,就是用到的库,你可以把 babel-runtime 换成其他类似的。transform-runtime 对比 babel-polyfill
其实通过上面的介绍我们已经了解他们是干什么的了,这里再稍微总结区分一下吧。我在这里把 babel-runtime 和 babel-plugin-transform-runtime 统称为 transform-runtime,因为一起用才比较好。
includes, filter, fill
等,这个算是一个关键问题吧,直接推荐用 polyfill。link另外,关于 babel-runtime 为什么是 dependencies 依赖。它只是一个集中了 polyfill 的 library,对应需要的 polyfill 都是要引入项目中,并跟项目代码一起打包的。不过它不会都引入,你用了哪个,plugin 就给你 require 哪个。所以即使你最终项目只是
require('babel-runtime/core-js/object/values')
其中的一个文件,但是对于这包来说,也是生产依赖的。注意:babel-polyfill 并不是一定会污染全局环境,在引入这个 js,并运行的时候,它会先判断当前有没有这个方法,在看要不要重写。如上图
presets
各种配置 plugin 实在是费劲,es6+ 编译要加入好多 plugins,比如为了在 node 中使用 esmodule,要把 esmodule 转化成 commomjs,使用
transform-es2015-modules-commonjs
,还有 asyncToGenerator,React jsx转化等等,不仅要装好多,还要配好多。presets 就是 plugins 的组合,你也可以理解为是套餐... 主要有
大部分的 presets 我觉得都不需要介绍了,官网上写的比较详细。而且 babel-preset-lastet 已经废弃,被 babel-preset-env 代替。
babel-preset-env
这个 preset 真是神器啊,它能根据当前的运行环境,自动确定你需要的 plugins 和 polyfills。通过各个 es标准 feature 在不同浏览器以及 node 版本的支持情况,再去维护一个 feature 跟 plugins 之间的映射关系,最终确定需要的 plugins。
preset-env 配置
详情:
主要介绍 debug 和 很好用的 useBuiltIns 吧。
debug
开启debug后,编译结果会得到使用的 targets,plugins,polyfill 等信息
useBuiltIns
env 会自动根据我们的运行环境,去判断需要什么样的 polyfill,而且,打包后的代码体积也会大大减小,但是这一切都在使用 useBuiltIns,而且需要你安装 babel-polyfill,并 import。它会启用一个插件,替换你的
import 'babel-polyfill'
,不是整个引入了,而是根据你配置的环境和个人需要单独的引入 polyfill。 我尝试了一下是否真的有效,下面是我的对比实验过程:step1: 首先是这样一段测试编译的代码,有 jsx,Object.values,async。env 的配置除了 useBuiltIns 都跟上面的配置一样。然后通过 webpack + babel-loader 打包,生成 build.js
step2: 然后通过设置不同的参数,打包,获取 build.js,并执行。得到下表
具体的过程、截图猛戳 这里
最终的结论就是,使用了 useBuiltIns 确实体积变小了,比直接
import 'babel-polyfill'
好了许多。step3: 然后... 我又试了一下 env 下,使用 transform-runtime。在不加 useBuiltIns,不引入 babel-polyfill 的情况下。build.js 体积234k,执行通过。
咦,这样好像体积更小啊。别忘了,我们的 babel-polyfill 是配置了执行环境的,通过环境看你需要哪些 polyfill。而 transform-runtime,是发现我们代码需要什么 polyfill,当然会少很多了。所以,又回到了用哪个的问题... 😓 参考上面的总结。
then
helpers 的问题。开发项目,使用 preset-env,并
import 'babel-polyfill'
,但是 helpers 好像没有地方配置。而且我试了两个文件分别用 async 函数,编译后每个模块都定义了 asyncToGenerat 函数,这种情况下我觉得最后就是自己生成一个 helpers.js 文件了。总结
现在看起来开发大点的项目,最好用的配置应该就是 preset-env,确定自己的运行环境,如果有需要,再加上 useBuiltIns,并生成一份 helpers.js 的文件。不过,一切还是看你的需求,我的前提是开发大点的“项目”,不过了解了这些东西,你会做出自己的选择。
babel 的配置
目前 babel 官方推荐是写到 .babelrc 文件下,你还可以在 package.json 里面添加 babel 字段。不用配置文件的话,可以把配置当做参数传给 babel-cli
配合其他工具
webpack
比较常用,除了 babel 自己的包,多装一个
babel-loader
配合 webpack 使用。并在 webpack.config.js 中加入 loader 的配置mocha
项目里的代码都是用 es6+ 写的,但是做单元测试的时候,测试框架并不认识你的什么 esModule,es6+ 的一些语法,mocha 是 node 程序,所以你要把 esModule 转化成 commomjs 之类的。
mocha 是支持编译器的,通过
--compilers
指定,这里我们用 babel,举个栗子因为 mocha 终究是在跑 node 程序的,适用于实时编译,所以可以用 babel-register 做编译器。
最后
总结这些东西花了我两三天的时间,虽然搞清楚了这些包是干嘛的,但是又在想到底应不应该花时间研究这些,工具始终是用来使用的,对于 babel 来说更应该研究的是它对 ast 的处理方式?不过看到自己的产出,我觉得是有必要的,另外,因为对工具更进一步的了解,才能更好的在项目中使用它们,而不是一顿照搬,代码是可以正常用了,但是可能会有很多不需要的东西,导致代码体积变大。“割鸡焉用牛刀”,我觉得还是需要有精益求精的精神。希望对你有所帮助。
我的个人博客地址 https://github.com/sunyongjian/blog ,欢迎订阅,star,谢谢。