大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
本文首发于 vivo互联网技术 微信公众号
链接: https://mp.weixin.qq.com/s/plJewhUd0xDXh4Ce4CGpHg
作者:Morrain创新互联建站-专业网站定制、快速模板网站建设、高性价比黎平网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式黎平网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖黎平地区。费用合理售后完善,10余年实体公司更值得信赖。
在上一节 《 CommonJS:不是前端却革命了前端》中,我们聊到了 ES6 Module,它是 ES6 中对模块的规范,ES6 是 ECMAScript 6.0 的简称,泛指 JavaScript 语言的下一代标准,它的第一个版本 ES2015 已经在 2015 年 6 月正式发布,本文中提到的 ES6 包括 ES2015、ES2016、ES2017等等。在第一节的《Web:一路前行一路忘川》中也提到过,ES2015 从制定到发布历经了十几年,引入了很多的新特性以及新的机制,浏览器对 ES6 的支持进度远远赶不上前端开发小哥哥们使用 ES6 的热情,于是矛盾就日益显著……
先来看下它在 官网上的定义:
Babel is a JavaScript compiler
没错就一句话,Babel 是 JavaScript 的编译器。至于什么是编译器,可以参考 the-super-tiny-compiler 这个项目,可以找到很好的答案。
本文是以 Babel 7.9.0 版本进行演示和讲解的,另外建议学习者阅读英文官网,中文官网会比原版网站慢一个版本,并且很多依然是英文的。
Babel 就是一套解决方案,用来把 ES6 的代码转化为浏览器或者其它环境支持的代码。 注意我的用词哈,我说的不是转化为 ES5 ,因为不同类型以及不同版本的浏览器对 ES6 新特性的支持程度都不一样,对于浏览器已经支持的部分,Babel 可以不转化,所以 Babel 会依赖浏览器的版本,后面会讲到。这里可以先参考 browerslist 项目。
在学习任何一门知识前,我都习惯先了解它的历史,这样才能深刻理解它存在意义。
Babel 的作者是 FaceBook 的工程师 Sebastian McKenzie。他在 2014 年发布了一款 JavaScript 的编译器 6to5。从名字就能看出来,它主要的作用就是将 ES6 转化为 ES5。
这里的 ES6 指 ES2015,因为当时还没有正式发布, ES2015 的名字还未被正式确定。
于是很多人评价,6to5 只是 ES6 得到支持前的一个过渡方案,它的作者非常不同意这个观点,认为 6to5 不光会按照标准逐步完善,依然具备非常大的潜力反过来影响并推进标准的制定。正因为如此 6to5 的团队觉得 '6to5' 这个名字并没有准确的传达这个项目的目标。加上 ES6 正式发布后,被命名为 ES2015,对于 6to5 来说更偏离了它的初衷。于是 2015 年 2 月 15 号,6to5 正式更名为 Babel。
(图片来源于网络)
Babel 是巴比伦文化里的通天塔,用来给 6to5 这个项目命名真得太贴切了!羡慕这些牛逼的人,不光代码写得好,还这么有文化,不像我们,起个变量名都得憋上半天,吃了没有文化的亏。这也是为什么我把这篇文章起名为 《Babel:把 ES6 送上天的通天塔》的原因。
了解了 Babel 是什么后,很明显我们就要开始考虑怎么使用 Babel 来转化 ES6 的代码了,除了 Babel 本身提供的 cli 等工具外,它还支持和其它打包工具配合使用,譬如 webpack、rollup 等等,可以参考 官网对不同平台提供的配置说明。
本文为了感受 Babel 最原始的用法,不结合其它任何工具,直接使用 Babel 的 cli 来演示。
使用如下命令构建一个 npm 包,并新建 src 目录 和 一个 index.js 文件。
npm init -y
package.json 内容如下:
{ "name": "demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
npm install --save-dev @babel/core @babel/cli @babel/preset-env
后面会介绍这些包的作用,先看用法
增加 babel 命令来编译 src 目录下的文件到 dist 目录:
{ "name": "demo", "version": "1.0.0", "description": "", "main": "src/index.js", "scripts": { "babel": "babel src --out-dir dist", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/cli": "^7.8.4", "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.0" } }
在工程的根目录添加 babel.config.js 文件,增加 Babel 编译的配置,没有配置是不进行编译的。
const presets = [ [ '@babel/env', { debug: true } ] ] const plugins = [] module.exports = { presets, plugins }
上例中 debug 配置是为了打印出 Babel 工作时的日志,可以方便的看来,Babel 转化了哪些语法。
推荐用 Javascript 文件来写配置文件,而不是 JSON 文件,这样可以根据环境来动态配置需要使用的 presets 和 plugins
const presets = [ [ '@babel/env', { debug: true } ] ] const plugins = [] if (process.env["ENV"] === "prod") { plugins.push(...) } module.exports = { presets, plugins }
编译时就会报如下错误:
根据报错的提示,添加 @babel/plugin-proposal-class-properties 即可。
npm install --save-dev @babel/plugin-proposal-class-properties 点击并拖拽以移动
// babel.config.js const presets = [ [ '@babel/env', { debug: true } ] ] const plugins = ['@babel/plugin-proposal-class-properties'] module.exports = { presets, plugins }
当 useBuiltIns 设置为 'usage' 或者 'entry' 时,还需要设置 @babel/preset-env 的 corejs 参数,用来指定注入 built-in 的实现时,使用 corejs 的版本。否则 Babel 日志输出会有一个警告。
最终的 Babel 配置如下:
// babel.config.js const presets = [ [ '@babel/env', { debug: true, useBuiltIns: 'usage', corejs: 3, targets: {} } ] ] const plugins = ['@babel/plugin-proposal-class-properties'] module.exports = { presets, plugins }
在介绍 @babel/plugin-transform-runtime 的用途之前,先前一个例子:
// src/index.js const add = (a, b) => a + b const arr = [1, 2] const hasThreee = arr.includes(3) new Promise(resolve=>resolve(10)) class Person { static a = 1; static b; name = 'morrain'; age = 18 } // dist/index.js "use strict"; require("core-js/modules/es.array.includes"); require("core-js/modules/es.object.define-property"); require("core-js/modules/es.object.to-string"); require("core-js/modules/es.promise"); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var add = function add(a, b) { return a + b; }; var arr = [1, 2]; var hasThreee = arr.includes(3); new Promise(function (resolve) { return resolve(10); }); var Person = function Person() { _classCallCheck(this, Person); _defineProperty(this, "name", 'morrain'); _defineProperty(this, "age", 18); }; _defineProperty(Person, "a", 1); _defineProperty(Person, "b", void 0);
之前的例子,再次编译后,可以看到,之前的 helper 函数,都变成类似require("@babel/runtime/helpers/classCallCheck") 的实现了。
// dist/index.js "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); require("core-js/modules/es.array.includes"); require("core-js/modules/es.object.to-string"); require("core-js/modules/es.promise"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var add = function add(a, b) { return a + b; }; var arr = [1, 2]; var hasThreee = arr.includes(3); new Promise(function (resolve) { return resolve(10); }); var Person = function Person() { (0, _classCallCheck2["default"])(this, Person); (0, _defineProperty2["default"])(this, "name", 'morrain'); (0, _defineProperty2["default"])(this, "age", 18); }; (0, _defineProperty2["default"])(Person, "a", 1); (0, _defineProperty2["default"])(Person, "b", void 0);
// babel.config.js const presets = [ [ '@babel/env', { debug: true, targets: {} } ] ] const plugins = [ '@babel/plugin-proposal-class-properties', [ '@babel/plugin-transform-runtime', { corejs: 3 } ] ] module.exports = { presets, plugins } // dist/index.js "use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var add = function add(a, b) { return a + b; }; var arr = [1, 2]; var hasThreee = (0, _includes["default"])(arr).call(arr, 3); new _promise["default"](function (resolve) { return resolve(10); }); var Person = function Person() { (0, _classCallCheck2["default"])(this, Person); (0, _defineProperty2["default"])(this, "name", 'morrain'); (0, _defineProperty2["default"])(this, "age", 18); }; (0, _defineProperty2["default"])(Person, "a", 1); (0, _defineProperty2["default"])(Person, "b", void 0);
'entry-global' 等价于 @babel/preset-env 中的 useBuiltIns: 'entry'
'usage-global' 等价于 @babel/preset-env 中的 useBuiltIns: 'usage'
'usage-pure' 等价于 @babel/plugin-transform-runtime 中的 corejs
本文为了讲解方便,都是用 Babel 原生的 @babel/cli 来编译文件,实际使用中,更多的是结合 webpack、rollup 这样第三方的工具来使用的。
所以下一节,我们聊聊打包工具 webpack。
6to5 JavaScript Transpiler Changes Name to Babel
Babel学习系列2-Babel设计,组成
初学 Babel 工作原理
RFC: Rethink polyfilling story