您好,登錄后才能下訂單哦!
本文首發于 vivo互聯網技術 微信公眾號
鏈接: https://mp.weixin.qq.com/s/plJewhUd0xDXh4Ce4CGpHg
作者:Morrain
在上一節 《 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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。