閱讀目錄
一:理解 babel之配置檔案.babelrc 基本配置項
1. 什麼是babel? 它是幹什麼用的?
ES6是2015年釋出的下一代javascript語言標準,它引入了新的語法和API,使我們編寫js程式碼更加得心應手,比如class,let,for...of promise等等這樣的,但是可惜的是這些js新特性只被最新版本的瀏覽器支援,但是低版本瀏覽器並不支援,那麼低版本瀏覽器下就需要一個轉換工具,把es6程式碼轉換成瀏覽器能識別的程式碼,babel就是這樣的一個工具。可以理解為 babel是javascript語法的編譯器。
2. Babel編譯器
在Babel執行編譯的過程中,會從專案的根目錄下的 .babelrc檔案中讀取配置。.babelrc是一個json格式的檔案。
在.babelrc配置檔案中,主要是對預設(presets) 和 外掛(plugins) 進行配置。.babelrc配置檔案一般為如下:
{ "plugins": [ [ "transform-runtime", { "polyfill": false } ] ], "presets": [ [ "env", { "modules": false } ], "stage-2", "react" ] }
2.1 plugins
該屬性是告訴babel要使用那些外掛,這些外掛可以控制如何轉換程式碼。
1. 理解 babel-polyfill 和 babel-runtime 及 babel-plugin-transform-runtime
Babel預設只轉換新的javascript語法,而不轉換新的API,比如 Iterator, Generator, Set, Maps, Proxy, Reflect,Symbol,Promise 等全域性物件。以及一些在全域性物件上的方法(比如 Object.assign)都不會轉碼。
比如說,ES6在Array物件上新增了Array.form方法,Babel就不會轉碼這個方法,如果想讓這個方法執行,必須使用 babel-polyfill來轉換等。
因此:babel-polyfill和babel-runtime就是為了解決新的API與這種全域性物件或全域性物件方法不足的問題,因此可以使用這兩個外掛可以轉換的。
那麼他們兩者的區別是什麼?
babel-polyfill 的原理是當執行環境中並沒有實現的一些方法,babel-polyfill會做相容。
babel-runtime 它是將es6編譯成es5去執行。我們使用es6的語法來編寫,最終會通過babel-runtime編譯成es5.也就是說,不管瀏覽器是否支援ES6,只要是ES6的語法,它都會進行轉碼成ES5.所以就有很多冗餘的程式碼。
babel-polyfill 它是通過向全域性物件和內建物件的prototype上新增方法來實現的。比如執行環境中不支援Array.prototype.find 方法,引入polyfill, 我們就可以使用es6方法來編寫了,但是缺點就是會造成全域性空間汙染。
babel-runtime: 它不會汙染全域性物件和內建物件的原型,比如說我們需要Promise,我們只需要import Promise from 'babel-runtime/core-js/promise'即可,這樣不僅避免汙染全域性物件,而且可以減少不必要的程式碼。
雖然 babel-runtime 可以解決 babel-polyfill中的避免汙染全域性物件,但是它自己也有缺點的,比如上,如果我現在有100個檔案甚至更多的話,難道我們需要一個個檔案加import Promise from 'babel-runtime/core-js/promise' 嗎?那這樣肯定是不行的,因此這個時候出來一個 叫 babel-plugin-transform-runtime,
它就可以幫助我們去避免手動引入 import的痛苦,並且它還做了公用方法的抽離。比如說我們有100個模組都使用promise,但是promise的polyfill僅僅存在1份。
這就是 babel-plugin-transform-runtime 外掛的作用。
2. 理解 babel-plugin-transform-runtime 的配置一些選項
因此通過上面的理解,我們可以對 transform-runtime 通過如下配置 plugins; 如下程式碼:
{ 'plugins': [ [ 'transform-runtime', { 'helpers': false, 'polyfill': false, 'regenerator': true, 'moduleName': 'babel-runtime' } ] ] }
配置項可以看官網,檢視官網
helpers: 預設值為true,表示是否開啟內聯的babel helpers(即babel或者環境本來存在的某些物件方法函式)如:extends,etc這樣的
在呼叫模組名字時將被替換名字。
polyfill:預設值為true,表示是否把內建的東西(Promise, Set, Map)等轉換成非全域性汙染的。
regenerator:預設值為true,是否開啟generator函式轉換成使用regenerator runtime來避免汙染全域性域。
moduleName:預設值為 babel-runtime,當呼叫輔助 設定模組(module)名字/路徑.
比如如下這樣設定:
{ "moduleName": "flavortown/runtime" }
import引入檔案如下這個樣子:
import extends from 'flavortown/runtime/helpers/extends';
3 presets
presets屬性告訴Babel要轉換的原始碼使用了哪些新的語法特性,presets是一組Plugins的集合。
3.1 理解 babel-preset-env
比如:
babel-preset-es2015: 可以將es6的程式碼編譯成es5.
babel-preset-es2016: 可以將es7的程式碼編譯為es6.
babel-preset-es2017: 可以將es8的程式碼編譯為es7.
babel-preset-latest: 支援現有所有ECMAScript版本的新特性。
舉個列子,比如我們需要轉換es6語法,我們可以在 .babelrc的plugins中按需引入一下外掛,比如:
check-es2015-constants、es2015-arrow-functions、es2015-block-scoped-functions等等幾十個不同作用的plugin:
那麼配置項可能是如下方式:
// .babelrc { "plugins": [ "check-es2015-constants", "es2015-arrow-functions", "es2015-block-scoped-functions", // ... ] }
但是Babel團隊為了方便,將同屬ES2015的幾十個Transform Plugins集合到babel-preset-es2015一個Preset中,這樣我們只需要在.babelrc的presets加入es2015一個配置就可以完成全部ES2015語法的支援了:
如下配置:
// .babelrc { "presets": [ "es2015" ] }
但是我們隨著時間的推移,將來可能會有跟多的版本外掛,比如 bebel-preset-es2018,.... 等等。
因此 babel-preset-env 出現了,它的功能類似於 babel-preset-latest,它會根據目標環境選擇不支援的新特性來轉譯。
首先需要在專案中安裝,如下命令:
npm install babel-preset-env --save-dev
在.babelrc配置檔案中 可以如下簡單的配置:
{ "presets": ['env'] }
我們還可以僅僅配置專案所支援的瀏覽器的配置
1. 支援每個瀏覽器最後兩個版本和safari大於等於7版本所需的polyfill程式碼轉換,我們可以如下配置:
{ 'presets': [ ['env', { 'target': { 'browsers': ['last 2 versions', 'safari >= 7'] } }] ] }
2. 支援市場份額超過5%的瀏覽器,可以如下配置:
{ 'presets': [ ['env', { 'target': { 'browsers': '> 5%' } }] ] }
3. 指定瀏覽器版本,可以如下配置:
{ 'presets': [ ['env', { 'target': { 'chrome': 56 } }] ] }
Node.js
如果通過Babel編譯Node.js程式碼的話,可以設定 "target.node" 是 'current', 含義是 支援的是當前執行版本的nodejs。
如下配置程式碼:
{ "presets": [ ["env", { "targets": { "node": "current" } }] ] }
理解 babel-preset-env 中的選項配置:
1. targets: {[string]: number | string }, 預設為{};
含義是支援一個執行環境的物件,比如支援node版本;可以如下配置: node: '6.0';
執行環境: chrome, opera, edge, firefox, safari, ie, ios, android, node, electron
2. targets.browsers <Array | string>
支援瀏覽器的配置項,該配置項使用方式可以到 browserslist來查詢(https://github.com/browserslist/browserslist)
比如上面的 支援每個瀏覽器最後兩個版本和safari大於等於7版本。如上配置。
3. modules
該引數的含義是:啟用將ES6模組語法轉換為另一種模組型別。將該設定為false就不會轉換模組。預設為 'commonjs'.
該值可以有如下:
'amd' | 'umd' | 'systemjs' | 'commonjs' | false
我們在專案中一般會看到如下配置,設定modules: false, 如下程式碼配置:
"presets": [ 'env', { 'modules': false } ]
這樣做的目的是:以前我們需要使用babel來將ES6的模組語法轉換為AMD, CommonJS,UMD之類的模組化標準語法,但是現在webpack都幫我做了這件事了,所以我們不需要babel來做,因此需要在babel配置項中設定modules為false,因為它預設值是commonjs, 否則的話,會產生衝突。
4. loose, 該引數值預設為false。
含義是:允許它們為這個 preset 的任何外掛啟用”loose” 轉換。
5. include: 包含一些外掛,預設為 [];
比如包含箭頭函式,可以如下配置:
{ "presets": [ ["env", { "targets": { "browsers": ["last 2 versions", "safari >= 7"] }, "include": ["transform-es2015-arrow-functions", "es6.map"] }] ] }
6. exclude; 排除哪些外掛,預設為 [];
比如 排除生成器,可以如下配置:
{ "presets": [ ["env", { "targets": { "browsers": ["last 2 versions", "safari >= 7"] }, "exclude": ["transform-regenerator", "es6.set"] }] ] }
3.2 理解 babel-presets-stage-x
官方預設(preset), 有兩種,一個是按年份(babel-preset-es2017),一個是按階段(babel-preset-stage-0)。 這主要是根據TC39 委員會ECMASCRPIT 釋出流程來制定的。因此到目前為止 有4個不同的階段預設:
babel-preset-stage-0 babel-preset-stage-1 babel-preset-stage-2 babel-preset-stage-3
以上每種預設都依賴於緊隨的後期階段預設,數字越小,階段越靠後,存在依賴關係。也就是說stage-0是包括stage-1的,以此類推。因此 stage-0包含stage-1/2/3的內容。所以如果我們不知道需要哪個stage-x的話,直接引入stage-0就好了。
stage0 (https://babeljs.io/docs/en/babel-preset-stage-0) 只是一個美好激進的想法,一些 Babel 外掛實現了對這些特性的支援 ,但是不確定是否會被定為標準.
stage1 (https://babeljs.io/docs/en/babel-preset-stage-1) 值得被納入標準的特性.
stage2 (https://babeljs.io/docs/en/babel-preset-stage-2) 該特性規範己經被起草,將會被納入標準裡.
stage3 (https://babeljs.io/docs/en/babel-preset-stage-3) 該特性規範已經定稿,大瀏覽器廠商和 Node.js 社群己開始著手實現.
但是在我們使用的時候只需要安裝你想要的階段就可以了:比如 babel-preset-stage-2, 安裝命令如下:
npm install --save-dev babel-preset-stage-2
二:在webpack中配置babel
在上面瞭解了babel後,現在我們需要知道如何在webpack中使用它了。由於babel所做的事情是轉換程式碼,所有需要使用loader去轉換,因此我們需要配置babel-loader。
在安裝babel-loader之前,我們需要安裝babel-core, 因為babel-core是Babel編譯器的核心,因此也就意味著如果我們需要使用babel-loader進行es6轉碼的話,我們首先需要安裝 babel-core, 安裝命令如下即可:
npm install --save-dev babel-core
然後我們再安裝 babel-loader, 命令如下:
npm install --save-dev babel-loader
接著我們需要安裝 babel-preset-env, babel-plugin-transform-runtime, babel-preset-stage-2, 如下命令安裝
npm install --save-dev babel-preset-env babel-plugin-transform-runtime babel-preset-stage-2
因此 .babelrc 配置如下即可:
{ "plugins": [ [ "transform-runtime", { "polyfill": false } ] ], "presets": [ [ "env", { "modules": false } ], "stage-2" ] }
在做demo之前,我們還是先看下目錄結構變成如下:
### 目錄結構如下: demo1 # 工程名 | |--- dist # 打包後生成的目錄檔案 | |--- node_modules # 所有的依賴包 | |--- js # 存放所有js檔案 | | |-- demo1.js | | |-- main.js # js入口檔案 | | | |--- webpack.config.js # webpack配置檔案 | |--- index.html # html檔案 | |--- styles # 存放所有的css樣式檔案 | |--- .gitignore | |--- README.md | |--- package.json | |--- .babelrc # babel轉碼檔案
因此webpack配置中需要新增 babel-loader 配置,如下配置:
module.exports = { module: { rules: [ { test: /\.js$/, exclude: /(node_modules)/, // 排除檔案 loader: 'babel-loader' } ] } }
webpack 所有配置如下程式碼
const path = require('path'); // 提取css的外掛 const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ClearWebpackPlugin = require('clean-webpack-plugin'); module.exports = { entry: './js/main.js', output: { filename: 'bundle.js', // 將輸出的檔案都放在dist目錄下 path: path.resolve(__dirname, 'dist'), publicPath: '/dist' }, mode: 'development', module: { rules: [ { // 使用正則去匹配要用該loader轉換的css檔案 test: /\.css$/, loaders: ExtractTextPlugin.extract({ // 轉換 .css檔案需要使用的Loader use: ['css-loader'] }) }, { test: /\.(png|jpg)$/, loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]' } }, { test: /\.js$/, exclude: /(node_modules)/, // 排除檔案 loader: 'babel-loader' } ] }, resolve: { // modules: ['plugin', 'js'] }, externals: { jquery: 'jQuery' }, devtool: 'source-map', plugins: [ // new ClearWebpackPlugin(['dist']), new ExtractTextPlugin({ // 從js檔案中提取出來的 .css檔案的名稱 filename: `main.css` }) ] };
package.json 安裝依賴包如下:
{ "name": "demo1", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline", "build": "webpack --progress --colors" }, "devDependencies": { "babel-core": "^6.26.3", "babel-loader": "^7.1.5", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.7.0", "babel-preset-stage-2": "^6.24.1", "clean-webpack-plugin": "^0.1.19", "css-loader": "^1.0.0", "extract-text-webpack-plugin": "^4.0.0-beta.0", "file-loader": "^1.1.11", "path": "^0.12.7", "style-loader": "^0.21.0", "uglifyjs-webpack-plugin": "^1.2.7", "url-loader": "^1.0.1", "webpack": "^4.16.1", "webpack-cli": "^3.0.8", "webpack-dev-server": "^3.1.4" }, "dependencies": { "axios": "^0.18.0", "jquery": "^3.3.1" } }
現在我們繼續在 main.js 程式碼內 編寫 Generator 函式,程式碼如下:
function* g() { yield 'a'; yield 'b'; yield 'c'; return 'ending'; } var gen = g(); console.log(gen.next()); // 返回Object {value: "a", done: false} for(let a of [1,2,3,4]) { console.log(a); // 列印出 1, 2, 3, 4 }
然後重新執行打包命令 npm run dev 後,開啟瀏覽器執行 可以看到控制檯輸出 {value: "a", done: false},說明babel已經轉譯了。