webpack進階之路三、(實戰一,使用ES6、ts、Flow、SCSS)
一、使用新語言來開發專案
1、使用ES6語言
通常我們需要將採用ES6編寫的程式碼轉換成目前已經支援良好的ES5程式碼,包含如下:
- 將新的ES6語法用ES5實現,例如ES6的class語法用ES5的prototype實現;
- 為新的API注入polyfill,例如使用新的fetch API時在注入對應的polyfill後才能讓低端瀏覽器正常執行。
-
認識Babel
Babel(https://babeljs.io) 可以方便地完成以上兩件事。Babel是一個JavaScript編譯器,能將ES6程式碼轉為ES5程式碼轉為ES5程式碼,讓我們使用最新的語言特性而不用擔心相容性問題,並且可以通過外掛機制根據需求靈活地擴充套件。在Babel執行編譯的過程中,會從專案根目錄下的 .babelrc 檔案中讀取配置。 .babelrc是一個JSON格式檔案,內容大致如下:{ "plugins": [ [ "transform-runtime", { "polyfill": false } ] ], "presets": [ [ "es2015", { "modules": false } ], "stage-2", "react" ] }
1.1 Plugins
plugins屬性告訴Babel要使用哪些外掛,這些外掛可以控制如何轉換程式碼。
以上配置檔案裡的transform-runtime
對應的外掛全名叫作babel-plugin-transform-runtime
,即在前面加上了babel-plufing-
npm i -D babel-plugin-transform-runtime
babel-plufin-transform-runtime
是Babel官方提供的一個外掛,作用是減少冗餘的程式碼。Babel在將ES6程式碼轉換成ES5程式碼時,通常需要一些由ES5編寫的輔助函式來完成新語法的實現,例如轉換class extent
語法時會在轉換後的ES5程式碼裡注入_extent
輔助函式用於實現繼承:function _extent(target){ for(var i = 1; i < arguments.length; i++){ var source = arguments[i]; for(var key in source){ if(Object.prototype.hasOwnProperty.call(source, key)){ target[key] = source[key]; } } } return target; }
這個導致每個使用
class extent
語法的檔案都被注入重複的_extent輔助函式程式碼,babel-plufin-transform-runtime
的作用在於將原本注入JavaScript檔案裡的輔助函式替換成一條匯入語句:
var _extent = require('babel-runtime/helpers/_extent');
這樣能減少Babel編譯出來的程式碼的檔案大小。
同時需要注意的是,由於babel-plugin-transform-runtime
注入了require('babel-runtime/helpers/_extent')
語句到編譯後的程式碼裡,需要安裝babel-runtime
依賴到我們的專案後,程式碼才能正常執行。也就是說babel-plugin-tranform-runtime
和babel-runtime
需要配套使用,在使用babel-plugin-transform-runtime
後一定需要使用babel-runtime
。
1.2 Presets
presets屬性告訴Babel要轉換的原始碼使用了哪些新的語法特性,一個Presets對一組新語法的特性提供了支援,多個Presets可以疊加。Presets其實是一組Plugins的集合,每個Plugin完成一個新語法的轉換工作。Presets是按照ECMAScript草案來組織的,通常可以分為三大類。
(1)已經被寫入ECMAScript標準裡的特性,由於之前每年都有新特性被加入到標準裡,所以又可細分如下。
+ ES2015(https://babeljs.io/docs/plugins/preset-es2015/): 包含在2015年加入的新特性。
+ ES2016(https://babeljs.io/docs/plugins/preset-es2016/): 包含在2016年加入的新特性。
+ ES2017 (https://babeljs.io/docs/plugins/preset-es2017/): 包含在2017年加入的新特性。
+ Env(https://babeljs.io/docs/plugins/preset-env),包含當前所有ECMAScript標準裡的最新特性。 -
接入Babel
在瞭解Babel後,下一步就需要知道如何在Webpack中使用它。由於Babel所做的事情是轉換程式碼,所以應該通過Loader去接入Babel。Webpack的配置如下:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
},
]
},
// 輸出source-map以方便直接除錯ES6原始碼
devtool: 'source-map'
}
以上配置命中了專案目錄下的所有JavaScript檔案,並通過babel-loader呼叫Babel完成轉換工作。在重新執行構建前,需要先安裝新引入的依賴:
# Webpack 接入Babel必須依賴的模組
npm i -D babel-core babel-loader
# 根據我們的需求選擇不同的Plugins或Presets
npm i -D babel-preset-env
2. 使用TypeScript語言
- 認識TypeScript
TypeScript是JavaScript的一個超集,主要提供了型別檢查系統和對ES6語法的支援,但不支援新的API。目前沒有任何環境支援執行原生的TypeScript程式碼,必須通過構建將它轉換成JavaScript程式碼後才能執行。
下面改造一下前面用過的例子,用TypeScript重寫JavaScript。由於TypeScript是JavaScript的超集,直接將字尾.js改成.ts是可以的。但為了體現出TypeScript的不同,我們在這裡重寫JavaScript程式碼,並加入型別檢查:
// show.ts
// 通過DOM元素,將content顯示到網頁上
// 通過ES6模組規範匯出show函式
// 為show函式增加型別檢查
export function show(content: string){
window.document.getElementById('app').innerText = 'Hello,' + content;
}
// main.ts
// 通過ES6模組規範匯入show函式
import { show } from './show';
// 執行show函式
show('Webpack');
TypeScript官方提供了能將TypeScript轉換成JavaScript的編譯器。我們需要在當前專案的根目錄下新建一個用於配置編譯選項的tsconfig.json檔案,編譯器預設會讀取和使用這個檔案,配置檔案的內容大致如下:
{
“compilerOptions”: {
"module": "commonjs", // 編譯出的程式碼採用的模組規範
"target": "es5", // 編譯出的程式碼採用ES的哪個版本
"sourceMap": true // 輸出Source Map以方便除錯
},
"exclude": [ // 不編譯這些目錄裡的檔案
"node_modules"
]
}
通過npm install -g typescript 安裝編譯器到全域性後,可以通過tschello.ts命令編譯出hello.js和hello.js.map檔案。
- 減少程式碼冗餘
TypeScript編譯器會有與3.1節中Babel同樣的問題:在將ES6語法轉換成ES5語法時需要注入輔助函式。為了不讓同樣的輔助函式重複出現在多個檔案中,可以開啟TypeScript編譯器的importHelpers選項,需要修改tsconfig.json檔案如下:
{
"compilerOptions": {
"importHelpers": true
}
}
該選項的原理和Babel中介紹的babel-plugin-transform-runtime非常類似,會將輔助函式轉換成如下匯入語句:
var _tslib = require('tslib');
_tslib._extend(target);
這會導致編譯出的程式碼依賴tslib這個迷你庫,但避免了程式碼冗餘。
- 整合Webpack
要讓Webpack支援TypeScript,需要解決以下兩個問題。
- 通過Loader將TypeScript轉換成JavaScript。
- Webpack在尋找模組對應的檔案時需要嘗試ts字尾。
我們需要修改預設的resolve.extensions配置項。
相關的Webpack配置如下:
const path = require('path');
module.exports = {
// 執行入口檔案
entry: './main',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
resolve: {
// 先嚐試以ts為字尾的TypeScript原始碼檔案
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader'
}
]
},
devtool: 'source-map', // 輸出Source Map以方便在瀏覽器裡除錯TypeScript程式碼
}
在執行構建前需要安裝上面用到的依賴:
npm i -D typescript awesome-typescript-loader
安裝成功後重新執行構建,我們將會在dist目錄下看到輸出的JavaScript檔案bundle.js,以及對應的Source Map檔案bundle.js.map。在瀏覽器裡開啟index.html頁面後,可以在開發工具裡看到和除錯用TypeScript編寫的原始碼。
3. 使用Flow檢查器
- 認識Flow
Flow是Facebook開源的一個JavaScript靜態型別檢查器,它是JavaScript語言的超集。我們所需要做的就是在需要的地方加上型別檢查,例如在兩個由不同的人開發的模組對接的介面處加上靜態型別檢查,就能在編譯階段指出部分模組使用不當的問題。同時,Flow能通過型別推斷檢查出在JavaScript程式碼中潛在的Bug。
Flow的使用效果如下:
需要注意的是,該段程式碼的第一行//@flow告訴Flow檢查器這個檔案需要被檢查。// @flow // 靜態型別檢查 function squarel(n: number): number { return n * n; } squarel('2'); // Error: squarel需要傳入number作為引數 // 型別推斷檢查 function square2(n){ return n * n; // Error: 傳入的string型別不能做乘法運算 } square2('2');
- 使用Flow
以上只是讓我們瞭解Flow的功能,下面講解如何執行Flow來檢查程式碼。Flow檢測器由高效能且跨平臺的OCaml(http://ocaml.org)語言編寫,它的可執行檔案可以通過npm i -D flow-bin
安裝,安裝完成後可先配置Npm Script:
再通過"scripts": { "flow": "flow" }
npm run flow
去呼叫Flow執行程式碼檢查。
除此之外,我們還可以通過npm i -g flow-bin
將Flow安裝到全域性,再直接通過flow命令執行程式碼檢查。
安裝成功後,在專案根目錄下執行Flow, Flow會遍歷出所有需要檢查的檔案並對其進行檢查,輸出錯誤結果到控制檯,例如:
採用了Flow靜態型別語法的JavaScript,是無法直接在目前已有的JavaScript引擎中執行的,要讓程式碼可以執行,需要將這些靜態型別的語法去掉。例如:Error: show.js:6 6: export function show(content){ ^^^^^^^ parameter `content`. Missing annotation Found 1 error }
有兩種方式可以做到這一點。// 採用Flow的原始碼 function foo(one: any, two: number, three?): string{} // 去掉靜態型別語法後輸出程式碼 function foo(one, two, three){}
- 整合Webpack
由於使用了Flow專案一般都會使用ES6語法,所以將Flow整合到使用Webpack構建的專案裡的最方便方法是藉助Babel。下面修改3.1節中的程式碼,為其加入Flow程式碼檢查,改動如下。
(1)安裝npm i -D babel-preset-flow
依賴到專案。
(2)修改.babelrc配置檔案,加入Flow Preset:
"presets": [ ...[], "flow" ]
向原始碼里加入靜態型別後重新構建專案,我們會發現採用了Flow的原始碼還是能在瀏覽器中正常執行的。
要明確構建的目的只是去除原始碼中的Flow靜態型別語法,而程式碼檢查和構建無關,許多編輯器已經整合了Flow,可以實時在程式碼中高亮顯示Flow檢查出的問題。
4. 使用SCSS語言
- 認識SCSS
SCSS(http://sass-lang.com)可以讓我們用更靈活的方式寫CSS。它是一種CSS前處理器,語法和CSS相似,但加入了變數、邏輯等程式設計元素,程式碼類似這樣:
SCSS又叫作SASS,區別在於SASS語法類似於Ruby,而SCSS語法類似於CSS,熟悉CSS的前端工程師會更喜歡SCSS。$blue: #1875e7; div{ color: $blue; }
採用SCSS去寫CSS的好處在於,可以方便的管理程式碼,抽離公共的部分,通過邏輯寫出更靈活的程式碼。和SCSS類似的CSS前處理器還有LESS(http://lesscss.org)等。
使用SCSS可以提升編碼的效率,但是必須將SCSS原始碼編譯成可以直接在瀏覽器環境下執行的CSS程式碼。SCSS官方提供了以多種語言實現的編譯器,由於本書更傾向於前端工程師使用的技術棧,所以主要介紹node-sass(http://github.com/sass/node-sass)。
node-sass的核心模組是用C++編寫的,再用Node.js封裝了一層,以提供給其他Node.js呼叫。node-sass還支援通過命令進行呼叫,先將它安裝到全域性:
再執行編譯命令:npm i -g node-sass
就能在原始碼同目錄下看到編譯後的main.css檔案。# 將main.scss原始檔編譯成main.css node-sass main.scss main.css
- 接入Webpack
我們曾在1.4節介紹過將SCSS原始碼轉換成CSS程式碼的最佳方式是使用Loader,Webpack官方提供了對應的sass-loader(https://github.com/webpack-contrib/sass-loader)。
Webpack接入sass-loader的相關配置如下:
以上配置通過正則/.scss/匹配所有以.scss為字尾的SCSS檔案,再分別使用3個Loader去處理。具體處理流程如下。module.exports = { module: { rules: [ { // 增加對SCSS檔案的支援, test: /\.scss/, // SCSS檔案的處理順序為先sass-loader,再css-loader,再style-loader use: ['style-loader', 'css-loader', 'sass-loader'], }, ] } }
- 通過sass-loader將SCSS原始碼轉換為CSS程式碼,再將CSS程式碼交給css-loader處理。
- css-loader會找出CSS程式碼中@import和url()這樣的匯入語句,告訴Webpack依賴這些資源。同時支援CSS Module、壓縮CSS等功能。處理完後再將結果交給style-loader處理。
- style-loader會將CSS程式碼轉換成字串後,注入JavaScript程式碼中,通過JavaScript向DOM增加樣式。如果我們想將CSS程式碼提取到一個單獨的檔案中,而不是和JavaScript混在一起,則可以使用在1.5節中介紹過的ExtractTextPlugin。
由於接入sass-loader,所以專案需要安裝這些新的依賴:
# 安裝Webpack Loader依賴 npm i -D sass-loader css-loader style-loader # sass-loader依賴node-sass npm i -D node-sass
本文為學習筆記:來源《深入淺出Webpack》