深入淺出Webpack

rickchen發表於2018-04-09

核心概念

  • Entry:入口,Webpack執行構建的第一步將從Entry開始,可抽象成輸入。
  • Module:模組,在Webpack裡一切皆模組,一個模組對應一個檔案。Webpack會從配置的Entry開始遞迴找出所有依賴的模組。
  • Chunk:程式碼塊,一個Chunk由多個模組組合而成,用於程式碼合併與分割。
  • Loader:模組轉換器,用於將模組的原內容按照需求轉換成新內容。
  • Plugin:擴充外掛,在wp構建流程中的特定時機注入擴充邏輯。
  • Output:輸出結果,在Webpack經過一系列處理並得出最終想要的程式碼後輸出結果.

Entry

入口,Webpack執行構建的第一步將從Entry開始,可抽象成輸入。

單頁面單入口檔案配置

module.exports = {
	entry: './path/to/my/entry/file.js'
}
複製程式碼

多頁面多入口檔案配置

module.exports = {
	entry: {
		one: './path/one.js', 
		two: './path/two.js'
	},
	plugins:[
		new HtmlWebpackPlugin({
			title:'第一個頁面',
			template: './pages/one.html', // 指定第一個頁面的模板
			filename: './pages/one.html', // 指定第一個頁面打包完成後的檔名
			chunks: ['one','two'] // 指定第一個頁面要打包進入的js
		}),	
		new HtmlWebpackPlugin({
			title:'第二個頁面',
			template: './pages/two.html', // 指定第二個頁面的模板
			filename: './pages/two.html', // 指定第二個頁面打包完成後的檔名
			chunks: ['one','two'] // 指定第二個頁面要打包進入的js
		}),	
	]
}
複製程式碼

Webpack構建單頁面和多頁面的例項

網上關於單頁面和多頁面的優點和缺點都有比較詳細的描述,具體需要應用單頁面還是多頁面得根據專案的需求來選擇。

一般單頁面的配置都有相應的腳手架,比如vue-cli,整合了wp,減少了配置webpack的很多繁瑣的工作

多頁面應用現在沒有腳手架,可以進行配置,具體的例項可參考這篇:

juejin.im/post/5a2257…

多頁面想向單頁面實現元件共用和封裝,可以參考這篇進行配置,需要引入ejs模板,參考這篇:

juejin.im/post/5a35f6…

Module

模組,在Webpack裡一切皆模組,一個模組對應一個檔案。Webpack會從配置的Entry開始遞迴找出所有依賴的模組。 ####配置Loader

  • test:匹配要進行轉換的檔案,使用正規表示式來匹配。

  • include: 只包含指定目錄的檔案進行轉換,加快webpack的編譯速度。

  • exclude: 排除某個檔案的轉換,加快編譯和搜尋速度。

  • use:對use後面加引數,比如進行快取和壓縮,也可以加快編譯的速度。

      module:{
      	rules: [
      		{
      			//解析js檔案
      			test: /\.js$/,
      			// 用babel-loader轉換js檔案
      			 // ?cacheDirectory表示傳給babel-loader的引數,用於快取babel的編譯結果,加快重新編譯的速度
      			use: ['babel-loader?cacheDirectory']
      			 // 只命中src目錄裡的Js檔案,加快webpack的編譯速度
      			 include: path.resolve(_dirname,'src')
      		},
      		{
      			//解析Scss檔案
      			test: /\.scss$/,
      			// 使用一組loader去處理scss檔案
      			// 處理順序為從後到前,即先交給scss-loader處理,再將結果交給css-loader,最後交給style-loader
      			use: ['style-loader','css-loader','sass-loader'],
      			// 排除node_modules目錄下的檔案
      			exclude: path.resolve(__dirname,'node_modules')
      		},
      		{
      			// 對非文字檔案採用file-loader載入
      			test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,
      			use: ['file-loader']
      		}
      	]
      }
    複製程式碼

在上面的例子中,test、include、exclude只傳入了一個字串或正則,其實他們也支援陣列型別

{
	test:[
		/\.jsx?$/,
		/\.tsx?$/
	],
	include:[
		path.resolve(__dirname,'src'),
		path.resolve(__dirname,'tests')
	],
	exclude:[
		path.resolve(__dirname, 'node_modiles'),
		path.resolve(__dirname, 'bower_modules')
	]
}
複製程式碼

noParse

noPaese配置項可以讓Webpack忽略對部分沒采用模組化的檔案的遞迴解析和處理,這樣做的好處是能提高構建效能。 原因是一些庫如jQuery,ChartJS龐大又沒有采用模組化的標準,讓Webpack去解析這些檔案既耗時又沒有意義。

noParse: /jquery|chartjs/
複製程式碼
使用函式,從Webpack3.0.0開始支援
noParse: (content) => {
	//content代表一個模組的檔案路徑
	//返回true或false
	return /jquery|chartjs/.test(content)
}
複製程式碼

parse

因為Webpack是以模組化的js檔案為入口的,所以內建了對模組化js的解析功能,支援AMD,CommonJS,SystemJS,ES6 parse屬性可以更細粒度地配置哪些模組語法被解析,哪些不被解析。

同noParse配置項的區別在於.parser可以精確到語法層面,而noParse只能控制哪些檔案不被解析。

parse的使用方法如下:

modele:{
	rules:[
		test: /\.js$/,
		use: ['babel-loader'],
		parser: {
			amd: false, //禁用AMD
			commonjs: false, // 禁用CommonJS
			system: false, // 禁用 SystemJS
			harmony: false, // 禁用ES6 import/export
			requireInclude: false, // 禁用requireInclude
			requireEnsure: false, // 禁用requireEnsure
			requireContext: false, // 禁用requireContext
			browserify: false,  // 禁用browserify
			requireJs: false // 禁用requirejs
		}
	]
}
複製程式碼

Loader

模組轉換器,用於將模組的原內容按照需求轉換成新內容。

  • Loader的執行順序是由後到前的。

  • 每Loader都可以通過URL querystring的方式傳入引數,例如 css-loader?minimize中的minimize告訴css-loader要開啟css壓縮。

  • 向loader中傳入屬性的方式除了可以通過querystring實現,還可以通過object實現。

      user:[
      	'style-loader',{
      		loader:'css-loader',
      		options:{
      			minimize:true
      		}
      	}
      ]
    複製程式碼

在Loader需要傳入很多引數時,我們還可以通過一個Object來描述,例如在上面的babel-loader配置中有如下程式碼

use:[
	{
		loader:'babel-loader',
		options:{
			cacheDirectory:true
		},
		// enforce: 'post'的含義是將該loader的執行順序放到最後
		// enforce: 'pre'的含義是將loader的執行順序放到最前面
	}
]
複製程式碼

Plugin

擴充外掛,在Webpack構建流程中的特定時機注入擴充邏輯。

Plugin的配置很簡單,plugins的配置項接受一個陣列,陣列裡的每一項都是一個要使用的Plugin的例項,Plugin需要的引數通過建構函式傳入。

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin')
module.exports = {
	plugins: [
		// 所有頁面都會用到的公共程式碼被提取到common程式碼塊中
		new CommonsChunkPlugin({
			name: 'common',
			chunks: ['a','b']
		})
	]
}
複製程式碼

將css檔案單獨提取出來

	const ExtractTextPluginn = require('extract-text-webpack-plugin')

	module:{
		reules:[
			{
				textL /\.css$/,
				loaders: ExtractTextPlugin.extract({
					use:[
						'css-loader'
					]
				}),
			}
		]
	},
	plugins: [
		new ExtractTextPlugin({
			//從.js檔案中提取出來的.css檔案的名稱
			filename: `[name]_[contenthash:8].css`
		})	
	]
複製程式碼

Resolve

webpack在啟動後會從配置的入口模組出發找出所有依賴的模組,resolve配置webpack如何查詢模組所對應的檔案

alias

resolve.alias配置項通過別名來將原匯入路徑對映成一個新的匯入路徑。例如使用一下配置:

resolve:{
	alias:{
		components: './src/components'
	}
}
複製程式碼

當通過**import Button from 'components/button'**匯入時

實際上被alias等價替換成了import Button from './src/components/button'

extensions

在匯入語句沒帶檔案字尾時,Webpack會自動帶上字尾後去嘗試訪問檔案是否存在。

resolve.extensions用於配置在嘗試過程中用到的字尾列表,預設是:

extensionsL ['.js','.json']
複製程式碼

也就是說,當遇到require('./data')這樣的匯入語句時,Webpack會先尋找./data.js檔案,如果該檔案不存在,就去找./data.json檔案,如果還是找不到就報錯。

escriptionFiles

如果resolve.enforceExtension被配置為true,則所有匯入語句都必須帶檔案字尾,例如開啟前import './foo'能正常工作,開啟後就必須寫成**import './foo.js' **

Devtool

devltool配置Webpack如何生成source map,預設值是false,即不生成source map,若想構建出的程式碼生成source map以方便除錯,則可以這樣配置:

module.export = {
	devtool: 'source-map'
}
複製程式碼

開啟source-map會方便我們開發中的除錯,方便我們定位到具體的程式碼問題,當也會影響一下相關的構建效能問題,所有要做出多配置檔案,開發環境配置和生產環境配置

source-map模式下會輸出質量最高且最詳細的Source Map,這會造成構建速度緩慢,特別是在開發過程中需要頻繁修改時會增加等待時間

在Source-Map模式下會將Source Map暴露,若構建釋出到線上的程式碼的source map暴力,就等同於原始碼被洩露

為了解決以上兩個問題,可以這樣做,如下所述

  • 在開發環境下devtool設定成cheap-module-eval-source-map,因為生成這種source map的速度最快,能加速構建。由於在開發環境下不會做程式碼壓縮,所以在source map的即使沒有列資訊,也不會影響斷電除錯.

  • 在生產環境下將devtool設定成hidden-source-map,意思是生成最詳細的source map,但不會將source map暴露出去。由於生產環境下會做程式碼壓縮,一個js檔案只有一行,所以需要列資訊。

在生產環境下通常不會將Source Map上傳到http伺服器讓使用者獲取,而是上傳到JavaScript錯誤收集系統,在錯誤收集系統上根據Source Map和收集到的JavaScript執行錯誤隊棧,計算出錯誤所在原始碼的位置。

不要在生產環境下使用inline模式的Source Map,因為這會使JavaSctipt檔案變的很大,而且會洩露原始碼。

Externals

External用來告訴Webpack要構建的程式碼中使用了哪些不用被打包的模組,也就是說這些模板是外部環境提供的,Webpack在打包時可以忽略它們

通過externals可以告訴Webpack在js執行環境中已經內建了哪些全域性變數,不用將這些全域性變數打包到程式碼中而是直接使用它們。

moudle.export = {
	externals: {
		//將匯入語句裡的jquery替換成執行環境裡的全域性變數jQuery
		jquery: 'jQuery'
	}
}
複製程式碼

Webpack優化

  • 優化開發體驗

      優化開發體驗的目的是提升開發效率,減少每次構建的耗時
      1. 優化構建速度
      2. 優化使用體驗,通過自動化手段完成一些重複的工資哦,讓我們專注於解決問題本身。
    複製程式碼
  • 優化輸出質量

      呈現使用者體驗更好的網頁,減少首屏載入時間,提升效能流暢度。
    
      1. 縮小檔案的搜尋範圍
      2. 優化Loader的配置,通過include去命中 只有哪些檔案去處理,通過exclude去去除哪些檔案不需要處理,比如node_module
      3. 優化resolve.modules配置
      resolve.modules用於配置Webpack去哪些目錄下尋找第三方模組。
      
      resolve.modelus的預設值是['node_modules']含義是先去當前目錄的./node_modules目錄下去找我們想找的模組,如果沒找到,就去上一級目錄../node_modules中找,再沒有就去../../node_modules中找,以此類推
      
      當安裝的第三方模組都放在專案根目錄的./node_modules目錄下時,就沒有必要按照預設的方式去一層層的尋找,可以指明存放第三方模組的絕對路徑,以減少尋找,配置如下:
      
      	module.exports = {
      		resolve: {
      			// 使用絕對路徑指明第三方模組存放的位置,以減少搜尋步驟
      			// 其中,__dirname表示當前工作目錄,也就是專案根目錄
      			modules: [path.resolve(__dirname,'node_modules')]
      		}
      	}
      4.優化resolve.alias配置,跳過遞迴解析操作
      
      5.優化resolve.extensions配置減少字尾,字尾要儘可能少,提升速度
      
      6.優化noParse配置
    複製程式碼

使用DllPlugin

包含大量複用模組的動態連結庫只需被編譯一次,在之後的構建過程中被動態連結庫包含的模組將不會被重新編譯,而是直接使用動態連結庫中的程式碼。由於動態連結庫中大多數包含的是常用的第三方模組,例如react,react-dom,所以只要不升級這些模組的版本,動態連結庫就不用重新編譯。

Webpack已經內建了對動態連結庫的支援,需要通過以下兩個內建的額外掛接入。

  • DllPlugin外掛:用於打包粗一個個單獨的鼎泰連結庫檔案.
  • DllReferecePlugin外掛:用於在主要的配置檔案中引入DllPlugin外掛打包好的動態連結庫檔案

HappyPack

執行在Node.js之上的Webpack是單執行緒模型,Happy Pack將任務分解給多個子程式去併發執行,子程式處理完後再講結果傳送給主程式,由於js是單執行緒模型,所以想要發揮多核cpu的功能,就只能通過多程式實現,而無法通過多執行緒實現。

整個Webpack構建流程中,最耗時的流程可能就是loader對檔案的轉換操作了,因為要轉換的檔案資料量巨大,而且這些轉換操作都只能一個一個地處理。HappyPack的核心原理就是將這部分任務分解到多個程式中去並行處理,從而減少總的構事件。

ParallelUglifyPlugin

原本會使用Uglifyjs去一個一個壓縮再輸出

Paralleuglifyplugin會開啟多個子執行緒,將對多個檔案的壓縮工作分配給多個子程式完成,每個子程式其實還是通過uglify去壓縮程式碼,但是變成了並行執行,所以Paralleuglifyplugin能更快地完成對多個檔案的壓縮工作

檔案監聽

檔案監聽是發現原始碼發生變化時,自動重新構建出新的輸出檔案.

讓Webpack開啟監聽模式,有如下兩種方式。

  • 在配置檔案webpack.config.js中設定watch:true
  • 在執行啟動webpack的命令時帶上--watch引數,完成的命令是webpack--watch

優化檔案的效能

忽略node_modules

module.export = {
	watchOptions:{
		ignored: /node_modules/
	}
}
複製程式碼

自動重新整理瀏覽器

開啟模組熱替換

##區分環境 區分開發環境和生產環境,指定對用的不同除錯模式Source Map,是否開啟壓縮,是否提取公共程式碼等

提取公共程式碼

相同的資源被重複載入,浪費使用者的流量和伺服器成本

頁面需要載入的資源太大,會導致網頁首屏載入緩慢,影響使用者體驗。

webpackchunkplugin
複製程式碼

程式碼分割,按需載入。

如何按需載入

  1. 在為單頁應用做按需載入優化時,一般採用以下原則

  2. 將整個網站劃分成一個個小功能,再按照每個功能的相關程度將它們分成幾類

  3. 將每一類合併為一個chunk,按需載入對應的chunk.

  4. 不要按需載入使用者首次開啟網站是需要看到的畫面所對應的功能,將其放到執行入口所在的Chunk中,以減少使用者能感知的網頁載入時間。

相關文章