webpack2 終極優化
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 ,配置如下:
"presets": [ [ "es2015", { "modules": false } ] ]
- 大多數分布到npm的庫里的代碼都是es5的,但是也有部分庫(redux,react-router等等)開始支持tree-shaking。這些庫發布到npm里的代碼即包含es5的又包含全采用了es6 import export 語法的代碼。 拿redux庫來說,npm下載到的目錄結構如下:
├── es │ └── utils ├── lib │ └── utils
"main": "lib/index.js", "jsnext:main": "es/index.js",
module.exports = { resolve: { mainFields: ['jsnext:main','main'], } };
優化 UglifyJsPlugin
webpack --optimize-minimize 選項會開啟 UglifyJsPlugin來壓縮輸出的js,但是默認的UglifyJsPlugin配置并沒有把代碼壓縮到最小輸出的js里還是有注釋和空格,需要覆蓋默認的配置:
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)有部分代碼是這樣的:
if(process.env.NODE_ENV !== 'production'){
// 不是生產環境才需要用到的代碼,比如控制臺里看到的警告
}
在環境變量 NODE_ENV 等于 production 的時候UglifyJs會認為if語句里的是死代碼在壓縮代碼時刪掉。
DedupePlugin 和 OccurrenceOrderPlugin
在webpack1里經常會使用 DedupePlugin 插件來消除重復的模塊以及使用 OccurrenceOrderPlugin 插件讓被依賴次數更高的模塊靠前分到更小的id 來達到輸出更少的代碼,在webpack2里這些已經這兩個插件已經被移除了因為這些功能已經被內置了。
除了壓縮文本代碼外還可以:
- 用 imagemin-webpack-plugin 壓縮圖片
- 用 webpack-spritesmith 合并雪碧圖
以上優化點只需要在構建用于生產環境代碼的時候才使用,在開發環境時最好關閉因為它們很耗時。
優化開發體驗
優化開發體驗主要從更快的構建和更方便的功能入手。
更快的構建
縮小文件搜索范圍
webpack的 resolve.modules 配置模塊庫(通常是指node_modules)所在的位置,在js里出現 import 'redux' 這樣不是相對也不是絕對路徑的寫法時會去node_modules目錄下找。但是默認的配置會采用向上遞歸搜索的方式去尋找node_modules,但通常項目目錄里只有一個node_modules在項目根目錄,為了減少搜索我們直接寫明node_modules的全路徑:
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'node_modules')]
}
};
除此之外webpack配置loader時也可以縮小文件搜索范圍。
- loader的test正則表達式也應該盡可能的簡單,比如在你的項目里只有 .js 文件時就不要把test寫成 /\.jsx?$/
- loader使用include命中只需要處理的文件,比如babel-loader的這兩個配置:
只對項目目錄下src目錄里的代碼進行babel編譯
{
test: /\.js$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src')
}
項目目錄下的所有js都會進行babel編譯,包括龐大的node_modules下的js
{
test: /\.js$/,
loader: 'babel-loader'
}
開啟 babel-loader 緩存
babel編譯過程很耗時,好在babel-loader提供緩存編譯結果選項,在重啟webpack時不需要創新編譯而是復用緩存結果減少編譯流程。babel-loader緩存機制默認是關閉的,打開的配置如下:
module.exports = {
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
}
]
}
};
使用 alias
resolve.alias 配置路徑映射。 發布到npm的庫大多數都包含兩個目錄,一個是放著cmd模塊化的lib目錄,一個是把所有文件合成一個文件的dist目錄,多數的入口文件是指向lib里面下的。 默認情況下webpack會去讀lib目錄下的入口文件再去遞歸加載其它依賴的文件這個過程很耗時,alias配置可以讓webpack直接使用dist目錄的整體文件減少文件遞歸解析。配置如下:
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是模塊化打包工具完全沒有必要去解析這些文件的依賴,因為它們都不依賴其它文件體積也很龐大,要忽略它們配置如下:
module.exports = {
module: {
noParse: /node_modules\/(jquey|moment|chart\.js)/
}
};
更方便的功能
模塊熱替換
模塊熱替換是指在開發的過程中修改代碼后不用刷新頁面直接把變化的模塊替換到老模塊讓頁面呈現出最新的效果。 webpack-dev-server內置模塊熱替換,配置起來也很方便,下面以react應用為例,步驟如下:
- 在啟動webpack-dev-server的時候帶上 --hot 參數開啟模塊熱替換,在開啟 --hot 后針對css的變化是會自動熱替換的,但是js涉及到復雜的邏輯還需要進一步配置。
- 配置頁面入口文件
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配置
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:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="A.js"></script>
<script src="B.js"></script>
</body>
</html>
輸出的目錄結構
├── A.js
├── B.js
└── index.html
最后附上這篇文章所講到的 webpack整體的配置 ,分為開發環境的 webpack.config.js 和生產環境的 webpack-dist.config.js
來自:http://imweb.io/topic/5868e1abb3ce6d8e3f9f99bb