2019-07-08javascript
本文基于Babel 7.4.5。java
Babel主要模块如上图所示,接下来将分别介绍。node
@babel/core
主要是进行代码转换的一些方法,能够将源代码根据配置转换成兼容目标环境的代码。react
import * as babel from "@babel/core"; babel.transform("code();", options, function(err, result) { result.code; result.map; result.ast; });
@babel/cli
是 babel 提供的命令行工具,用于命令行下编译源代码。git
首先安装依赖:es6
npm install --save-dev @babel/core @babel/cli
新建一个js文件:github
let array = [1,2,3,4,5,6]; array.includes(function(item){ return item>2; }) class Robot { constructor (msg) { this.message = msg } say () { alertMe(this.message) } } Object.assign({},{ a:1,b:2 }) const fn = () => 1; new Promise();
执行转换:typescript
npx babel index.js --out-file out.js
能够发现输出代码没有变化,这是由于没有进行配置来肯定怎么进行转换。shell
babel是经过插件来进行代码转换的,例如箭头函数使用plugin-transform-arrow-functions
插件来进行转换。npm
首先安装该插件:
npm install --save-dev @babel/plugin-transform-arrow-functions
能够经过@babel/cli传参或者配置文件的方式使用插件:
@babel/cli
npx babel index.js --out-file out.js --plugins=@babel/plugin-transform-arrow-functions
则能够获得out.js文件,能够看到箭头函数已经被转换。
let array = [1, 2, 3, 4, 5, 6]; array.includes(function (item) { return item > 2; }); class Robot { constructor (msg) { this.message = msg } say () { alertMe(this.message) } } Object.assign({}, { a: 1, b: 2 }); const fn = function () { return 1; }; new Promise();
配置文件babel.config.js(javascript写法)或.babelrc(json写法),使用配置文件是更加经常使用的方式。
module.exports = function (api) { api.cache(true); const plugins = [ "@babel/plugin-transform-arrow-functions" ]; return { plugins }; }
咱们在index.js中使用了多种es6的语法,一个个的导入插件很麻烦,presets是一组预设好的插件集合。官方为常见环境组装了一些 presets (固然也能够本身配置):
咱们使用@babel/preset-env为例(使用前需npm install @babel/preset-env):
module.exports = function (api) { api.cache(true); const presets = [ ["@babel/preset-env"] ]; return { presets }; }
获得的结果以下, 能够看到箭头函数被编译、es6类、let声明被编译了。
"use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var array = [1, 2, 3, 4, 5, 6]; array.includes(function (item) { return item > 2; }); var Robot = /*#__PURE__*/ function () { function Robot(msg) { _classCallCheck(this, Robot); this.message = msg; } _createClass(Robot, [{ key: "say", value: function say() { alertMe(this.message); } }]); return Robot; }(); Object.assign({}, { a: 1, b: 2 }); var fn = function fn() { return 1; }; new Promise();
可是能够看到数组的实例方法includes、对象的静态方法,以及promise并无被编译。
这是由于babel 把 Javascript 语法为syntax 和 api, api 指那些咱们能够经过 函数从新覆盖的语法 ,相似 includes, map, includes, Promise, 凡是咱们能想到重写的均可以归属到api。syntax 指像箭头函数,let,const,class, 依赖注入 Decorators等等这些,咱们在 Javascript在运行是没法重写的,想象下,在不支持的浏览器里无论怎么样,你都用不了 let 这个关键字。
@babel/presets默认只对syntax进行转换,咱们须要使用@babel/polyfill来提供对api的的支持。
@babel/polyfill由core-js2和regenerator-runtime组成,后者是facebook开源库,用来实现对generator、async函数等的支持,前者是js标准库,包含不一样版本javascipt语法的实现。
只要在js文件的入口顶部引入@babel/polyfill就能够在后问的代码中自由的使用es6 api了。
可是总体@babel/polyfill整个包体积较大,咱们一般只使用了其中一部分方法,而引入整个库显然是不合适的。因此你能够只引入使用的方法:
import 'core-js/features/array/from'; // <- at the top of your entry point import 'core-js/features/array/flat'; // <- at the top of your entry point import 'core-js/features/set'; // <- at the top of your entry point import 'core-js/features/promise'; // <- at the top of your entry point Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3] [1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5] Promise.resolve(32).then(x => console.log(x)); // => 32
若是你不想污染全局命名空间(例如在写一个npm库时,要保持其隔离性)。能够引入纯净版:
import from from 'core-js-pure/features/array/from'; import flat from 'core-js-pure/features/array/flat'; import Set from 'core-js-pure/features/set'; import Promise from 'core-js-pure/features/promise'; from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3] flat([1, [2, 3], [4, [5]]], 2); // => [1, 2, 3, 4, 5] Promise.resolve(32).then(x => console.log(x)); // => 32
preset-env的配置项中的useBuiltIns
属性能够方便@babel/polyfill的使用。
useBuiltIns:false(default)
:此时不对 polyfill
作操做。若是引入 @babel/polyfill
,则无视配置的浏览器兼容,引入全部的 polyfill
。useBuiltIns:"entry"
:根据配置的浏览器兼容,引入浏览器不兼容的 polyfill
。须要在入口文件手动添加 import '@babel/polyfill'
,会自动根据 browserslist
替换成浏览器不兼容的全部 polyfill
。useBuiltIns:"usage"
:不须要在文件顶部手动引入@babel/polyfill,会根据代码中的使用进行按需添加。在这里使用useBuiltIns:"usage"
做为示例,babel.config.js文件以下:
module.exports = function (api) { api.cache(true); const presets = [ ["@babel/preset-env", { "useBuiltIns": "usage", "targets":{ "browsers":["> 1%", "last 2 versions", "not ie <= 8"] } } ] ]; return { presets, // plugins }; }
获得的编译结果:
"use strict"; require("core-js/modules/es6.promise"); require("core-js/modules/es6.object.to-string"); require("core-js/modules/es6.object.assign"); require("core-js/modules/es7.array.includes"); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var array = [1, 2, 3, 4, 5, 6]; array.includes(function (item) { return item > 2; }); var Robot = /*#__PURE__*/ function () { function Robot(msg) { _classCallCheck(this, Robot); this.message = msg; } _createClass(Robot, [{ key: "say", value: function say() { alertMe(this.message); } }]); return Robot; }(); Object.assign({}, { a: 1, b: 2 }); var fn = function fn() { return 1; }; new Promise();
能够看到实现了polyfill的按需引入。可是在配置文件中未指定core-js版本时,默认会使用core-js2。命令行会出现以下提示:
WARNING: We noticed you're using theuseBuiltIns
option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via thecorejs
option.
这是由于core-js3已经发布,@babel/polyfill不支持从core-js2到core-js3的平滑过渡,因此在babel 7.4版本中,已经废弃@babel/polyfill(只能用core-js2),而是直接引入core-js3和regenerator-runtime代替。
import "@babel/polyfill"; // migration import "core-js/stable"; import "regenerator-runtime/runtime";
使用core-js3有不少优势,首先就是新,包含不少新特性,其次就是能够配合@babel/runtime(后文详述)。更多优势见core-js@3, babel and a look into the future。
使用core-js3是 babel.config.js以下:
module.exports = function (api) { api.cache(true); const presets = [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs":3, "targets":{ "browsers":["> 1%", "last 2 versions", "not ie <= 8"] } } ] ]; return { presets, // plugins }; }
仔细观察上面的编译结果能够发现有两个问题。
要解决这两个问题,就要须要使用@babel/runtime和@babel/plugin-transform-runtime了。
@babel/runtime依赖@babel/helpers和regenerator-runtime,helper函数均可以从这里面引入,手动的确定不可能,因而 babel 提供了 @babel/plugin-transform-runtime
来替咱们作这些转换。
babel.config.js文件为:
module.exports = function (api) { api.cache(true); const presets = [ ["@babel/preset-env", { "useBuiltIns": "usage", "targets":{ "browsers":["> 1%", "last 2 versions", "not ie <= 8"] } } ] ]; const plugins = [ ["@babel/plugin-transform-runtime"] ] return { presets, plugins }; }
获得的编译结果是:
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); require("core-js/modules/es6.promise"); require("core-js/modules/es6.object.to-string"); require("core-js/modules/es6.object.assign"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); require("core-js/modules/es7.array.includes"); var array = [1, 2, 3, 4, 5, 6]; array.includes(function (item) { return item > 2; }); var Robot = /*#__PURE__*/ function () { function Robot(msg) { (0, _classCallCheck2.default)(this, Robot); this.message = msg; } (0, _createClass2.default)(Robot, [{ key: "say", value: function say() { alertMe(this.message); } }]); return Robot; }(); Object.assign({}, { a: 1, b: 2 }); var fn = function fn() { return 1; }; new Promise();
能够看到咱们的第一个问题已经圆满解决了。
解决第二个问题须要使用@babel/plugin-transform-runtime option中的corejs参数。默认为false,不对polyfill进行处理。能够设为不一样版本的core-js。
例如使用core-js2时,须要先安装
npm install --save @babel/runtime-corejs2
配置文件为:
module.exports = function (api) { api.cache(true); const presets = [ ["@babel/preset-env", { "useBuiltIns": "usage", "targets":{ "browsers":["> 1%", "last 2 versions", "not ie <= 8"] } } ] ]; const plugins = [ ["@babel/plugin-transform-runtime",{corejs:2}] ] return { presets, plugins }; }
获得的结果是:
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault"); var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise")); var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass")); require("core-js/modules/es7.array.includes"); var array = [1, 2, 3, 4, 5, 6]; array.includes(function (item) { return item > 2; }); var Robot = /*#__PURE__*/ function () { function Robot(msg) { (0, _classCallCheck2.default)(this, Robot); this.message = msg; } (0, _createClass2.default)(Robot, [{ key: "say", value: function say() { alertMe(this.message); } }]); return Robot; }(); (0, _assign.default)({}, { a: 1, b: 2 }); var fn = function fn() { return 1; }; new _promise.default();
能够看到polyfill引入时获得了一个别名,能够避免全局变量污染,可是能够发现实例方法includes并无获得相应的处理。这是core-js2没有解决的问题,随着2019年3月core-js3的发布,这个问题获得了完美解决。咱们将corejs设为3,获得告终果以下:
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass")); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var array = [1, 2, 3, 4, 5, 6]; (0, _includes.default)(array).call(array, function (item) { return item > 2; }); var Robot = /*#__PURE__*/ function () { function Robot(msg) { (0, _classCallCheck2.default)(this, Robot); this.message = msg; } (0, _createClass2.default)(Robot, [{ key: "say", value: function say() { alertMe(this.message); } }]); return Robot; }(); (0, _assign.default)({}, { a: 1, b: 2 }); var fn = function fn() { return 1; }; new _promise.default();
通过 babel 的编译后,咱们的源代码与运行在生产下的代码是不同的。
babel-register 则提供了动态编译。换句话说,咱们的源代码可以真正运行在生产环境下,不须要 babel 编译这一环节。
咱们先在项目下安装 babel-register:
$ npm install --save-dev @babel/register
而后在入口文件中 require
:
require('@babel/register') require('./app')
在入口文件头部引入 @babel/register
后,咱们的 app
文件中便可使用任意 es2015 的特性。
固然,坏处是动态编译,致使程序在速度、性能上有所损耗。(咱们在启动测试脚本的时候可使用)
咱们上面说,babel-register 提供动态编译,可以让咱们的源代码真正运行在生产环境下 - 但其实否则,咱们仍须要作部分调整,好比新增一个入口文件,并在该文件中 require('@babel/register')
。而 babel-node 能真正作到一行源代码都不须要调整:
$ npm install --save-dev @babel/core @babel/node $ npx babel-node app.js
只是,请不要在生产环境中使用 babel-node,由于它是动态编译源代码,应用启动速度很是慢
http://babel.docschina.org/docs/en/babel-plugin-transform-runtime#technical-details
https://blog.hhking.cn/2019/0...
https://segmentfault.com/a/11...
https://zhuanlan.zhihu.com/p/...