babel-runtime, babel-preset-env, babel-plugin-transform-runtime, babel-polyfill 详解
一直以来都对babel-runtime
,babel-preset-env
,babel-plugin-transform-runtime
,babel-polyfill
等库之间的关系,所以就去仔细研究下喽。
先简单介绍一下Babel
。
它是一个编译器可以让你使用最新版本的
ES
规范比如ES2015(ES6)
,ES2016(ES7)
,ES2017(ES8)
的写法并把它编译成老的ES5
的写法。
babel-core 是 babel 的编译器核心。
意思即你可以使用最新的JavaScript
规范的写法比如async
等来写代码,然后Babel
会帮你编译成ES5
以兼容老旧的浏览器。
转换前:
|
|
转换后:
|
|
文章中凡是以@babel
开头的都是指的Babel 7.x的否则是Babel 6.x,@
的意思可参考npm 官方,标星号的表示只有 @babel 版本的才会有的参数。
安装
可以选择全局或者本地安装,最好是依据项目本地安装。
|
|
在项目根目录下创建.babelrc
文件来配置Babel
或者是在package.json
里面进行设置。
命令
babel script.js --out-file script-compiled.js
babel来编译输出
babel-node
是一个和babel
一样功能的命令,但是它有缓存功能并且会编译 ES6 代码后再运行它。
babel-polyfil
根据官网的表述由于Babel
只是转换了语法比如箭头函数,但是并没有实现兼容那些新的原生方法比如Promise
和 Array.reduce
所以必须使用。它使用了core-js和 regenerator。
必须安装为依赖npm i babel-polyfill
。
需要注意的是这个有一个弊端会污染全局的变量,后面会有解决方案。
babel-preset-env
npm i -D babel-preset-env
当不进行任何设置的时候和babel-preset-latest
一样就是默认使用 ES2015(ES6)
,ES2016(ES7)
,ES2017(ES8)
的新语法。
|
|
参数
这里有一个参数useBuiltIns
,这个参数默认值为false
意思是不为每个文件自动添加兼容插件,或者把 import "@babel/polyfill"
根据不同目标环境拆分为多个的兼容插件。
比如当启用的时候:
输入:
|
|
输出依据不同的输出环境:
|
|
这里的输出环境即目标环境比如下 .babelrc:
|
|
这里需要注意的是只有2.x版本的useBuiltIns参数才会有usage
和entry
选项,参见这里。
useBuiltIns
在babel 6
版本只有两个值true
和false
。默认false
表示不自动为每个文件添加兼容插件或者把对import "@babel/polyfill"
的引用依据不同的目标环境输出不同的兼容插件。
Babel 的插件有两种模式:
- 正常模式,尽量贴近 ES6 的书写语法。
- 更类似 ES5 的代码
loose选项大概的意思即编译出来的代码更像ES5
而不是ES6
的语法。
优缺点如下:
- 编译出来的代码在旧的引擎中可能会运行得更快和兼容
- 缺点是在以后的版本中当从编译的ES6切换到原生ES6可能会有问题,不过这种情况极少。
例如如下代码:
|
|
注意看如下行(A)那里的不同,一个是使用ES6语法一个是使用ES5。
正常模式:
|
|
宽松模式:
具体可参见这里。
插件
Babel
是一个编译器,在高级层面它分三阶段解析,转换,生成代码。
官方 Presets
如果不想自己设置一堆插件的话,官方有env
,react
,flow
三个 Presets。即预安装了 plugins 的配置。
Stage-X(试验性 Presets)
stage-x presets指的是任何未被包括在官方发布版比如(ES6/ES2015)中的草案。任何 stage-3
之前的都应该要谨慎使用,
一共有以下几个试验性的版本:
- Stage 0 - 草稿:只是一个设想可能是一个 Babel 插件
- Stage 1 - 提案:值得去推进的东西
- Stage 2 - 草案:初始化规范
- Stage 3 - 候选:完整规范和初始化浏览器实现
- Stage 4 - 完成:会加入到下一版中
转换插件
有很多的转换插件具体可查看这里。
语法插件
这种插件可以让 Babel
来解析特殊类型的语法。
如果对应的转换插件已经使用了转换插件会自动使用语法插件。
|
|
Plugins/Preset 路径
如果插件发布到npm上面则可以使用"plugins": ["babel-plugin-myPlugin"]
,也可以使用"plugins:": ["./node_modules/pluginPath"]
。
Plugins/Preset 快捷方式
如果插件前缀是babel-plugin-
则可以使用"plugins:": ["myPlugin"]
,presets
同理"presets": ["@org/babel-preset-name"]
。作用域包也是如此,"presets": ["@org/babel-preset-name"]
快捷方式为:"presets": ["@org/name"]
。
Plugin/Preset 顺序
当这两种插件同时处理代码的时候,将以插件或者preset的顺序来进行转换。
- 插件在
presets
之前运行 - 插件顺序是从第一到最后
- Preset顺序是相反的从最后到第一
比如:
|
|
将会先运行transform-decorators-legacy
后transform-class-properties
。
Presets的顺序是相反的:
|
|
将会以stage-2
,react
和 es2015
的顺序运行。这主要是因为向后兼容性,因为大多数用户把es2015
放在 stage-0
之前。
Plugins 和 Preset 参数
|
|
|
|
开发插件
|
|
开发 Preset
|
|
Plugins 和 Preset 配合使用
如果Plugins和Preset配合使用的话假设如下配置:
|
|
以上 @babel
指的是在babel
组织下的相关包这里即是兼容的babel 7.x
版本。按照解析的规则是 plugins
先于presets
,之后再进入presets
,转化出来的代码是一样的,因为@babel/preset-env
即是兼容了那个es6
的for...of
写法。
babel-runtime 和 babel-plugin-transform-runtime
先来介绍 babel-plugin-transform-runtime。
需要注意的是实例方法例如
"foobar".includes("foo")
因为会更改内置的方法所以不会正常工作需要引用babel-polyfill
。
这个插件的作用有两个:
由于
Babel
使用工具函数作为常用函数比如_extend
。默认情况下会被添加进每个文件中。这样就会导致重复,特别是当程序包含了多个文件。所有的工具函数都会引用模块babel-runtime
来减少代码重复,运行时会被编译进构建的代码之中。另一个作用是代码提供一个沙箱环境。当你使用
babel-polyfill
和其内置的诸如Promise
,Set
等新 API,这些会污染全局变量。当是一个 app 的时候或者是一个命令行工具的时候不会有问题,但是如果是想要发布来给别人用的时候就会因为无法控制目标环境而出现问题。
这个转换插件将对这些内置函数的引用指向core-js
这样就可以不需要使用 polyfill。
一般情况下需要安装babel-plugin-transform-runtime
来作为一个开发依赖包。
|
|
然后安装babel-runtime
来作为生产环境的依赖包。
|
|
转换插件只是在开发环境使用,但是运行时插件依赖于部署发布的代码。
在 .babelrc 中使用如下配置即可:
|
|
参数
helpers
默认为 true,是否内联的 Babel 工具函数替换为对模块的引用。
即以下代码:1class Person {}
一般会转换为:
|
|
使用 babel-runtime
转换插件会转换为:
|
|
这里在实际操作过程中需要添加类似如下的代码否则是不会进行任何转换:
|
|
polyfill
默认 true,是否允许让内置的诸如
Promise
,Set
,Map
使用不会污染全局作用域的兼容插件。regenerator
默认为 true,是否设置生成器函数是否使用再生器运行时插件来避免污染全局作用域。
moduleName
默认为
"babel-runtime"
。当使用工具函数的时候设置模块的名称 / 路径。useBuiltIn*
默认为 false。当启用的意思即当使用工具函数的时候不再引用
core-js
中的兼容插件。useESModules*
默认 false,当启用的时候转换插件就不会使用经过 @babel/plugin-transform-modules-commonjs 处理的工具函数。这样有利于为诸如 webpack 的模块构建系统减少构建包的大小,因为 webpack 不需要保存 commonjs 的语法。
babel-plugin-transform-runtime,babel-preset-env,babel-polyfill 的使用
这三个插件是如何配合的?这里主要是因为 Babel 7 和 Babel 6,会造成一些区别,分情况来说明吧。
举个例子吧,目录结构如下:
|
|
执行如下命令
|
|
Babel 7
babel-preset-env 需要和 babel-polyfill 配合使用。例如
12345678910111213141516{"presets": [["@babel/preset-env",{"targets": {"browsers": ["last 2 versions"]},"useBuiltIns": "usage","debug": true}]],}package.json 中添加
1"babel": "babel build/test.js --out-file build/test-compiled.js"运行 npm run babel 即可。
例一
.babelrc:
12345678910111213141516{"presets": [["@babel/preset-env",{"targets": {"browsers": ["last 2 versions"]},"useBuiltIns": "usage","debug": true}]],}test.js 内容如下:
123456789101112class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js
123456789101112131415161718"use strict";require("core-js/modules/es6.promise");function _classCallCheck(instance, Constructor) { if (!(instance instanceofConstructor)) { throw new TypeError("Cannot call a class as a function"); } }var Person = function Person() {_classCallCheck(this, Person);};var a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');可以看到上面是把 test.js 在目标浏览器环境下是通过引用 polyfill 的方式。
1require("core-js/modules/es6.promise")这里如果不想要用 commonjs 的方式引入兼容的插件可以设置 modules 参数。
例二
.babelrc:
1234567891011121314{"presets": [["@babel/preset-env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": "entry","debug": true}]],}test.js
1234567891011121314import "@babel/polyfill";class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
12345678910111213141516171819202122"use strict";require("core-js/modules/es6.array.sort");require("core-js/modules/es6.date.to-json");require("core-js/modules/es6.typed.array-buffer");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);};var a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例三
当设置 useBuiltIns 为 false 的时候的输出。
test.js:1234567891011121314import "@babel/polyfill";class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
123456789101112131415161718"use strict";require("@babel/polyfill");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);};var a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例四
.babelrc:
1234567{"plugins": [["@babel/plugin-transform-runtime", {"moduleName": "@babel/runtime"}]],}test.js
123456789101112class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
123456789101112var _Promise = require("@babel/runtime/core-js/promise");class Person {}let a = new _Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');可以看到上面的 Promise 并没有污染全局的 Promise 而是引用了 @babel/runtime/core-js/promise。
例五
.babelrc:
12345678910111213141516171819{"plugins": [["@babel/plugin-transform-runtime", {"moduleName": "@babel/runtime",}]],"presets": [["@babel/preset-env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": "usage","debug": true}]],}test.js:
123456789101112class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
12345678910111213141516var _Promise = require("@babel/runtime/core-js/promise");var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");var Person = function Person() {_classCallCheck(this, Person);};var a = new _Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例六
.babelrc:
12345678910111213141516171819{"plugins": [["@babel/plugin-transform-runtime", {"moduleName": "@babel/runtime",}]],"presets": [["@babel/preset-env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": "entry","debug": true}]],}test.js:
1234567891011121314import "@babel/polyfill";class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
1234567891011121314151617181920212223242526272829303132"use strict";var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");var _promise = _interopRequireDefault(require("@babel/runtime/core-js/promise"));var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));require("core-js/modules/es6.array.sort");require("core-js/modules/es6.date.to-json");require("core-js/modules/es6.typed.array-buffer");require("core-js/modules/es6.typed.int8-array");require("core-js/modules/es6.symbol");require("core-js/modules/es6.number.parse-int");var Person = function Person() {(0, _classCallCheck2.default)(this, Person);};var a = new _promise.default(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');当 @babel/preset-env 中的 useBuiltIns 为 false 的时候和 usage 是一样的(但是必须是 test.js 中没有有 import polyfill)。
Babel 6
例一
.babelrc:
1234567891011121314{"presets": [["babel-preset-env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": true,"debug": true}]],}test.js:
123456789101112class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
123456789101112131415161718'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);};var a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例二
.babelrc 同例一。
test.js:12345678910111213import "babel-polyfill";class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
12345678910111213141516171819202122232425262728293031323334'use strict';require('core-js/modules/es6.typed.array-buffer');require('core-js/modules/es6.typed.int8-array');require('core-js/modules/es6.typed.uint8-array');require('core-js/modules/es6.typed.uint8-clamped-array');require('core-js/modules/es6.typed.int16-array');require('core-js/modules/es6.typed.uint16-array');require('core-js/modules/es6.typed.int32-array');require('core-js/modules/es6.typed.uint32-array');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);};var a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例三
.babelrc:
1234567891011121314{"presets": [["babel-preset-env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": false,"debug": true}]],}test.js 同例二。
test-compiled.js:1234567891011121314151617181920'use strict';require('babel-polyfill');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);};var a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例四
.babelrc:
12345{"plugins": ["transform-runtime"],}test.js:
123456789101112class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
1234567891011121314import _Promise from 'babel-runtime/core-js/promise';class Person {}let a = new _Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例五
.babelrc 和例四一样。
test.js:12345678910111213import "babel-polyfill";class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
1234567891011121314import _Promise from 'babel-runtime/core-js/promise';import "babel-polyfill";class Person {}let a = new _Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例六
.babelrc:
1234567891011121314151617{"plugins": ["transform-runtime"],"presets": [["babel-preset-env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": true,"debug": true}]],}test.js:
12345678910111213import "babel-polyfill";class Person {}let a = new Promise(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');test-compiled.js:
12345678910111213141516171819202122232425262728293031323334353637'use strict';var _promise = require('babel-runtime/core-js/promise');var _promise2 = _interopRequireDefault(_promise);var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);require('core-js/modules/es6.typed.array-buffer');require('core-js/modules/es6.typed.int8-array');require('core-js/modules/es6.typed.uint8-array');require('core-js/modules/es6.typed.uint8-clamped-array');require('core-js/modules/es6.typed.int16-array');...还有其它兼容插件function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var Person = function Person() {(0, _classCallCheck3.default)(this, Person);};var a = new _promise2.default(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');例七
.babelrc 和例六一样。
test.js和例六比去掉1import "babel-polyfill"test-compiled.js:
1234567891011121314151617181920212223242526'use strict';var _promise = require('babel-runtime/core-js/promise');var _promise2 = _interopRequireDefault(_promise);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);};var a = new _promise2.default(function (resolve, reject) {console.log('Promise');resolve();});a.then(function () {console.log('resolved.');});console.log('Hi');设置 .babelrc 中 useBuiltIns 为 false 和上面是一样的结果。
当设置 .babelrc 中 useBuiltIns 为 false 并且test.js
中添加1import "babel-polyfill";则输出会多一句
1require('babel-polyfill');当设置 .babelrc 中 useBuiltIns 为 true 并且 test.js 中添加
1import "babel-polyfill"则输出会多
1234require('core-js/modules/es6.typed.int16-array');require('core-js/modules/es6.typed.uint16-array');require('core-js/modules/es6.typed.array-buffer');...还有其它总结
这里有一个非常重要的问题就是关于transform-runtime官方文档上写的关于有些方法必须使用 babel-polyfill 中的方法。
那么这样导致的结果就是如果是在 Babel 6 中的时候当在源文件中写上“foobar”.includes(“foo”)就必须写上import “babel-polyfill”;。
但是 Babel 7 就不需要在源文件中写
_import "@babel/polyfill";_
会智能判断是否需要去引用这个includes
的 polyfill。当
test.js
中有import “babel-polyfill”;的时候,Babel 6 版本的“useBuiltIns”: true即是 Babel 7 的“useBuiltIns”: “entry”。在 Babel 7 中即使是源文件中不写import “@babel/polyfill”;当设置“useBuiltIns”: “usage”会根据情况输出“@babel/polyfill”对应兼容插件的输出。
安装
babel-polyfill
。当是 Babel 7 的时候使用 .babelrc 配置建议如下:
12345678910111213141516{"plugins": ["@babel/plugin-transform-runtime"],"presets": [["@babel/preset-env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": "usage",}]],}Babel 6
如果有使用 babel-polyfill。
1234567891011121314{"plugins": ["transform-runtime"],"presets": [["env",{"targets": {"browsers": ["last 2 versions", "safari 7"]},"useBuiltIns": true,}]],}Babel 6 webpack 中的配置:
12345678910111213141516171819202122const path = require('path');module.exports = {entry: {index: ["babel-polyfill", "./test.js"]},output: {filename: '[name].bundle.js',path: path.resolve(__dirname, 'dist')},module: {rules: [{test: /\.js$/,exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader'}},]}};Babel 7 webpack 配置:
1234567891011121314151617181920const path = require('path');module.exports = {entry: ["./test.js"],output: {filename: '[name].bundle.js',path: path.resolve(__dirname, 'dist')},module: {rules: [{test: /\.js$/,exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader'}},]}};