webpack是當下最流行的js打包工具,這得益於網頁應用日益複雜和js模組化的流行。webpack2增加了一些新特性也正式釋出了一段時間,是時候告訴大家如何用webpack2優化你的構建讓它構建出更小的檔案尺寸和更好的開發體驗。
優化輸出
打包結果更小可以讓網頁開啟速度更快以及簡約寬頻。可以通過這以下幾點做到
壓縮css
css-loader
在webpack2裡預設是沒有開啟壓縮的,最後生成的css檔案裡有很多空格和tab,通過配置
css-loader?minimize
引數可以開啟壓縮輸出最小的css。css的壓縮實際是是通過cssnano實現的。
tree-shaking
tree-shaking 是指藉助es6 import export
語法靜態性的特點來刪掉export但是沒有import過的東西。要讓tree-shaking工作需要注意以下幾點:
- 配置babel讓它在編譯轉化es6程式碼時不把
import export
轉換為cmd的module.export
,配置如下:
1 2 3 4 5 6 7 8 |
"presets": [ [ "es2015", { "modules": false } ] ] |
- 大多數分佈到npm的庫裡的程式碼都是es5的,但是也有部分庫(redux,react-router等等)開始支援tree-shaking。這些庫釋出到npm裡的程式碼即包含es5的又包含全採用了es6
import export
語法的程式碼。
拿redux庫來說,npm下載到的目錄結構如下:
1 2 3 4 |
├── es │ └── utils ├── lib │ └── utils |
其中lib目錄裡是編譯出的es5程式碼,es目錄裡是編譯出的採用import export
語法的es5程式碼,在redux的package.json
檔案裡有這兩個配置:
1 2 |
main": "lib/index.js", "jsnext:main": "es/index.js", |
這是指這個庫的入口檔案的位置,所以要讓webpack去讀取es目錄下的程式碼需要使用jsnext:main欄位配置的入口,要做到這點webpack需要這樣配置:
1 2 3 4 5 |
module.exports = { resolve: { mainFields: ['jsnext:main','main'], } }; |
這會讓webpack先使用jsnext:main欄位,在沒有時使用main欄位。這樣就可以優化支援tree-shaking的庫。
優化 UglifyJsPlugin
webpack --optimize-minimize
選項會開啟 UglifyJsPlugin來壓縮輸出的js,但是預設的UglifyJsPlugin配置並沒有把程式碼壓縮到最小輸出的js裡還是有註釋和空格,需要覆蓋預設的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
new UglifyJsPlugin({ // 最緊湊的輸出 beautify: false, // 刪除所有的註釋 comments: false, compress: { // 在UglifyJs刪除沒有用到的程式碼時不輸出警告 warnings: false, // 刪除所有的 `console` 語句 // 還可以相容ie瀏覽器 drop_console: true, // 內嵌定義了但是隻用到一次的變數 collapse_vars: true, // 提取出出現多次但是沒有定義成變數去引用的靜態值 reduce_vars: true, } }) |
定義環境變數 NODE_ENV=production
很多庫裡(比如react)有部分程式碼是這樣的:
1 2 3 |
if(process.env.NODE_ENV !== 'production'){ // 不是生產環境才需要用到的程式碼,比如控制檯裡看到的警告 } |
在環境變數 NODE_ENV
等於 production
的時候UglifyJs會認為if語句裡的是死程式碼在壓縮程式碼時刪掉。
使用 CommonsChunkPlugin 抽取公共程式碼
CommonsChunkPlugin可以提取出多個程式碼塊都依賴的模組形成一個單獨的模組。要發揮CommonsChunkPlugin的作用還需要瀏覽器快取機制的配合。在應用有多個頁面的場景下提取出所有頁面公共的程式碼減少單個頁面的程式碼,在不同頁面之間切換時所有頁面公共的程式碼之前被載入過而不必重新載入。這個方法可以非常有效的提升應用效能。
在生產環境按照檔案內容md5打hash
webpack編譯在生產環境出來的js、css、圖片、字型這些檔案應該放到CDN上,再根據檔案內容的md5命名檔案,利用快取機制使用者只需要載入一次,第二次載入時就直接訪問快取。如果你之後有修改就會為對應的檔案生產新的md5值。做到以上你需要這樣配置:
1 2 3 4 5 6 |
{ output: { publicPath: CND_URL, filename: '[name]_[chunkhash].js', }, } |
知道以上原理後我們還可以進一步優化:利用CommonsChunkPlugin提取出使用頁面都依賴的基礎執行環境。比如對於最常見的react體系你可以抽出基礎庫react
react-dom
redux
react-redux
到一個單獨的檔案而不是和其它檔案放在一起打包為一個檔案,這樣做的好處是隻要你不升級他們的版本這個檔案永遠不會被重新整理。如果你把這些基礎庫和業務程式碼打包在一個檔案裡每次改動業務程式碼都會導致瀏覽器重複下載這些包含基礎庫的程式碼。以上的配置為:
1 2 3 4 5 6 7 8 9 |
// vender.js 檔案抽離基礎庫到單獨的一個檔案裡防止跟隨業務程式碼被重新整理 // 所有頁面都依賴的第三方庫 // react基礎 import 'react'; import 'react-dom'; import 'react-redux'; // redux基礎 import 'redux'; import 'redux-thunk'; |
1 2 3 4 5 6 |
// webpack配置 { entry: { vendor: './path/to/vendor.js', }, } |
DedupePlugin 和 OccurrenceOrderPlugin
在webpack1裡經常會使用 DedupePlugin
外掛來消除重複的模組以及使用 OccurrenceOrderPlugin
外掛讓被依賴次數更高的模組靠前分到更小的id 來達到輸出更少的程式碼,在webpack2裡這些已經這兩個外掛已經被移除了因為這些功能已經被內建了。
除了壓縮文字程式碼外還可以:
- 用imagemin-webpack-plugin 壓縮圖片
- 用webpack-spritesmith 合併雪碧圖
- 對於支援es6的js執行環境使用babili
以上優化點只需要在構建用於生產環境程式碼的時候才使用,在開發環境時最好關閉因為它們很耗時。
優化開發體驗
優化開發體驗主要從更快的構建和更方便的功能入手。
更快的構建
縮小檔案搜尋範圍
webpack的resolve.modules
配置模組庫(通常是指node_modules)所在的位置,在js裡出現import 'redux'
這樣不是相對也不是絕對路徑的寫法時會去node_modules目錄下找。但是預設的配置會採用向上遞迴搜尋的方式去尋找node_modules,但通常專案目錄裡只有一個node_modules在專案根目錄,為了減少搜尋我們直接寫明node_modules的全路徑:
1 2 3 4 5 |
module.exports = { resolve: { modules: [path.resolve(__dirname, 'node_modules')] } }; |
除此之外webpack配置loader時也可以縮小檔案搜尋範圍。
- loader的test正規表示式也應該儘可能的簡單,比如在你的專案裡只有
.js
檔案時就不要把test寫成/\.jsx?$/
- loader使用include命中只需要處理的檔案,比如babel-loader的這兩個配置:
只對專案目錄下src目錄裡的程式碼進行babel編譯
1 2 3 4 5 |
{ test: /\.js$/, loader: 'babel-loader', include: path.resolve(__dirname, 'src') } |
專案目錄下的所有js都會進行babel編譯,包括龐大的node_modules下的js
1 2 3 4 |
{ test: /\.js$/, loader: 'babel-loader' } |
開啟 babel-loader 快取
babel編譯過程很耗時,好在babel-loader提供快取編譯結果選項,在重啟webpack時不需要創新編譯而是複用快取結果減少編譯流程。babel-loader快取機制預設是關閉的,開啟的配置如下:
1 2 3 4 5 6 7 8 |
module.exports = { module: { loaders: [{ test: /\.js$/, loader: 'babel-loader?cacheDirectory', }] } }; |
使用 alias
resolve.alias
配置路徑對映。
釋出到npm的庫大多數都包含兩個目錄,一個是放著cmd模組化的lib目錄,一個是把所有檔案合成一個檔案的dist目錄,多數的入口檔案是指向lib裡面下的。
預設情況下webpack會去讀lib目錄下的入口檔案再去遞迴載入其它依賴的檔案這個過程很耗時,alias配置可以讓webpack直接使用dist目錄的整體檔案減少檔案遞迴解析。配置如下:
1 2 3 4 5 6 7 8 9 |
module.exports = { resolve: { alias: { 'moment': 'moment/min/moment.min.js', 'react': 'react/dist/react.js', 'react-dom': 'react-dom/dist/react-dom.js' } } }; |
使用 noParse
module.noParse
配置哪些檔案可以脫離webpack的解析。
有些庫是自成一體不依賴其他庫的沒有使用模組化的,比如jquey、momentjs、chart.js,要使用它們必須整體全部引入。
webpack是模組化打包工具完全沒有必要去解析這些檔案的依賴,因為它們都不依賴其它檔案體積也很龐大,要忽略它們配置如下:
1 2 3 4 5 |
module.exports = { module: { noParse: /node_modules\/(jquey|moment|chart\.js)/ } }; |
除此以外還有很多可以加速的方法:
更方便的功能
模組熱替換
模組熱替換是指在開發的過程中修改程式碼後不用重新整理頁面直接把變化的模組替換到老模組讓頁面呈現出最新的效果。
webpack-dev-server內建模組熱替換,配置起來也很方便,下面以react應用為例,步驟如下:
- 在啟動webpack-dev-server的時候帶上
--hot
引數開啟模組熱替換,在開啟--hot
後針對css的變化是會自動熱替換的,但是js涉及到複雜的邏輯還需要進一步配置。 - 配置頁面入口檔案
1 2 3 4 5 6 7 8 9 10 11 |
import App from './app'; function run(){ render(<App/>,document.getElementById('app')); } run(); // 只在開發模式下配置模組熱替換 if (process.env.NODE_ENV !== 'production') { module.hot.accept('./app', run); } |
當./app發生變化或者當./app依賴的檔案發生變化時會把./app編譯成一個模組去替換老的,替換完畢後重新執行run函式渲染出最新的效果。
自動生成html
webpack只做了資源打包的工作還缺少把這些載入到html裡執行的功能,在龐大的app裡手寫html去載入這些資源是很繁瑣易錯的,我們需要自動正確的載入打包出的資源。
webpack原生不支援這個功能於是我做了一個外掛 web-webpack-plugin
具體使用點開連結看詳細文件,使用大概如下:
webpack配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module.exports = { entry: { A: './a', B: './b', }, plugins: [ new WebPlugin({ // 輸出的html檔名稱,必填,注意不要重名,重名會覆蓋相互檔案。 filename: 'index.html', // 該html檔案依賴的entry,必須是一個陣列。依賴的資源的注入順序按照陣列的順序。 requires: ['A', 'B'], }), ] }; |
將會輸出一個index.html
檔案,這個檔案將會自動引入 entry A
和 B
生成的js檔案,
輸出的html:
1 2 3 4 5 6 7 8 9 |
<html> <head> <meta charset="UTF-8"> </head> </body> <script src="A.js"></script> <script src="B.js"></script> </body> </html> |
輸出的目錄結構
1 2 3 |
├── A.js ├── B.js └── index.html |
管理多頁面
雖然webpack適用於單頁應用,但複雜的系統經常是由多個單頁應用組成,每個頁面一個功能模組。webpack給出了js打包方案但缺少管理多個頁面的功能。 web-webpack-plugin的AutoWebPlugin
會自動的為你的系統裡每個單頁應用生成一個html入口頁,這個入口會自動的注入當前單頁應用依賴的資源,使用它你只需如下幾行程式碼:
1 2 3 4 5 6 7 8 |
plugins: [ // ./src/pages/ 代表存放所有頁面的根目錄,這個目錄下的每一個目錄被看著是一個單頁應用 // 會為裡面的每一個目錄生成一個html入口 new AutoWebPlugin('./src/pages/', { //使用單頁應用的html模版檔案,這裡你可以自定義配置 template: './src/assets/template.html', }), ], |
檢視web-webpack-plugin的文件瞭解更多
分析輸出結果
如果你對當前的配置輸出或者構建速度不滿意,webpack有一個工具叫做webpack analyze 以視覺化的方式直觀的分析構建,來進一步優化構建結果和速度。要使用它你需要在執行webpack的時候帶上--json --profile
2個引數,這代表讓webpack把構建結果以json輸出並帶上構建效能資訊,使用如下:
1 |
webpack --json --profile > stats.json |
會生產一個stats.json
檔案,再開啟webpack analyze 上傳這個檔案開始分析。
最後附上這篇文章所講到的webpack整體的配置,分為開發環境的webpack.config.js
和生產環境的webpack-dist.config.js