看着Babel
的官方文檔學習實在是困難,大概仍是由於那年英語太差,所幸有不少大大們對文檔進行了翻譯。另外看了不少前輩們的文章,受益不淺。可是紙上得來終覺淺,眼睛老是在欺騙我。當我覺得我看懂了,閉上眼睛自省一下:「我會了什麼?」這個時候才發現一切都是我覺得的。因此又從新整理了本身的思路,並寫了下來,方便回顧。node
Babel 是一個 JavaScript 編譯器react
Babel 是一個工具鏈,主要用於將 ECMAScript 2015+ 版本的代碼轉換爲向後兼容的 JavaScript 語法,以便可以運行在當前和舊版本的瀏覽器或其餘環境中。webpack
Babel 能幹什麼?
web
通俗地講,Babel 只是轉移新標準引入的語法,例如 ES6 中的箭頭函數、解構等。而新標準中新增的方法、函數等就須要經過 Polyfill 在目標環境中添加缺失的特性(即新標準中新增的方法、函數等)來解決。typescript
Babel編譯的過程分爲三個階段:npm
本文主要分析的是三個階段中的 轉換,大部分的內容參考自 Babel官網。json
Babel雖然能夠開箱即用,可是若是什麼都不配置,那麼它會將代碼解析以後再輸出一樣的代碼。因此咱們須要經過配置插件(Plugins)和預設(Preset)來轉換咱們的代碼。promise
爲了更清楚的瞭解 Babel 是如何 「起做用」 的,咱們能夠進行如下的準備工做。瀏覽器
建立demobash
新建文件夾babel-test
,進入該目錄下。使用 npm init -y
初始化,新建src/index.js
文件,並添加文件內容const fn = () => 1;
安裝必要的依賴
@babel/core
是 Babel 的核心,包含了全部的核心 API。
@babel/cli
是命令行工具,爲咱們提供 babel 命令來編譯文件。
# 當前文章安裝的版本:
# @babel/core: ^7.9.0
# @babel/cli: ^7.8.4
npm install --save-dev @babel/core @babel/cli
複製代碼
添加編譯命令
在package.json
文件中的的script
字段下添加一項:
{
...
"scripts": {
"trans": "babel src --out-dir lib"
}
...
}
複製代碼
該命令行的做用是將babel-test/src
目錄下的每一個JavaScript文件轉換並輸出到babel-test/lib
目錄。
目前package.json
文件內容以下圖所示:
npm run trans
咱們會發現
lib/index.js
中輸出的內容和
src/index.js
是一致的,這是由於咱們沒有告訴
Babel
應該執行什麼樣的轉換,因此下面就須要咱們設置插件和預設來解決這個問題。
插件用於轉換咱們的代碼,分爲兩種:語法插件和轉換插件。
語法插件
只容許Babel
解析(parse)特定類型的語法(而不是轉換)。
轉換插件
轉換插件將啓用相應的語法插件,所以咱們沒必要同時指定這兩個插件。
當咱們啓用轉換插件時,會自動啓用相應的語法插件進行解析,而後經過轉換插件進行轉換。想要詳細瞭解有哪些轉換插件能夠看這裏:插件。
插件的使用
若是插件是在項目根目錄下經過 npm 安裝的,那咱們能夠輸入插件的名稱,babel 會自動檢查它是否已經被安裝到node_modules
目錄下。
要將src/index.js
中的箭頭函數轉換成普通函數,咱們能夠藉助官方提供的轉換插件@babel/plugin-transform-arrow-functions
。
npm isntall --save-dev @babel/plugin-transform-arrow-functions
複製代碼
新建bebal-test/.babelrc
文件並添加上面安裝好的插件,文件內容以下:
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
複製代碼
配置完成後,再次執行npm run trans
命令,咱們能夠看到lib/index.js
文件中已是咱們想要的內容了。
固然,在實際開發中,若是咱們一個一個這樣去配置轉換插件,那也麻煩了。所幸Babel
爲咱們提供了預設Preset
。
Preset 能夠做爲 Babel 插件的組合。
官方針對咱們經常使用的環境編寫了一些Preset
:
如需建立preset,導出一份配置便可。
module.exports = function() {
return {
plugins: [
"pluginA",
"pluginB",
"pluginC",
]
};
}
複製代碼
preset 能夠包含其餘的 preset,以及帶有參數的插件。
module.exports = () => ({
presets: [
require("@babel/preset-env"),
],
plugins: [
[require("@babel/plugin-proposal-class-properties"), { loose: true }],
require("@babel/plugin-proposal-object-rest-spread"),
],
});
複製代碼
@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!(官網)
意思是:@babel/preset-env
是一個靈活的預設,你不須要管理目標環境須要的語法轉換或瀏覽器polyfills,就可使用最新的 JavaScript,同時也會讓 JavaScript 打包後的文件更小。
那麼@babel/preset-env
的做用是什麼呢?
須要注意的是,@babel/preset-env它不支持stage-x插件。
安裝
# @babel/preset-env: ^7.9.0
npm install --save-dev @babel/preset-env
複製代碼
修改.babelrc
文件內容:
{
"presets": [
"@babel/preset-env"
]
}
複製代碼
栗子🌰
修改src/index.js
中的內容以下所示:
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
複製代碼
執行編譯命令npm run trans
,lib/index.js
中的內容以下所示:
"use strict";
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
let promsie = new Promise();
複製代碼
經過上面兩段代碼的對比,咱們能夠看到只有原先的箭頭函數發生了轉換。而Array.from
方法和Promise
構造函數並無發生轉換。
小結:
由於@babel/preset-env轉換的是語法,不包含新增的全局變量、方法等,因此須要加載瀏覽器polyfills來完善代碼轉換。(後面介紹完Polyfill會對.babelrc
文件進行完善)
@babel/preset-env
會拿到咱們指定的目標環境,檢查這些映射表來編譯一系列的插件並傳給 Babel。針對基於瀏覽器的或Electron-based的項目,官方推薦使用.browserslistrc
文件來指定目標環境。如果咱們沒有設置targets或ignoreBrowserslistConfig配置項,@babel/preset-env
默認會使用.browserslistrc
中的配置。
好比,當咱們只想包括大於0.25%市場份額的瀏覽器的那些polyfill和代碼轉換:
options(關於@babel/preset-env
的更多options配置)
{
"presets": [
[
"@babel/preset-env",
{
"targets": "> 0.25%, not dead"
}
]
]
}
複製代碼
browserslist
> 0.25%
not dead
複製代碼
package.json
{
"browserslist": "> 0.25%, not dead"
}
複製代碼
polyfill 的中文意思是墊片,用來墊平不一樣目標環境下的差別,讓新的內置函數、實例方法等在低版本瀏覽器中也可使用。
Babel 7.4.0 版本開始,@babel/polyfill 已經被廢棄不推薦使用,支持直接導入 core-js/stable(polyfill ECMAScript 特性)和 regenerator-runtime/runtime(須要使用轉換後的 generator 函數)(官網)。
這裏我就根據本身的理解介紹一下 Babel 7.4.0 版本以後 Polyfill 的用法,若是有理解錯的地方,還請各位看官大大幫我指出。
core-js
和regenerator-runtime
將模擬完整的ES2015 +環境(不包含第4階段的提議),在 JavaScript 代碼執行前引入。
This means you can use new built-ins like Promise or WeakMap, static methods like Array.from or Object.assign, instance methods like Array.prototype.includes, and generator functions (provided you use the regenerator plugin). The polyfill adds to the global scope as well as native prototypes like String in order to do this.
意思是咱們可使用: 新的內置函數 如 Promise 和 WeakMap; 新的靜態方法 如 Array.from 和 Object.assign; 新的實例方法 如 Array.prototype.includes和generator函數(前提是使用了 @babel/plugin-transform-regenerator 插件)。 polyfill
將添加到全局範圍以及本機原型(如String)中,以便執行此操做。
安裝core-js
和regenerator-runtime
。
# core-js: ^3.6.4;提供 es 新的特性。
# regenerator: ^0.14.4;應用代碼中用到generator、async函數的話,提供對 generator 支持。
npm install --save core-js regenerator
複製代碼
安裝完成後修改src/index.js
文件。
import "core-js/stable";
import "regenerator-runtime/runtime";
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
複製代碼
執行npm run trans
命令後,查看lib/index.js
中的內容。
"use strict";
require("core-js/stable");
require("regenerator-runtime/runtime");
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
var promsie = new Promise();
複製代碼
此時咱們的代碼在低版本瀏覽器中已經可以正常運行了。
經過webpack打包後,發現包的大小爲127kb。這是由於咱們引入了所有的Polyfill
致使壓縮後包的體積變大,因此咱們更但願按需引入,所幸Babel
已經爲咱們提供瞭解決方案(webpack的配置文件在文末會貼出來)。
@babel/preset-env
提供了一個 useBuiltIns
參數,設置值爲 usage
時,就只會包含代碼須要的 polyfill 。設置該參數時,必需要同時設置 corejs,前面已經安裝了這裏就不重複提了。(core-js@2已經不會再添加新特性,新特性都會添加到 core-js@3)
優化
修改配置文件.babelrc
。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
複製代碼
修改src/index.js
。
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
async function fn() {
return 1
}
複製代碼
執行npm run trans
命令後,查看lib/index.js
中的內容。
"use strict";
require("core-js/modules/es.array.from");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
require("core-js/modules/es.string.iterator");
require("regenerator-runtime/runtime");
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
var promsie = new Promise();
function fn() {
return _fn.apply(this, arguments);
}
function _fn() {
_fn = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
return _context.abrupt("return", 1);
case 1:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _fn.apply(this, arguments);
}
複製代碼
從輸出結果能夠看到,咱們已經實現了按需引入Polyfill
,再次打包後發現包的大小已經變成了30kb,效果非常顯著,可是這種使用方式始終存在污染全局環境的問題。
爲了解決這個問題,Babel
給咱們提供了@babel-runtime
。它將開發者依賴的全局內置對象等,抽取成單獨的模塊,並經過模塊導入的方式引入,避免了對全局環境的污染。
@babel/runtime
是一個包含 Babel modular runtime helpers 和 regenerator-runtime 的庫。
與Polyfill
的區別:
Polyfill 會修改(覆蓋)新增的內置函數、靜態方法和實例方法。
@babel/runtime 不會,它只是引入一些 helper 函數,創造對應的方法。
安裝(@babel-runtime
是代碼運行時須要的依賴,因此須要做爲生產依賴安裝)
npm install --save @babel/runtime
複製代碼
有時Babel
可能會在輸出中注入一些跨文件的相同代碼,所以可能會被重用。
栗子🌰
修改src/index.js
中的內容:
class Parent {}
複製代碼
執行編譯命令npm run trans
,lib/index.js
中的內容:
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Parent = function Parent() {
_classCallCheck(this, Parent);
};
複製代碼
這意味着每一個包含類的文件都將引入_classCallCheck,重複的代碼注入必然致使包的變大。這個時候就須要使用插件@babel/plugin-transform-runtime
。
@babel/plugin-transform-runtime
用於構建過程的代碼轉換,而 @babel/runtime
是提供幫助方法的模塊,這樣就能夠避免重複的代碼注入。
@babel/plugin-transform-runtime
是一個能夠重複使用 Babel 注入的幫助程序,以節省代碼大小的插件。
Babel使用很小的幫助器來完成例如Class的功能。默認狀況下,它將被添加到須要它的每一個文件中。有時不須要重複,特別是當咱們的應用程序分佈在多個文件中時。
這是@babel/plugin-transform-runtime
插件的來源:全部幫助程序都將引用該模塊,@babel/runtime
以免在編譯後的輸出中出現重複。運行時將被編譯到咱們的構建中。
該轉換器的另外一個目的是爲咱們的代碼建立一個沙盒環境。若是直接引入core-js
或@babel/polyfill
,它提供了諸如內置插件Promise,Set和Map等,這些會污染全局環境。儘管這對於應用程序或命令行工具多是能夠的,可是若是咱們的代碼是要發佈供他人使用的庫,或者咱們沒法徹底控制代碼運行的環境,則將成爲一個問題。@babel/plugin-transform-runtime
會將這些內置別名做爲core-js
的別名,所以咱們能夠無縫使用它們,而無需使用polyfill。(官網)
安裝
npm install --save-dev @babel/plugin-transform-runtime
複製代碼
修改.babelrc
文件內容:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
複製代碼
執行編譯命令npm run trans
:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var Parent = function Parent() {
(0, _classCallCheck2["default"])(this, Parent);
};
複製代碼
從lib/index.js
中輸出的內容能夠看出classCallCheck不是直接注入到代碼中,而是從 @babel/runtime
中引入,這就避免了相同代碼的重複注入。
經過添加配置避免全局環境被污染。
安裝@babel/runtime-corejs3
npm install --save @babel/runtime-corejs3
複製代碼
修改src/index.js
:
Array.from('foo'); // ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
let promsie = new Promise();
async function fn() {
return 1
}
複製代碼
修改.babelrc
:
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
複製代碼
執行編譯命令npm run trans
,lib/index.js
中的內容:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from"));
(0, _from["default"])('foo'); // ['f', 'o', 'o']
(0, _from["default"])([1, 2, 3], function (x) {
return x + x;
}); // [2, 4, 6]
var promsie = new _promise["default"]();
function fn() {
return _fn.apply(this, arguments);
}
function _fn() {
_fn = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
return _context.abrupt("return", 1);
case 1:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _fn.apply(this, arguments);
}
複製代碼
從輸出文件中能夠看到,@babel/plugin-transform-runtime
經過模塊導入的方式引入所需的功能代碼,避免了對全局環境的污染。
# 本文使用的webpack相關的版本
# webpack-cli@3.3.11
# webpack@4.42.0
# clean-webpack-plugin@3.0.0
# babel-loader@8.1.0
npm install --save-dev webpack-cli webpack babel-loader clean-webpack-plugin
複製代碼
babel-test/webpack.config.js
文件const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'production',
entry: './lib/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash].js'
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
},
plugins: [
new CleanWebpackPlugin()
]
}
複製代碼
添加命令
在package.json
文件中的的script
字段下添加一項:
{
...
"scripts": {
"build": "webpack --mode=production"
}
...
}
複製代碼
執行命令
npm run build
複製代碼
紙上得來終覺淺,絕知此事要躬行。
另外也謝謝各位大大的文章,讓我受益不淺,下面的連接能夠進去看看哦。 7
若是這篇文章一樣對你有幫助,那就給我留個贊吧,謝謝~
參考連接
* Babel官網
* 不可錯過的 Babel7 知識
* Babel 社區概覽* ...