知多一點 webpack 的 CommonsChunkPlugin

sea_ljf發表於2019-03-04

hello~親愛的看官老爺們大家好~ 最近一直在學習 webpack 的相關知識。曾幾何時我總覺得 webpack 的體系龐大而難以掌握,一直迴避不願去學。然而偉人魯迅曾說過: 世上太多事會因無法掌握而使你狂躁不安,最好的解決方法就是硬著頭皮開始做! 因而就從比較簡單的 CommonsChunkPlugin 開始學起吧~

雖然本文比較簡單,但還是需要一點 webpack 知識的,如若完全沒接觸過 webpack ,建議先移步 官方文件Webpack 3,從入門到放棄 瞭解一下 webpack 基礎為佳~

基礎配置

CommonsChunkPlugin 外掛,是一個可選的用於建立一個獨立檔案(又稱作 chunk)的功能,這個檔案包括多個入口 chunk 的公共模組。通過將公共模組拆出來,最終合成的檔案能夠在最開始的時候載入一次,便存起來到快取中供後續使用。這個帶來速度上的提升,因為瀏覽器會迅速將公共的程式碼從快取中取出來,而不是每次訪問一個新頁面時,再去載入一個更大的檔案。

簡單來說,這有點像封裝函式。把不變的與變化的分開,使得不變的可以高效複用,變化的靈活配置。接下來會根據這個原則優化我們的專案,現在先看看虛擬的專案長成什麼樣吧~

新建一個 index.html 模板與入口 index.js檔案,簡單配置如下:

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>{{ vue_test }}</p>
    </div>
    <div class="jq_test"></div>
</body>
</html>複製程式碼

index.js:

import Vue from `vue`;
import $ from `jquery`;

new Vue({
  el: `#app`,
  data: {
    vue_test: `vue is loaded!`
  }
})

$(function() {
  $(`.jq_test`).html(`jquery is loaded!`)
})複製程式碼

為演示起見,程式碼十分簡單,相信不用多加解釋。接下來先簡單配置一下 webpack.config.js,程式碼如下:

const path = require(`path`);
const webpack = require(`webpack`);
const HtmlWebpackPlugin = require(`html-webpack-plugin`);
const CleanWebpackPlugin = require(`clean-webpack-plugin`);
const BundleAnalyzerPlugin = require(`webpack-bundle-analyzer`).BundleAnalyzerPlugin;

module.exports = {
  entry: {
    index: path.join(__dirname, `index.js`)
  },
  output: {
    path: path.join(__dirname, `/dist`),
    filename: `js/[name].[chunkhash].js`
  },
  resolve: { alias: { `vue`: `vue/dist/vue.js` } },
  plugins: [
    new CleanWebpackPlugin([`./dist`]),
    new HtmlWebpackPlugin({
      filename: `index.html`,
      template: `index.html`,
      inject: true
    }),
    new BundleAnalyzerPlugin(),
  ]
};複製程式碼

CleanWebpackPlugin 主要用於清除 dist 目錄下的檔案,這樣每次打包就不必手動清除了。HtmlWebpackPlugin 則是為了在 dist 目錄下新建 html 模板並自動插入依賴的 jsBundleAnalyzerPlugin 主要是為了生成打包後的 js 檔案包含的依賴,如此時進行打包,則生成:

可以看到生成的 index.js 檔案包含了 vuejquery

首次優化

一般而言,我們專案中的類庫變化較少,業務程式碼倒是多變的。需要想辦法把類庫抽離出來,把業務程式碼單獨打包。這樣加傷 hash 後瀏覽器就能快取類庫的 js 檔案,優化使用者體驗。此時我們的主角 CommonsChunkPlugin 就正式登場了。我們在 webpack.config.js 檔案的 plugins 中新增 CommonsChunkPlugin,配置如下:

plugins: [
    //...此前的程式碼
    new webpack.optimize.CommonsChunkPlugin({
      name: `vendor`,
      minChunks: function(module) {
        return (
          module.resource &&
          /.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, `./node_modules`)
          ) === 0
        )
      }
    }),
]複製程式碼

上述配置,是通過 CommonsChunkPlugin 生成一個名為 vendorjs 檔案,它抽取入口檔案也就是 index.js 中來源於 node_modules 的依賴組成。此例中就是 vuejquery。打包出來畫風是這樣的:

此時看上去解決了我們的問題,將依賴的類庫抽取抽來獨立打包,加上快取就能被瀏覽器快取了。然而事情沒那麼簡單,不行你隨意改一下入口的 index.js 程式碼,再次打包:

絕望地發現 vendor.js 檔案的 hash 改變了。簡單說,這是因為模組標識產生了變化所導致的,更具體的原因可以檢視相關的中文文件~修正的方法其實也挺簡單,就是再使用 CommonsChunkPlugin 抽取一次模組,將不變的類庫沉澱下來,將變化的抽離出去。因而添如下程式碼:

plugins: [
    //...此前的程式碼
    new webpack.optimize.CommonsChunkPlugin({
      name: `vendor`,
      minChunks: function(module) {
        return (
          module.resource &&
          /.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, `./node_modules`)
          ) === 0
        )
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: `manifest`,
      chunks: [`vendor`, `index`]
    })
]複製程式碼

打包後, dist/js 目錄下多出一個名為 manifestjs 檔案,此時你無論如何改變 index.js 的程式碼,打包後的 vendor.jshash 都不再會改變了。

然而稍等,當你想拍拍手收工的時候,思考一下這樣的場景:隨著專案不斷迭代,vendor 中的依賴不斷被新增與刪除,使得它的 hash 會不斷變化,這顯然不符合我們的利益,這到底如何解決呢?

再次優化

既然 CommonsChunkPlugin 是可以按照我們的需求抽取模組,而依賴的外部模組可能是不斷變化的,那麼為何不將基礎的依賴模組抽取出來作為一個檔案,其他的依賴如外掛等作為另一個檔案呢?

簡單說,如我們的專案中 vue 是基本的依賴,必須用到它,而 jquery 等則是後加的類庫,之後可能變更。那麼將 vue 獨立打包一個檔案,有利於瀏覽器快取,因為無論此後新增更多的類庫或刪去 jquery 時, vue 檔案的快取依然是生效的。因而我們可以這麼做,首先新建一個入口:

entry: {
    index: path.join(__dirname, `index.js`),
    vendor: [`vue`],
},複製程式碼

此處主要是用於指明需要獨立打包的依賴有哪些。之後在 plugins 中做如下修改:

plugins: [
    //...此前的程式碼
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: `vendor`,
      minChunks: Infinity,
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: `common`,
      minChunks: function(module) {
        return (
          module.resource &&
          /.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, `./node_modules`)
          ) === 0
        )
      },
      chunks: [`index`],
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: `manifest`,
      chunks: [`vendor`, `common`, `index`]
    })
]複製程式碼

外掛 HashedModuleIdsPlugin,是用於保持模組引用的 module id 不變。而 CommonsChunkPlugin 則提取入口指定的依賴獨立打包,minChunks: Infinity,的用意是讓外掛別管其他,就按照設定的陣列提取檔案就好。之後修改一下原來的 vendor,重新命名為 common,指定它從入口 index.js 中抽取來自 node_modules 的依賴。最後就是抽取 webpack 執行時的函式及其模組標識組成 manifest。執行一下 webpack,構建出來如圖:

可以看到 vuejquery 被分開打包成了兩個檔案,我們嘗試新增一下新的依賴 vuex,打包後結果如下:

如此一來,我們的優化目的就達到了,不變的都提取出來,變化的可以動態配置~

小結

webpack 外掛 CommonsChunkPlugin 就介紹到這裡了,然而優化還是有很多的,比如開啟壓縮,去除註釋等。而當專案體積逐漸增大時,CommonsChunkPlugin 就不一定是提取程式碼的最優解了。在打包速度與控制構建的精細程度來說,結合 DLLPlugin 會有更好的表現。根據不同的場景組合不同的外掛以達到我們的目的,本來就是 webpack 的魅力之一。

感謝各位看官大人看到這裡,知易行難,希望本文對你有所幫助,所有的程式碼均會被上傳到 github 上,滾求 star ~謝謝!

相關文章