談談webpack

構建工具備不少,好比Npm Script任務執行者、Grunt也是任務執行者、Gulp基於流的自動化構建工具、Fis3百度構建工具、Webpack打包模塊化JavaScript工具和Rollup模塊打包工具。javascript

這裏談談用得很頻繁的webpack構建工具。css

基本概念

從最基本的概念開始瞭解:html

入口(entry)

entry是配置模塊的入口,必填。前端

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

出口(output)

output配置如何輸出最終想要的代碼。output是一個object,裏面包含一系列的配置項。java

output.filename配置輸出文件的名稱,爲string類型。node

output.path配置輸出文件存放在本地的目錄(路徑),必須是string類型的絕對路徑。react

path: path.resolve(__dirname, 'dist_[hash]')
複製代碼

output.publicPath配置發佈到線上資源的URL前綴,爲string類型。默認爲空字符串'',即便用相對路徑。jquery

好比須要將構建的資源上傳到CDN服務上,以便加快網頁的打開速度。配置代碼以下:webpack

filename: '[name]_[chunkhash:8].js'
publicPath: 'https://cdn.example.com/assets/'
複製代碼

發佈到線上時候,HTML中引入的JavaScript文件以下:git

<script src='https://cdn.example.com/assets/a_12345678.js'></script>
複製代碼

線上出現404錯誤的時候,看下路徑有沒有錯~

還有其餘配置請看文檔

模塊(module)

module配置如何處理模塊。

module.rules配置模塊的讀取和解析規則,一般用來配置Loader。其類型是一個數組,數組裏每一項都描述瞭如何去處理部分文件。應用一項rules時大體經過如下方式:

  1. 條件匹配:經過testincludeexclude三個配置項來命中Loader要應用規則的文件。
  2. 應用規則:對選中後的文件經過use配置項來應用Loader,能夠只應用一個Loader或者按照從後往前的順序應用一組Loader,同時還能夠給Loader傳入參數。
  3. 重置順序:一組Loader執行順序默認是從右往左執行,經過enforce選項可讓其中一個Loader的執行順序放在前面或者最後。
module: {
	rules: [
		{
			// 命中scss文件
			test: /\.scss$/,
			// 處理順序從右往左
			use: ['style-loader', 'css-loader', 'sass-loader'],
			// 排除node_modules目錄下的文件
			exclude: path.resolve(__dirname, 'node_modules'),
		}
	]
}
複製代碼

Loader須要傳入多個參數的時候的例子:

use: [
	{
		loader:'babel-loader',
		options:{
			cacheDirectory:true,
		},
		// enforce:'post' 的含義是把該 Loader 的執行順序放到最後
		// enforce 的值還能夠是 pre,表明把 Loader 的執行順序放到最前面
		enforce:'post'
	},
	// 省略其它 Loader
]
複製代碼

module.noParse配置項可讓webpack忽略對部分沒采用模塊化的文件的遞歸解析和處理,這樣作有助於提升構建性能。好比:

module: {
	noParse: (content) => /jquery|lodash/.test(content)
}
複製代碼

module.rules.parser屬性能夠更細粒度的配置哪些模塊須要解析,哪些不須要,和noParse配置項的區別在於parser能夠精確到語法層面,而noParse只能控制哪些文件不被解析。

module: {
	rules: [
		{
			test: /\.js$/,
			use: ['babel-loader'],
			parser: {
				amd: false, // 禁用 AMD
      			commonjs: false, // 禁用 CommonJS
      			...
			}
		}
	]
}
複製代碼

解析(resolve)

Resolve配置webpack如何尋找模塊所對應的文件。Webpack內置Javascript模塊化語法解析功能,默認會採用模塊化標準裏面約定好的規則去尋找,你也能夠按照需求修改默認規則。

resolve.alias配置項經過別名來把原導入的路徑映射成一個新的導入路徑。以下:

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

當你經過import Button from 'components/button'導入時,實際上被alias等價替換了import Button from './src/components/button'

resolve.modules配置webpack去哪些目錄下找第三方模塊,默認只會去node_modules目錄下尋找。

resolve.enforceExtension若是配置爲true全部導入語句都必須帶有後綴,例如開啓前import './foo能正常工做,開啓後就必須寫成import './foo.js'

插件(plugin)

Plugin用於擴展Webpack功能,各類各樣的Plugin幾乎讓Webpack能夠作任何構建相關的事情。

舉個例子:

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

module.exports = {
	plugins: [
		// 全部頁面都會用到的公共代碼提取到 common 代碼塊中
		new CommonsChunkPlugin({
			name: 'common',
			chunks: ['a', 'b']
		})
	]
}
複製代碼

DevServer配置

在開發環境的時候使用。要配置DevServer,除了在配置文件裏面經過devServer傳入參數外,還能夠經過命令行參數傳入。

注意:只有在經過DevServer去啓動Webpack時配置項文件裏devServer纔會生效。

devServer.hot配置是否啓用使用DevServer中提到的模塊熱替換功能。

devServer.host配置項用於配置 DevServer 服務監聽的地址。

devServer.port配置項用於配置 DevServer 服務監聽的端口,默認使用8080端口。

devServer.https配置HTTPS協議服務。某些狀況下你必須使用HTTPS,HTTP2 和 Service Worker 就必須運行在 HTTPS 之上。

devServer: {
	https: true
}
複製代碼

webpack原理

Webpack的運行是一個串行的過程,從啓動到結束會執行如下流程:

  1. 初始化參數:從配置文件和Shell語句中讀取與合併參數,獲得最終的參數
  2. 開始編譯:用上一步獲得的參數初始化Compiler對象,加載全部配置的插件,執行對象的run方法開始執行編譯。
  3. 肯定入口:根據entry找出全部文件
  4. 編譯模塊:從入口文件出發,調用全部配置的Loader對模塊進行編譯,再找到模塊依賴的模塊,再遞歸本步驟,直到全部入口依賴的文件都通過了本步驟的處理;
  5. 完成編譯:在第四步驟後,獲得了每一個模塊被編譯的內容和它們直接的依賴關係;
  6. 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的chunk,再把每一個chunk轉換成一個單獨的文件加入到輸出內容後,這一步是能夠修改輸出內容的最後機會。
  7. 輸出完成:在肯定好輸出內容後,根據配置肯定輸出的路徑和文件名,把文件內容寫入系統。

webpack優化

縮小文件搜索範圍

  • 優化loader配置

Loader對文件的轉換操縱很耗時,須要讓儘量少的文件被Loader處理。

在使用Loader時能夠經過testincludeexclude三個配置項來命中Loader要應用規則的文件。

module.exports = {
	module: {
		rules: [
			{
				test: /\.js$/,
				use: ['babel-loader?cacheDirectory'],
				include: path.resolve(__dirname, 'src')
			}
		]
	}
}
複製代碼
  • 優化resolve.modules配置

resolve.modules用於配置webpack去哪些目錄下尋找第三方模塊。

resolve.modules的默認值是['node_modules'],含義是先去當前的目錄下./node_modules目錄下去找想找的模塊,以此類推,若是沒有找到就去上一級目錄../node_modules中找,再沒有去上上一級,以此類推...

若是知道安裝的模塊在項目的根目錄下的./node_modules時候,沒有必要按照默認的方式一層層找:

module.exports = {
	resolve: {
		modules: [path.resolve(__dirname, 'node_modules')]
	}
}
複製代碼
  • 優化resolve.alias配置

resolve.alias配置項經過別名來把原導入路徑映射成一個新的導入路徑。能夠減小耗時的遞歸解析操做。

  • 優化module.noParse配置

module.noParse配置項可讓Webpack忽略對部分沒采用模塊化的文件的遞歸解析處理,這樣作的好處是能提升構建性能。

const path = require('path');
module.exports = {
	module: {
		// 獨完整的 `react.min.js` 文件就沒有采用模塊化,忽略對 `react.min.js` 文件的遞歸解析處理
		noParse: [/react\.min\.js$/],
	}
}
複製代碼

把任務分解爲多個子進程去併發執行

HappyPack把任務分解成多個子進程併發執行,子進程處理完後再把結果發送給主進程。減小了總的構建時間。

const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {
	module: {
		rules: [
			{
				test: /\.js$/,
				loader: 'happypack/loader?id=happyBabel',
				exclude: /node_modules/
			}
		]
	},
	plugins: [
		new HappyPack({
			id: 'happyBabel',
			loaders: [{
				loader: 'babel-loader?cacheDirectory= true',
			}],
			// 共享進程池
			threadPool: happyThreadPool,
			// 容許happypack輸出日誌
			verbose: true,
		})
	]
}
複製代碼

使用自動刷新

例如:

module.export = {
	watch: true,
	watchOptions: {
		// 監聽到變化發生後會等300ms再去執行動做,防止文件更新太快致使從新編譯頻率過高
	    // 默認爲 300ms
	    aggregateTimeout: 300,
	    // 判斷文件是否發生變化是經過不停的去詢問系統指定文件有沒有變化實現的
	    // 默認每隔1000毫秒詢問一次
	    poll: 1000
	}
}
複製代碼

因爲保存文件的路徑和最後編輯時間須要佔用內存,定時檢查週期檢查須要佔用CPU以及文件I/O,因此最好減小須要監聽的文件數量和下降檢查頻率。

熱替換

熱替換就是當一個源碼發生改變的時,只從新編譯發生改變的模塊,再用新輸出的模塊替換掉瀏覽器中對應的老模塊。

開啓熱替換:

webpack-dev-server --hot
複製代碼

區分環境

區分開發環境和生產環境,進行不一樣的構建~

CDN加速

CDN又叫內容分發網絡,經過把資源部署到世界各地,用戶在訪問時按照就近原則從離用戶最近的服務器獲取資源,從而加速資源的獲取速度。

CDN 實際上是經過優化物理鏈路層傳輸過程當中的網速有限、丟包等問題來提高網速的。

結合publicPath來處理:

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { WebPlugin } = require('web-webpack-plugin');

module.exports = {
	output: {
		filename: '[name]_[chunkhash:8].js',
		path: path.resolve(__dirname, './dist'),
		publicPath: '//js.cdn.com/id/'
	},
	module: {
		rules: [
			{
				test: /\.css$/,
				use: ExtractTextPlugin.extract({
	          	use: ['css-loader?minimize'],
	          	publicPath: '//img.cdn.com/id/'
	        }),
			}
		]
	},
	plugins: [
		new WebPlugin({
	      template: './template.html',
	      filename: 'index.html',
	      // 指定存放 CSS 文件的 CDN 目錄 URL
	      stylePublicPath: '//css.cdn.com/id/',
	    }),
	    new ExtractTextPlugin({
	      // 給輸出的 CSS 文件名稱加上 Hash 值
	      filename: `[name]_[contenthash:8].css`,
	    }),
	]
}
複製代碼

tree shaking優化

將多餘的代碼移除。

webpack --display-used-exports --optimize-minimize
複製代碼

提取公共代碼

公共代碼的提取。

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

new CommonsChunkPlugin({
	// 從哪些 Chunk 中提取
	chunks: ['a', 'b'],
	// 提取出的公共部分造成一個新的 Chunk,這個新 Chunk 的名稱
	name: 'common'
})
複製代碼

按需加載

對於採用單頁應用做爲前端架構的網站來講,會面臨一個網頁須要加載的代碼量很大的問題,由於許多功能都作到了一個HTML裏面,這會致使網頁加載緩慢、交互卡頓、用戶體驗將很是糟糕。

致使這個問題的根本緣由在於一次性的加載全部功能對應的代碼,但其實用戶每一階段只可能使用其中一部分功能。 因此解決以上問題的方法就是用戶當前須要用什麼功能就只加載這個功能對應的代碼,也就是所謂的按需加載。

Webpack 內置了強大的分割代碼的功能去實現按需加載。好比:

  • 網頁首次加載時只加載main.js文件,網頁會展現一個按鈕main.js文件中只包含監聽按鈕事件和加載按需加載的代碼。
  • 當按鈕被點擊時纔去加載被分割出去的show.js文件,加載成功後再執行show.js裏的函數。

main.js中:

window.document.getElementById('btn').addEventListener('click', function () {
	// 當按鈕被點擊後纔去加載 show.js 文件,文件加載成功後執行文件導出的函數
	import(/* webpackChunkName: "show" */ './show').then((show) => {
		show('Webpack');
	})
});
複製代碼

show.js中:

module.exports = function (content) {
	window.alert('Hello ' + content);
};
複製代碼

代碼中最關鍵的一句是import(/* webpackChunkName: "show" */ './show'),Webpack 內置了對import(*)語句的支持,當 Webpack 遇到了相似的語句時會這樣處理:

  • ./show.js爲入口新生成一個Chunk
  • 當代碼執行到import所在語句時纔會去加載由Chunk對應生成的文件。
  • import返回一個Promise,當文件加載成功時能夠在Promisethen方法中獲取到show.js導出的內容。

在工做中具體使用到的時候再按須要進行更改配置項啦~

參考

文章首發在github上--談談webpack。更多的內容,請戳個人博客進行了解,能留個star就更好了💨

相關文章
相關標籤/搜索