Description
了解Babel 6
生态
现在写一个babel的简介好像已经不太必要了(太晚了😄 )。但大多数情况下,会配置babel
来编译代码,不代表我们清楚babel
的概念,而且Babel 6
相关的多个babel-xx
包还是容易让人混淆的。所以这里还是希望帮助理清整个Babel 6
生态。
参考:
Babel 6
的核心特性(相比5的巨大变化)
我刚开始用babel
的时候,版本是5,一个月后Babel 6
发布——变化简直天翻地覆。
相比前一版本,Babel 6
最大的变化是更模块化,各种内置库都被分散到独立的模块;其次,让所有插件可选,这意味着Babel默认不会编译ES2015代码,并且所有的transformer完全独立;同时,为了减少配置的复杂性,引入了preset;最后,提升了性能。
下面列出一些Babel 6
的核心模块/变化:
-
babel
package被弃用。我们可以看babel@6.x的源码,两个提示很明显:
- babel/index.js::node API 被转移到
babel-core
。 - babel/src/cli.js: cli 被转移到
babel-cli
。
- babel/index.js::node API 被转移到
-
babel-core
是babel的core compiler,主要用来对你的源码跑一系列变换(transform)。但默认情况下,不会应用任何变换——你必须自己安装和注册这些变换。 -
babel-cli
是babel的command line,有babel
/babel-external-helpers
/babel-node
3个命令。babel-doctor
已被移除,见Moving babel-doctor to a separate package/removing babel/babel#4678。babel
即用于编译代码。babel-external-helpers
用于生成一段js代码(里面是一些helper函数)。这些helper如果被用到,一般被置于生成代码顶部(公用),所以生成的代码不会有内连这些helper好几遍。但是如果你有多个文件的话,你可能又要重复这些helper好几遍了。所以你可以生成这样一份代码,然后在每个文件中直接引入(node通过require
,browser通过<script>
)。详情见external-helpers。babel-node
是方便开发的node二进制(非生产使用),内置了babel-polyfill
,并用babel-register
来编译被require的模块。
-
babel-register
,require hook
,替换了node的require
。The require hook will bind itself to node's require and automatically compile files on the fly.
- 如果模块是内置模块或者是
node_modules
内的模块,则使用node的require
。 - 否则使用babel的
require
,自动编译模块。
- 如果模块是内置模块或者是
Babel 6
的plugins
详情见https://babeljs.io/docs/plugins/。
这里不多说,只简单说两点:
- Babel引入了preset的概念,preset其实是一组
plugins
。 - 我们常用的
babel-preset-es2015
包括了完整的ES2015
特性,引入它即可编译ES2015
代码到ES5
。
用transform还是polyfill实现?
babel-core
仅仅聚焦于code transform,所以不是什么事都可以用babel
来转换的。
比如,检索上面的plugins列表,你会发现没有一个plugin用来转换Promise
;事实上,如果环境不支持Promise
,你应该自己引入相应polyfill。
那么什么时候应该用tranform,什么时候该用polyfill呢?如果一个新特性你可以用ES5
实现,那么,你应该用polyfill,比如Array.from
。否则,你应该用transform,比如箭头函数。
babel-polyfill
vs babel-runtime
这可能是babel中最让人误解的一组概念:当你需要支持ES2015
的所有特性时,究竟用babel-polyfill
还是 babel-runtime
?
babel-polyfill
和 babel-runtime
是达成同一种功能(模拟ES2015
环境,包括global keywords
,prototype methods
,都基于core-js
提供的一组polyfill和一个generator runtime
)的两种方式:
-
babel-polyfill
通过向全局对象和内置对象的prototype上添加方法来达成目的。这意味着你一旦引入babel-polyfill
,像Map
,Array.prototype.find
这些就已经存在了——全局空间被污染。 -
babel-runtime
不会污染全局空间和内置对象原型。事实上babel-runtime
是一个模块,你可以把它作为依赖来达成ES2015
的支持。比如当前环境不支持
Promise
,你可以通过require(‘babel-runtime/core-js/promise’)
来获取Promise
。这很有用但不方便。幸运的是,babel-runtime
并不是设计来直接使用的——它是和babel-plugin-transform-runtime
一起使用的。babel-plugin-transform-runtime
会自动重写你使用Promise
的代码,转换为使用babel-runtime
导出(export)的Promise-like
对象。注意: 所以
plugin-transform-runtime
一般用于开发(devDependencies),而runtime
自身用于部署的代码(dependencies),两者配合来一起工作。
那么我们什么时候用babel-polyfill
,什么时候用babel-runtime
?
babel-polyfill
会污染全局空间,并可能导致不同版本间的冲突,而babel-runtime
不会。从这点看应该用babel-runtime
。- 但记住,
babel-runtime
有个缺点,它不模拟实例方法,即内置对象原型上的方法,所以类似Array.prototype.find
,你通过babel-runtime
是无法使用的。 - 最后,请不要一次引入全部的polyfills(如
require('babel-polyfill')
),这会导致代码量很大。请按需引用最好。
Activity
mengxingshike2012 commentedon Feb 19, 2017
额,好像现在是习惯性的引入babel-polyfill; 其实关于babel-core这个库,如果使用webpack的话,自然会有babel-loader,但是现在loader又不依赖babel-core,是不是一般都不用加入项目的依赖了
creeperyang commentedon Feb 20, 2017
@mengxingshike2012
babel-loader
的 peerDependencies:lgh06 commentedon Mar 7, 2017
我只用了这三个 babel-preset-env + babel-polyfill + whatwg-fetch 。
babel-preset-env很智能的。各种transform傻瓜化配置了。useBuiltIns选项对polyfill也做了筛选,不用引入全部的polyfill
creeperyang commentedon Mar 7, 2017
@lgh06 才知道
babel-preset-env
👍是个不错的选择,不过看起来它是依赖指定node版本或者browser版本来决定引入哪些polyfill的;如果不用内置对象原型上的方法,
babel-runtime
可能更好一点。lgh06 commentedon Mar 7, 2017
太喜欢Array.from、forEach、Promise,没办法不污染原型了…… 新浏览器里面都内置了,也不能算污染……
For-me commentedon Jul 19, 2017
有个小问题。比如你引用了一些第三方库,直接require是使用的编译后的文件。但是如果第三方库作者使用了es6并且没有使用相应的polyfill来转换类似于for(item of list) 这种语法转换的时候会变成Symbol。造成某一些环境下对其不支持的错误。这应该是开发者本人来引入polyfill解决还是第三方作者在转换的时候就应该引入polyfill转换?还是说是babel本身的一些问题
creeperyang commentedon Jul 19, 2017
@For-me 我觉得是库作者的责任,他应该提供转换后的可用库文件,或者明确告知使用者必须自己转换。
xwenliang commentedon Dec 19, 2017
为什么vue-cli生成的项目里,同时使用了
transform-runtime
和babel-preset-env
呢.babelrc:
creeperyang commentedon Dec 19, 2017
@xwenliang 很好的问题。
我仔细查阅了文档,答案是
babel-preset-env@1.x
没法很好地消除未使用的polyfill(就是说有未使用的代码被引入进来了)。如果希望避免这一点,那么就会禁用useBuiltIns: true
,而用更好的transform-runtime
代替。在
babel-preset-env@2.x
中可以用useBuiltIns: 'usage'
达到按需引入的目的。详情可见:
babel/babel-preset-env#84
babel/babel-preset-env#241
可以看到 vuejs-templates/webpack/ 引入的是 1.3 的
babel-preset-env
。10 remaining items
YardWill commentedon Aug 30, 2018
@cuiyongjian 赞同第一点,实际操作了一下,entry应该是按照目标浏览器的兼容性把所有需要的polyfill都放上去,
而usage就是上面提到的按照目标兼容性和按需原则引入,而当值为false的时候,就是不管三七二十一,把polyfill里面所有的内容都引入。
第二点的话,依我的理解一个完整和高兼容性的项目是需要babel-runtime和babel-polyfill一起支持的,babel-runtime提供runtimeHelper,babel-polyfill提供instance method。
YardWill commentedon Aug 30, 2018
包括拆分后的runtime-corejs2也不支持添加instance method。
creeperyang commentedon Aug 30, 2018
@cuiyongjian @YardWill
其实按你们的说法,既然polyfill全引入了,怎么还会有
ReferenceError: regeneratorRuntime is not defined
的错误?对于
useBuiltIns: false
,babel 将不会加入任何 polyfill。可以打开
debug
看到调试信息:Using polyfills: No polyfills were added, since the
useBuiltInsoption was not set.
HsuTing commentedon Aug 30, 2018
我自己的使用結果是,給您們做參考。
useBuiltIns: false
是會按照目前兼容性把需要的polyfill
放在當前編譯的檔案useBuiltIns: entry
是會把兼容性需要的polyfill
放在唯一import '@babel/polyfill'
的檔案裡面(我沒用過)ReferenceError: regeneratorRuntime is not defined
不是babel
的問題,是regenerator-runtime
的問題,已經被修了,但還沒 publishhttps://github.com/facebook/regenerator/pull/353/files
.如果有用
useBuiltIns
是可以不用在使用babel-polyfill/regenerator
,useBuiltIns
加入polyfill
的方式是require('core-js/lib/<modules>')
,把需要polyfill
的function
,variable
以全域的方式補齊。babel-polyfill/regenerator
則是以const module = require('core-js/lib/<modules>')
的方式,僅當前編譯當案有polyfill
。比較起來
useBuiltIns: false
跟babel-polyfill/regenerator
基本上是一樣的,只是一個是用全域的方式,一個是本地的方式。YardWill commentedon Aug 30, 2018
我尝试了一下仅用这个preset,然后在index内加上import '@babel/polyfill';使用async await 并没有报ReferenceError: regeneratorRuntime is not defined
我也看过polyfill的代码,transform-regenerator库是被require了。
我感觉没什么问题,不知道你是怎么出现这种情况的。
YardWill commentedon Aug 30, 2018
@creeperyang 你也可以看一看最后打包出来的文件,看一下有没有这句代码。我按照上面的配置是能搜索到的。
creeperyang commentedon Aug 30, 2018
@YardWill 理解你的意思了。你手动引入了
@babel/polyfill
,再设置useBuiltIns: false
,这就是告诉preset-env
不要处理任何有关polyfill的事(你自己会手动处理,即你手动引入了@babel/polyfill
)。所以我的意思是对的:设置
useBuiltIns: false
时,babel不会帮你处理任何polyfill的事,你必须手动处理。YardWill commentedon Aug 30, 2018
@creeperyang 多谢指教,不过我还有一个问题https://github.com/babel/babel/blob/master/packages/babel-preset-env/src/index.js#L286 为什么entry还需要自己引入polyfill?
以下是debug信息
creeperyang commentedon Aug 30, 2018
@YardWill @HsuTing
以一个简单示例来说明。有文件
test.js
如下:我们用不同的babel配置来验证一些东西。
1. 如果只设置
useBuiltIns: false
:可以看到,由于
transform-async-to-generator
plugin起作用,async/wait
被翻译到了regeneratorRuntime
;但是
regeneratorRuntime
和Promise
都没有相应的polyfill被引入——即polyfill完全被忽略(不处理)。2. 如果只设置
useBuiltIns: 'usage'
:可以看到文件开头引入了需要的
promise/regeneratorRuntime
polyfill,但是由于我们未手动依赖(手动安装)regenerator-runtime
库,会报错ReferenceError: regeneratorRuntime is not defined
。同时(
require("core-js/modules/es6.promise")
)同样会报错,因为我们也没有安装这个依赖。3. 设置
useBuiltIns: 'usage'
且同时使用plugin-transform-runtime
:可以看到,上面的inline代码(如_asyncToGenerator等)被替换成
@babel/runtime
中相应的引用了。同时通过
require("@babel/runtime/regenerator")
,@babel/runtime
为我们提供了缺失的regenerator-runtime
库。但(
require("core-js/modules/es6.promise")
)还是会报错,因为我们没有安装这个依赖。4. 设置
useBuiltIns: 'usage'
且同时使用plugin-transform-runtime
(且配置core-js):对比下上面,可以发现
Promise
被正确修复——代码已经可以正常运行。HsuTing commentedon Aug 31, 2018
@creeperyang 我想了解您說的未手動安裝是指
install
? 如果只是未手動安裝,不是只要在個別install
就好了?我猜@babel/env
本身因為還有處理其他東西,所以babel
團隊不希望在把整個@babel/polyfill
加入到dependencies
裡面,才需要自行安裝,但不需要自己在code
裡面一一引入所需要的core-js
。另外
這段我也有找到,但是我自己測試的情況是,在
只設置useBuiltIns: 'usage'
的情況下,就算裝了core-js
或@babel/polyfill
一樣會出現ReferenceError: regeneratorRuntime is not defined
,但我手動把require("regenerator-runtime/runtime");
改成var regeneratorRuntime = require("regenerator-runtime/runtime");
是可以的,所以我猜他是沒有被加到全域變數裡面。我有試過用
patch-package
方式把https://github.com/facebook/regenerator/pull/353/files
這段 PR 覆蓋掉node_modules
的regenerator-runtime
,則確實是有把regeneratorRuntime
加到全域變數,則不用另外在手動修正成var regeneratorRuntime = require("regenerator-runtime/runtime");
。creeperyang commentedon Aug 31, 2018
@HsuTing
https://github.com/facebook/regenerator/pull/353/files 里已经不在注册全局的
regeneratorRuntime
了,但是@babel/runtime@7.0.0
或者说regenerator-runtime <= 0.13.0
里的regenerator-runtime/runtime
都是会直接提供全局的regeneratorRuntime
的(挂载到global上)。我说的 手動安裝 更多的意思是必须自己指定
@babel/polyfill
或者@babel/runtime
等包作为依赖并 install,而不是 babel 自动添加/管理。webjohnjiang commentedon Sep 11, 2018
现在对generator这里更明白一点了:
写了一篇长文,啰嗦了这些实验过程..
zhishaofei3 commentedon Dec 3, 2018
有项目地址吗