1. 跟上技術的迭代
- Node、Npm、Yarn、Webpack都要保持最新的版本
2. 在儘可能少的模塊上應用Loader
- 合理的使用exclude或者include的配置,來儘量減少loader被頻繁執行的頻率。當loader執行頻率降低時,也會提升webpack的打包速度。比如:
module: {
rules: [{
test: /\.js$/,
include: path.resolve(__dirname, '../src'), // 只對src目錄下的js文件做打包轉譯工作
// exclude: /node_modules/, // 如果你的js文件在node_modules裏邊,就不使用babel-loader了,因爲它裏邊的代碼都是些第三方代碼,已經做好了轉譯的工作。
use: [{
loader: "babel-loader"
}]
}]
}
3. Plugin儘可能少並確保可靠
- 選擇性能比較好的、官方推薦的或者社區認可的插件來使用。
- Plugin儘可能少的使用,減少冗餘的代碼分析。比如:(css代碼壓縮插件只在生產環境中配置,開發環境中沒有必要使用)
- 開發環境無用插件的剔除。
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-plugin');
const prodConfig = {
mode: 'production', // 生產環境
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
}
module.exports = prodConfig;
4. resolve參數合理配置
- 通過合理優化resolve配置項,讓webpack打包速度更快。
resolve: { // 新增配置項(如果想在模塊裏引入js文件,直接可省略後綴的話)
extensions: ['.js', '.jsx'], // 當引入一個其他目錄下的模塊的時候,首先會去找.js爲後綴的文件,如果沒有匹配到,再去找.jsx爲後綴的文件
mainFiles: ['index', 'child'], // 當引入一個目錄下的內容的時候,不知道具體要引入哪個文件,那麼可以通過mainFiles配置先嚐試去找index文件,如果沒有index,再去找child文件
alias: {
child: path.resolve(__dirname, '../src/a/b/child') // 配置引入模塊的別名
}
}
- extensions 配置了很多值時,那麼在查找文件時,是會有性能損耗的。所以,一般我們在引入一些如js、jsx這種 邏輯型文件 時,我們纔會把它配置到extensions的配置項裏(像一些’.css’/’.png’等文件,強烈不建議配置在extensions裏) 。這樣我們的代碼寫起來也會方便些,性能方面也得到了合理的提升。
- mainFiles 配置很多值時,也會影響打包性能,所以在項目開發中,要視情況而定,不要盲目的配置mainFiles項。(一般項目不用配置)
- alias 當邏輯代碼中引入的其他模塊文件,所在層級較深時,比較適用,其他情況下,不建議使用。
5. 使用DllPlugin提升webpack打包速度
當我們的js代碼裏,引入第三方庫 的時候,每次重新打包,都要重新分析所引入的第三方庫代碼,最終把他們打包到我們的項目之中。第三方庫的代碼都有個特點,那就是他們是不變的(不隨業務邏輯變化而變化),所以我們可以把所有引入的第三方的代碼都打包生成一個文件裏,只在第一次打包時分析第三方代碼,之後再執行打包時,直接用上次分析好的結果即可。
具體步驟如下:
- 我們對第三方庫單獨進行打包,生成一個打包文件。
- 使用 library 通過全局變量的形式把第三方庫的所有代碼暴露出去。
- 通過DllPlugin插件,來對我們暴露的代碼做一個分析,生成一個manifest映射文件。
- 通過執行npm run build:dll 生成一個單獨的第三方庫的打包文件。(在scripts中配置)
- 在 webpack.common.js(公共的配置文件)文件中,引入DllReferencePlugin插件,去拿打包的映射文件。查找是否存在第三方庫的映射關係。
如何結合全局變量和manifest.json映射文件進行webpack的配置??下方的例子:
使用DllReferencePlugin和DllPlugin進行配置:
我們單獨配置一個第三方庫的打包文件:webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom', 'lodash']
},
plugins: [
new webpack.DllPlugin({
name: '[name]', // 用DllPlugin對庫進行分析
path: path.resolve(__dirname, '../dll/[name].manifest.json') // 把庫裏邊第三方的一些映射關係,放到name.manifest.json文件下
})
],
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'), // 把第三方庫代碼打包到dll文件夾下
library: '[name]' // 暴露全局變量
}
}
- library 通過全局變量的形式把第三方庫的所有代碼暴露出去
scripts配置:package.json文件
"scripts": {
"build": "webpack --config ./build/webpack.prod.js",
"build:dll": "webpack --config ./build/webpack.dll.js"
}
執行打包第三方庫文件:npm run build:dll
webpack.common.js中的插件配置:
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
plugins: [
new AddAssetHtmlWebpackPlugin({ // 爲頁面引入打包好的第三方庫代碼文件
filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
}),
new webpack.DllReferencePlugin({ // 查找第三方庫的映射關係
manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
})
]
}
- webpack內置的DllReferencePlugin插件的作用主要是:在執行webpack打包時,對我們引入的第三方庫,它會先去dll下的manifest映射文件中查找第三方模塊的映射關係,如果能找到映射關係,那麼它就會認爲,引入的第三方庫,沒有必要再次進行打包了, 可以直接去全局變量裏拿; 如果發現引入的第三放庫的映射關係不在manifest映射文件裏,那麼它就會再從node_modules裏取出該模塊,然後再進行打包到我們的源代碼裏邊。
再次打包 npm run build 我們會發現:
不配置DllReferencePlugin插件時:打包速度基本穩定在1000ms
配置DllReferencePlugin插件之後:打包速度基本穩定在600ms
實際上,在webpack配置大型複雜項目過程中,DllPlugin和DllReferencePlugin用的還是比較多的。
在webpack做打包的時候,我們就可以結合全局變量、以及我們生成的manifest映射文件,然後來對我們的源代碼進行分析,一旦發現你使用的第三方庫的內容是在vendors.dll.js裏邊,那麼它就會直接使用vendors.dll.js裏的內容了。就不會再去node_modules裏引入我們的模塊了。
6. 控制包文件大小
在我們的項目開發中,經常會引入一些沒有用到的模塊,引入之後,其實我們並沒有使用這些模塊。這個時候,如果你沒有配置tree-shaking,就會導致在打包的過程中有很多冗餘的代碼。那麼這些冗餘的代碼,實際上就會拖累webpack的打包速度。所以,在我們做打包的時候,對於一些冗餘的代碼,我們可以通過 tree-shaking(查看詳解) 把它去除掉。(或者直接不去引用它)這樣我們就可以控制webpack打包生成文件的大小,從而提升打包速度。
也可以通過splitChunksPlugin插件,對代碼進行拆分,把一個大的文件,拆分成幾個小的文件,做webpack的打包處理,這樣也可以提高webpack的打包速度。
7. thread-loader, parallel-webpack, happypack 多進程打包
webpack默認是基於node.js來運行的,所以它是一個單進程的打包過程。我們也可以藉助webpack裏的多進程來幫我們提升打包速度
如:thread-loader, parallel-webpack, happypack 多進程打包。
他們可以利於node中的多進程,同時利用多個cpu進行打包,具體需要開幾個cpu進行打包速度是最快的,對其他應用影響是最小的,要根據項目實際情況,多做幾次嘗試來做一個權衡。
8. 合理使用 sourceMap(查看詳解)
9. 結合stats.json文件分析打包結果
10. 開發環境內存編譯
webpack-dev-server做打包的時候不會生成dist目錄,而是會把打包生成的文件放到內存裏,那麼內存的讀取,肯定要比硬盤的讀取要快的多,所以採用這種手段,也會讓我們在開發的過程中webpack打包的性能得到很大的提升。
另外,提升webpack打包速度的方式還有很多,比如:一些loader會提供一些參數,幫助我們去提升它的打包速度。
知識補充
我們對webpack.dll.js文件裏的entry再做一個變更:(對打包的dll文件做拆分)
entry: {
vendors: ['lodash'],
react: ['react', 'react-dom'] // 讓react、react-dom庫的代碼打包到文件名爲react的文件裏
}
在webpack公共配置文件中多配置一份插件:
單獨打包第三方庫代碼:(執行打包)
npm run build:dll
此時的dll目錄如下:
再次執行打包:
npm run dev
頁面顯示正常,react、vendors也可以正常訪問。
如果在大型項目中,對dll文件做拆分比較多的話,這樣會導致在webpack公共的配置文件裏,重複實例化多個相同的插件,下邊分享個更好的方法:
通過node來分析dll目錄下究竟有幾個dll文件和manifest文件,然後動態的往plugins裏添加AddAssetHtmlWebpackPlugin和DllReferencePlugin。
const path = require('path'); // 引入node的path模塊(loader模塊)
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
// 創建一個插件數組
const plugins = [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin()
]
// 使用fs讀取dll目錄下的文件
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
// 對獲取到的所有文件進行循環
files.forEach(file => {
if(/.*\.dll.js/) { // 如果是以.dll.js結尾的文件,就以走AddAssetHtmlWebpackPlugin插件
plugins.push(
new AddAssetHtmlWebpackPlugin({ // 爲頁面引入打包好的第三方庫代碼文件
filepath: path.resolve(__dirname, '../dll', file)
})
)
}
if(/.*\.manifest.json/) { // 如果是以.manifest.json結尾的文件,就走DllReferencePlugin插件
plugins.push(
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
})
)
}
})
module.exports = {
entry: {
main: './src/index.js'
},
resolve: { // 新增配置項(如果想在模塊裏引入js文件,直接可省略後綴的話)
extensions: ['.js', '.jsx']
},
module: {
rules: [{
test: /\.jsx?$/, // 這裏代表既匹配js文件,也匹配jsx文件
include: path.resolve(__dirname, '../src'), // 只對src目錄下的js文件做打包轉譯工作
// exclude: /node_modules/, // 如果你的js文件在node_modules裏邊,就不使用babel-loader了,因爲它裏邊的代碼都是些第三方代碼,已經做好了轉譯的工作。
use: [{
loader: "babel-loader"
}]
}]
},
plugins, // 插件配置項
optimization: {
splitChunks: { // 有默認配置項
chunks: "all" // 不管是同步還是異步,都進行代碼分割
}
},
output: {
filename: '[name].js', // 打包之後的輸出文件
path: path.resolve(__dirname, '../dist')
}
}
執行打包:
npm run dev
打開控制檯我們發現,index.html頁面引入了我們打包過後的第三方庫文件。
此時,再對打包的dll文件做拆分,就比較方便了,每次做拆分就不必一次次的添加插件的實例了。