1. 程式人生 > >webpack進階之路三、(實戰一,使用ES6、ts、Flow、SCSS)

webpack進階之路三、(實戰一,使用ES6、ts、Flow、SCSS)

一、使用新語言來開發專案

1、使用ES6語言

通常我們需要將採用ES6編寫的程式碼轉換成目前已經支援良好的ES5程式碼,包含如下:

  • 將新的ES6語法用ES5實現,例如ES6的class語法用ES5的prototype實現;
  • 為新的API注入polyfill,例如使用新的fetch API時在注入對應的polyfill後才能讓低端瀏覽器正常執行。
  1. 認識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-

    。要讓Babel正常執行,我們必須先安裝這個外掛: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-runtimebabel-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標準裡的最新特性。

  2. 接入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語言

  1. 認識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檔案。

  1. 減少程式碼冗餘
    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這個迷你庫,但避免了程式碼冗餘。

  1. 整合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檢查器

  1. 認識Flow
    Flow是Facebook開源的一個JavaScript靜態型別檢查器,它是JavaScript語言的超集。我們所需要做的就是在需要的地方加上型別檢查,例如在兩個由不同的人開發的模組對接的介面處加上靜態型別檢查,就能在編譯階段指出部分模組使用不當的問題。同時,Flow能通過型別推斷檢查出在JavaScript程式碼中潛在的Bug。
    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檢查器這個檔案需要被檢查。
  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會遍歷出所有需要檢查的檔案並對其進行檢查,輸出錯誤結果到控制檯,例如:
    Error: show.js:6
    	6: export function show(content){
    								^^^^^^^ parameter `content`. Missing annotation Found 1 error
    	}
    
    採用了Flow靜態型別語法的JavaScript,是無法直接在目前已有的JavaScript引擎中執行的,要讓程式碼可以執行,需要將這些靜態型別的語法去掉。例如:
    // 採用Flow的原始碼
    function foo(one: any, two: number, three?): string{}
    // 去掉靜態型別語法後輸出程式碼
    function foo(one, two, three){}
    
    有兩種方式可以做到這一點。
  3. 整合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語言

  1. 認識SCSS
    SCSS(http://sass-lang.com)可以讓我們用更靈活的方式寫CSS。它是一種CSS前處理器,語法和CSS相似,但加入了變數、邏輯等程式設計元素,程式碼類似這樣:
    $blue: #1875e7;
    div{
    	color: $blue;
    }
    
    SCSS又叫作SASS,區別在於SASS語法類似於Ruby,而SCSS語法類似於CSS,熟悉CSS的前端工程師會更喜歡SCSS。
    採用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.scss原始檔編譯成main.css
    node-sass main.scss main.css
    
    就能在原始碼同目錄下看到編譯後的main.css檔案。
  2. 接入Webpack
    我們曾在1.4節介紹過將SCSS原始碼轉換成CSS程式碼的最佳方式是使用Loader,Webpack官方提供了對應的sass-loader(https://github.com/webpack-contrib/sass-loader)。
    Webpack接入sass-loader的相關配置如下:
    module.exports = {
    	module: {
    		rules: [
    			{
    				// 增加對SCSS檔案的支援,
    				test: /\.scss/,
    				// SCSS檔案的處理順序為先sass-loader,再css-loader,再style-loader
    				use: ['style-loader', 'css-loader', 'sass-loader'],
    			},
    		]
    	}
    }
    
    以上配置通過正則/.scss/匹配所有以.scss為字尾的SCSS檔案,再分別使用3個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》