天天看點

淺入webpack4 高效簡單的配置

前言

在vue-cli3中已經将webpack等詳細配置(config)去除,我們配置webpack隻能在vue.config.js裡進行配置,這裡我個人總結了一套webpack的優化方案模闆并且附有我個人的講解(尚在研究中)。 總體優化這幾個方面:

  1. 提升生産打包的建構速度
  2. 拆分每個 npm 包
  3. 将穩定的第三方庫(體積比較大的)改用cdn引入,不進行打包
  4. 安裝可視化打包分析器(可選)

1.提升生産打包的建構速度

首先,你要知道運作在 Node.js 之上的 Webpack 是單線程模型的,是以Webpack 需要處理的事情需要一件一件的做,不能多件事一起做。我們需要Webpack 能同一時間處理多個任務,發揮多核 CPU 電腦的威力,HappyPack 就能讓 Webpack 做到這點,它把任務分解給多個子程序去并發的執行,子程序處理完後再把結果發送給主程序。 想了解happypack原理機制可以參考下面這篇文章,我主要講解如何使用,不作過多剖析。 happypack原理詳解

運作機制

淺入webpack4 高效簡單的配置

首頁利用npm安裝happypack并在package檔案的devDependencies節點寫入依賴。

npm install happypack --save-dev
或
npm install happypack -D           

複制

在vue.config.js中:

const HappyPack = require('happypack')
/*
os 子產品提供了一些基本的系統操作函數
os.cpus()
傳回一個對象數組,包含所安裝的每個 CPU/核心的資訊:型号、速度(機關 MHz)、時間
(一個包含 user、nice、sys、idle 和 irq 所使用 CPU/核心毫秒數的對象)。
*/
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
  configureWebpack: {
      plugins: [
      new HappyPack({
        //用id來辨別 happypack處理那裡類檔案 要和 rules 中指定的 id 對應起來
        id: 'babel',
        //如何處理  用法和loader 的配置一樣
        loaders: ['babel-loader?cacheDirectory=true'],
        //共享程序池
        threadPool: happyThreadPool
      })
    ],
  }
   chainWebpack: config => {
    //在這裡使用loader時候後面跟的id就是happypack處理那一類檔案
    const jsRule = config.module.rule('js');
    jsRule.uses.clear();
    jsRule.use('happypack/loader?id=babel')
      .loader('happypack/loader?id=babel')
      .end();
   }
 }           

複制

這裡要注意的是

plugins

中的id辨別符要和loader中的

?id=babel

要一緻,去告訴 happypack/loader 去選擇哪個 HappyPack 執行個體去處理檔案。 HappyPack 參數

id

: String 用唯一的辨別符 id 來代表目前的 HappyPack 是用來處理一類特定的檔案.

loaders

: Array 用法和 webpack Loader 配置中一樣.

threads

: Number 代表開啟幾個子程序去處理這一類型的檔案,預設是3個,類型必須是整數。

verbose

: Boolean 是否允許 HappyPack 輸出日志,預設是 true。

threadPool

: HappyThreadPool 代表共享程序池,即多個 HappyPack 執行個體都使用同一個共享程序池中的子程序去處理任務,以防止資源占用過多。

verboseWhenProfiling

: Boolean 開啟webpack --profile ,仍然希望HappyPack産生輸出。

debug

: Boolean 啟用debug 用于故障排查。預設 false。

注:這上面還引用了Node的OS子產品,他提供了一些基本的系統操作函數。

2.拆分每個 npm 包

當我們運作項目并且打包的時候,會發現chunk-vendors.js這個檔案非常大,那是因為webpack将所有的依賴全都壓縮到了這個檔案裡面,這時我們可以将其拆分,将所有的依賴都打包成單獨的js。

這裡可以利用splitChunks将每個依賴包單獨打包,拆分每個npm包。 cacheGroups其實是splitChunks裡面最核心的配置,splitChunks就是根據cacheGroups去拆分子產品的, splitChunks預設有兩個緩存組:vender和default 我拿了這裡的預設配置→_→ splitChunks的系統預設配置

//splitChunks的預設配置:
module.exports = {
  //...
    optimization{
        splitChunks: {
            /**
                1. 三個值 
                    async 僅提取按需載入的module
                    initial 僅提取同步載入的module
                    all 按需、同步都有提取
            */
            chunks: "async",
            // 隻有導入的子產品 大于 該值 才會 做代碼分割 (機關位元組)
            minSize: 30000,
            // 提取出的新chunk在兩次壓縮之前要小于多少kb,預設為0,即不做限制
            maxSize: 0,
            // 被提取的chunk最少需要被多少chunks共同引入
            minChunks: 1,
            // 按需加載的代碼塊(vendor-chunk)并行請求的數量小于或等于5個
            maxAsyncRequests: 5, 
            // 初始加載的代碼塊,并行請求的數量小于或者等于3個
            maxInitialRequests: 3,
            // 預設命名 連接配接符
            automaticNameDelimiter: '~',
            /**
                name 為true時,分割檔案名為 [緩存組名][連接配接符][入口檔案名].js
                name 為false時,分割檔案名為 [子產品id][連接配接符][入口檔案名].js
                如果 緩存組存在 name 屬性時 以緩存組的name屬性為準
            */
            name: true,
            // 緩存組 當符合 代碼分割的 條件時 就會進入 緩存組 把各個子產品進行分組,最後一塊打包
            cacheGroups: {
                // 如果 引入檔案 在node_modules 會被打包 這個緩存組(vendors)
                vendors: {
                    // 隻分割 node_modules檔案夾下的子產品
                    test: /[\\/]node_modules[\\/]/,
                    // 優先級 因為如果 同時滿足 vendors、和default 哪個優先級高 就會打包到哪個緩存組
                    priority: -10
                },
                default: {
                    // 表示這個庫 至少被多少個chunks引入,滿足要求的包會被拆分出來
                    minChunks: 2,
                    priority: -20,
                    // 如果 這個子產品已經 被分到 vendors組,這裡無需在分割 直接引用 分割後的
                    reuseExistingChunk: true
                }
            }
        }
    } 
};           

複制

這裡我們做個簡單的配置,各取所需就好了。           

複制

module.exports = {
 optimization: {
/* runtimeChunk 将包含chunks 映射關系的 list單獨從 app.js裡提取出來,因為每一個 chunk 的 id 基本都是
基于内容 hash 出來的,是以你每次改動都會影響它,如果不将它提取出來的話,等于app.js每次都會改變。緩存就失效了。
*/
      runtimeChunk: 'single',
      splitChunks: {
        chunks: 'all',
        maxInitialRequests: Infinity, //最大初始化請求 
        minSize: 20000, // 依賴包超過20000bit将被單獨打包
        cacheGroups: {  //設定緩存組用來抽取滿足不同規則的chunk,這裡以生成vendor為例
        //第三方庫
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name(module) {
              //  拆分每個 npm 包
              const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
              return `npm.${packageName.replace('@', '')}`
            }
          }
        }
      }
 }
 }           

複制

上面的代碼會将超過20000bit的依賴包單獨打包,并且加上字首npm.,如果你覺得第三方引入的庫或包實在太大,莫方,接下來我要講的就是解決這個問題的方法。

3.改用CDN引入第三方庫

什麼是CDN? 内容分發網絡,加速網絡傳輸,就是通過将資源部署到世界各地,使用者通路時按照就近原則從最近的伺服器擷取資源,來提高擷取資源的速度,cdn就是對http的提速。

這個方法推薦使用于那些版本穩定,體積較大的第三方庫。 webpack中提供了externals配置用于第三方庫脫離webpack打包,不被打入bundle中,進而減少打包時間,但又不影響運用第三方庫的方式,例如import方式等。

module.exports = {
    chainWebpack: config => {
        config.externals({
          'vue': 'Vue',
          'vuex': 'Vuex',
          'vue-router': 'VueRouter',
          'axios': 'axios',
          'element-ui': 'ELEMENT',
        })
    }
}           

複制

然後你需要在你項目中的根html使用cdn引入第三方庫:

<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <script src="https://cdn.bootcss.com/vue/2.6.10/vue.runtime.min.js"></script>
    <script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>
    <script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>
    <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
    <script src="https://cdn.bootcss.com/element-ui/2.11.1/index.js"></script>           

複制

這種方式大大減少了打包之後的項目體積并且不影響項目中這些庫的使用。 但是這種方式也有缺點,在項目加載的時候cdn依賴網絡。不論是cdn還是打包在項目中,在首屏加載時候都一樣會加載,隻是第三方庫在不在包裡的差別。

關于打包之後的Map檔案,可以通過productionSourceMap:boolen來舍取,Map檔案占的體積非常大。

4.安裝可視化打包分析器(可選)

安裝:

npm install --save-dev webpack-bundle-analyzer           

複制

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  configureWebpack:{
    plugins:[
      new BundleAnalyzerPlugin()
    ]
  }
}           

複制

BundleAnalyzerPlugin中的預設可選參數: // 可以是

server

static

disabled

。 // 在

server

模式下,分析器将啟動HTTP伺服器來顯示軟體包報告。 // 在“靜态”模式下,會生成帶有報告的單個HTML檔案。 // 在

disabled

模式下,你可以使用這個插件來将

generateStatsFile

設定為

true

來生成Webpack Stats JSON檔案。 analyzerMode: 'server', // 将在“伺服器”模式下使用的主機啟動HTTP伺服器。 analyzerHost: '127.0.0.1', // 将在“伺服器”模式下使用的端口啟動HTTP伺服器。 analyzerPort: 8888, // 路徑捆綁,将在

static

模式下生成的報告檔案。 // 相對于捆綁輸出目錄。 reportFilename: 'report.html', // 子產品大小預設顯示在報告中。 // 應該是

stat

parsed

或者

gzip

中的一個。 defaultSizes: 'parsed', // 在預設浏覽器中自動打開報告 openAnalyzer: true, // 如果為true,則Webpack Stats JSON檔案将在bundle輸出目錄中生成 generateStatsFile: false, // 如果

generateStatsFile

true

,将會生成Webpack Stats JSON檔案的名字。 // 相對于捆綁輸出目錄。 statsFilename: 'stats.json', // stats.toJson()方法的選項。 // 例如,您可以使用

source:false

選項排除統計檔案中子產品的來源。 // 在這裡檢視更多選項: // https//github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21 statsOptions: null, logLevel: 'info' // 日志級别。可以是'資訊','警告','錯誤'或'沉默'。

效果如圖

淺入webpack4 高效簡單的配置

5.總結

附上上面所有的配置代碼,一些沒提到的,代碼中都有注釋。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HappyPack = require('happypack')
/*
os 子產品提供了一些基本的系統操作函數
os.cpus()
傳回一個對象數組,包含所安裝的每個 CPU/核心的資訊:型号、速度(機關 MHz)、時間
(一個包含 user、nice、sys、idle 和 irq 所使用 CPU/核心毫秒數的對象)。
*/
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const UglifyPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  publicPath: '',
  outputDir: "dist",
  assetsDir: 'static',
  lintOnSave: false,
  productionSourceMap: false,
  configureWebpack: {
    plugins: [
      new BundleAnalyzerPlugin({        
          analyzerHost: '127.0.0.1',
          //區分開發和生産環境的端口,若你的項目在開發環境運作的情況下進行打包他就會使用另一個端口号,不會産生沖突。
          //這裡需要在根目錄下建立.env.development進行一個簡單的配置 NODE_ENV=development 區分環境
        analyzerPort: process.env.NODE_ENV == 'development' ? 8888 : 8889,
      }),
      new HappyPack({
        //用id來辨別 happypack處理那裡類檔案 要和 rules 中指定的 id 對應起來
        id: 'babel',
        //如何處理  用法和loader 的配置一樣
        loaders: ['babel-loader?cacheDirectory=true'],
        //共享程序池
        threadPool: happyThreadPool
      })
    ],
    optimization: {
      runtimeChunk: 'single',
      splitChunks: {
        chunks: 'all',
        maxInitialRequests: Infinity, //最大初始化請求 
        minSize: 20000, // 依賴包超過20000bit将被單獨打包
        cacheGroups: {  
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name(module) {
              //  拆分每個 npm 包
              const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
              return `npm.${packageName.replace('@', '')}`
            }
          }
        }
      }

    }
    //關閉webpack性能提示
    // performance: {
    //   // hints: 'warning',            
    //   //入口起點的最大體積 整數類型(以位元組為機關)
    //   maxEntrypointSize: 50000000,
    //   //生成檔案的最大體積 整數類型(以位元組為機關 300k)
    //   maxAssetSize: 30000000,
    //   //隻給出 js 檔案的性能提示
    //   assetFilter: function(assetFilename) {
    //       return assetFilename.endsWith('.js');
    //   }
    // }
  },
  chainWebpack: config => {
    //改用CDN引入子產品
    config.externals({
      'vue': 'Vue',
      'vuex': 'Vuex',
      'vue-router': 'VueRouter',
      'axios': 'axios',
      'element-ui': 'ELEMENT',
    })
    //在這裡使用loader時候後面跟的id就是happypack處理那一類檔案
    const jsRule = config.module.rule('js');
    jsRule.uses.clear();
    jsRule.use('happypack/loader?id=babel')
      .loader('happypack/loader?id=babel')
      .end();

  },

}           

複制

參考文章: vue-cli3 webpack配置 happypack原了解析 打封包件分析工具 核心功能optimization介紹 webpack官網