提升webpack打包速度的方法

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代碼裏,引入第三方庫 的時候,每次重新打包,都要重新分析所引入的第三方庫代碼,最終把他們打包到我們的項目之中。第三方庫的代碼都有個特點,那就是他們是不變的(不隨業務邏輯變化而變化),所以我們可以把所有引入的第三方的代碼都打包生成一個文件裏,只在第一次打包時分析第三方代碼,之後再執行打包時,直接用上次分析好的結果即可。

具體步驟如下:

  1. 我們對第三方庫單獨進行打包,生成一個打包文件。
  2. 使用 library 通過全局變量的形式把第三方庫的所有代碼暴露出去。
  3. 通過DllPlugin插件,來對我們暴露的代碼做一個分析,生成一個manifest映射文件。
  4. 通過執行npm run build:dll 生成一個單獨的第三方庫的打包文件。(在scripts中配置)
  5. 在 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文件做拆分,就比較方便了,每次做拆分就不必一次次的添加插件的實例了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章