webpack3的CommonsChunkPlugin插件詳解

webpack打出來的包在不做處理的情況下是非常大的,所有依賴都被塞進一個文件中,文件中有業務代碼,有業務代碼依賴的第三方庫代碼,還有webpack生成的運行時代碼等。這樣的一個文件不方便靜態資源緩存,並且初始化頁面的時候下載了所有的JS這是沒必要的,拖慢了頁面速度。所以對於webpack打包的資源文件進行分割按需加載是很重要的一件事情。

webpack4都出來了爲啥要寫一篇關於webpack3的文章。

目前webpack3應用的還是很多,並且學習相關知識協查找過相關資料很多遍,所以這次總結一下通過webpack3分割代碼的方法,方便後期需要的時候方便查閱。

在webpack3中使用的分割thunk方法主要是使用webpack自帶的插件(webpack.optimize.CommonsChunkPlugin)實現的。

首先通過webpack來構建項目

目錄結構:

|— src

​ |— index.html

​ |— indexa.js

​ |— indexb.js

|— webpack.config.js

|— node_modules

​ |— jquery/

indexa.js

import $ from 'jquery'

$() // 調用一下

console.log('我是indexa.js')

indexb.js

import $ from 'jquery'

$() // 調用一下

console.log('我是indexb.js')

webpack.config.js

// output中的path需要絕對路徑
let path = require('path')
// 用於將打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: {
    indexa: './src/indexa.js',
    indexb: './src/indexb.js'
  },
  output: {
    path: path.resolve('./dist/'),
    filename: '[name].[chunkHash].js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}

上面的示例中有兩個入口,一個是pagea.js,另一個是pageb.js。這兩個入口都引入了jquery.js,並且打包結果中我們看到jquery.js被同時打到了兩個入口文件中。

在這裏插入圖片描述

提取公共第三方庫jquery

這樣的結果顯然不是我們期望的,我們期望兩個入口都引入的jquery被打到單獨的包中,然後在兩個入口引入這個包即可。這就需要藉助webpack.optimize.CommonsChunkPlugin插件,下面修改webpack.config.js文件爲:

// output中的path需要絕對路徑
let path = require('path')
// 用於將打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin

module.exports = {
  entry: {
    indexa: './src/indexa.js',
    indexb: './src/indexb.js',
    jquery: ['jquery'] // 依賴的第三方庫node_modules中
  },
  output: {
    path: path.resolve('./dist/'),
    filename: '[name].[chunkHash].js'
  },
  plugins: [
    new CommonsChunkPlugin({
      name: 'jquery', // 如果有該名稱的chunk則選擇這個chunk提取公共文件,這裏是jquery,如果沒有則生成的文件是這個名稱的chunk
    }),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}

通過CommonsChunkPlugin插件我們提取了在indexa和indexb中都引入的jquery庫這樣打包結果中pagea.js和pageb.js就大幅減小了。

在這裏插入圖片描述

提取自定義公共模塊

通常我們不止有第三方的公共模塊,我們自己也會寫一些公用的工具方法。現加入公用工具方法文件utils.js。

utils.js

function common () {
  console.log('我是工具方法')
}

export {
  common
}

修改pagea.js文件

import $ from 'jquery'
import {common} from './utils.js'

common() // 新加的

$() // 調用一下

console.log('我是indexa.js')

修改pageb.js文件

import $ from 'jquery'
import {common} from './utils.js'

common() // 新加的

$() // 調用一下

console.log('我是indexb.js')

打包後發現自己的公共方法文件被打包到了jquery……js文件中了,我們並不希望這樣,因爲第三方庫一般是不會修改的,我們希望每次打包第三方庫的名稱不變,這樣有助於客戶端緩存。所以我們需要從當前的jquery…js中提取出自己的公共方法文件。

在這裏插入圖片描述

分離utils.js文件和jquery等第三方庫文件

  1. 修改webpack.config.js
// output中的path需要絕對路徑
let path = require('path')
// 用於將打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin

module.exports = {
  entry: {
    indexa: './src/indexa.js',
    indexb: './src/indexb.js',
    jquery: ['jquery'] // 依賴的第三方庫node_modules中
  },
  output: {
    path: path.resolve('./dist/'),
    filename: '[name].[chunkHash].js'
  },
  plugins: [
    new CommonsChunkPlugin({
      name: 'jquery', // 如果有該名稱的chunk則選擇這個chunk提取公共文件,這裏是jquery,如果沒有則生成的文件是這個名稱的chunk
      miniChunks: Infinity // 這樣就只會打包出自身chunk和 webpack生成的一些文件
    }),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}

通過打包結果發現我們自定義的模塊確實從jquery中提取了出來,但是卻打到了每個引入的頁面中,這也是我們接受不了的。

在這裏插入圖片描述

  1. 從單個頁面中分離公共方法

修改webpack.config.js文件如下:

// output中的path需要絕對路徑
let path = require('path')
// 用於將打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin

module.exports = {
  entry: {
    indexa: './src/indexa.js',
    indexb: './src/indexb.js',
    jquery: ['jquery'] // 依賴的第三方庫node_modules中
  },
  output: {
    path: path.resolve('./dist/'),
    filename: '[name].[chunkHash].js'
  },
  plugins: [
    new CommonsChunkPlugin({
      name: 'jquery', // 如果有該名稱的chunk則選擇這個chunk提取公共文件,這裏是jquery,如果沒有則生成的文件是這個名稱的chunk
      minChunks: Infinity // 這樣就只會打包出自身chunk和 webpack生成的一些文件
    }),
    new CommonsChunkPlugin({
      name: 'utils',
      chunks: ['indexa', 'indexb']
    }),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}

打包結果如下,可以發現utils被提取了出來,這樣就我們的目的就達到了。

在這裏插入圖片描述

minChunks的函數值

我們發現第三方庫我們是通過entry字段手動添加的,這樣比較麻煩,不能以後添加一個第三方庫我們就手動修改一下entry的jquery數組。

我們可以通過minChunks的值傳入一個函數來做,函數返回true則會被打包。修改webpack.config.js文件如下:

// output中的path需要絕對路徑
let path = require('path')
// 用於將打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin

module.exports = {
  entry: {
    indexa: './src/indexa.js',
    indexb: './src/indexb.js',
    // 已經不需要了 jquery: ['jquery'] // 依賴的第三方庫node_modules中
  },
  output: {
    path: path.resolve('./dist/'),
    filename: '[name].[chunkHash].js'
  },
  plugins: [
    new CommonsChunkPlugin({
      name: "vendor", // 修改jquery名稱爲vendor,第三方庫集合
      minChunks: function (module, ) {
        // node_modules中出來的都打到這個文件中
        return module.context && module.context.includes("node_modules");
      }
    }),
    new CommonsChunkPlugin({
      name: 'utils',
      chunks: ['indexa', 'indexb']
    }),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}

打包結果如下,只是將jquery…js的名稱換成了vendor…js其他沒有任何變化。

在這裏插入圖片描述

讓moduleId固定下來

通過上面兩張截圖的觀察我們可以發現indexb…js的chunkHash不一樣了,但是我們並沒有修改文件內容。這是因爲webpack生成模塊的moduleId在變化。讓moduleId停止變化的插件有兩個,一個是HashedModuleIdsPlugin,還有一個是NamedModulesPlugin。

HashedModuleIdsPlugin: 該插件會根據模塊的相對路徑生成一個四位數的hash作爲模塊id, 建議用於生產環境。

NamedModulesPlugin: 當開啓 HMR 的時候使用該插件會顯示模塊的相對路徑,建議用於開發環境。

我們就選擇生產環境用的插件,修改webpack.config.js文件如下:

// output中的path需要絕對路徑
let path = require('path')
// 用於將打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin

module.exports = {
  entry: {
    indexa: './src/indexa.js',
    indexb: './src/indexb.js',
    // 已經不需要了 jquery: ['jquery'] // 依賴的第三方庫node_modules中
  },
  output: {
    path: path.resolve('./dist/'),
    filename: '[name].[chunkHash].js'
  },
  plugins: [
    new CommonsChunkPlugin({
      name: "vendor", // 修改jquery名稱爲vendor,第三方庫集合
      minChunks: function (module) {
        // node_modules中出來的都打到這個文件中
        return module.context && module.context.includes("node_modules");
      }
    }),
    new CommonsChunkPlugin({
      name: 'utils',
      chunks: ['indexa', 'indexb']
    }),
    // 固定下來模塊的moduleId
    new HashedModuleIdsPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}

runtime和manifest

其實vender中不止有node_module文件夾中的包,還包括

runtime: 指在瀏覽器運行時,webpack 用來連接模塊化的應用程序的所有代碼。其中包含:在模塊交互時,連接模塊所需的加載和解析邏輯。包括瀏覽器中的已加載模塊的連接,以及懶加載模塊的執行邏輯。

manifest: 當編譯器(compiler)開始執行、解析和映射應用程序時,它會保留所有模塊的詳細要點。這個數據集合稱爲 “Manifest”,當完成打包併發送到瀏覽器時,會在運行時通過 Manifest 來解析和加載模塊。無論你選擇哪種模塊語法,那些 import 或 require 語句現在都已經轉換爲 __webpack_require__ 方法,此方法指向模塊標識符(module identifier)。通過使用 manifest 中的數據,runtime 將能夠查詢模塊標識符,檢索出背後對應的模塊。

當模塊做出改變的時候manifest也會改變,同時也會導致vender改變,最後導致vender的緩存失效,這種失效並不是因爲vender本身內容的改變導致的,所以我們需要分離runtime和manifest。

提取runtime和manifest

修改webpack.config.js文件如下:

// output中的path需要絕對路徑
let path = require('path')
// 用於將打包的js文件注入到html文件中
let HtmlWebpackPlugin = require('html-webpack-plugin')
let webpack = require('webpack')
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin

module.exports = {
  entry: {
    indexa: './src/indexa.js',
    indexb: './src/indexb.js'
  },
  output: {
    path: path.resolve('./'),
    filename: '[name].[chunkHash].js'
  },
  plugins: [
    new CommonsChunkPlugin({
      name: "vendor", // 修改jquery名稱爲vendor,第三方庫集合
      minChunks: function (module) {
        // node_modules中出來的都打到這個文件中
        return module.context && module.context.includes("node_modules");
      }
    }),
    new CommonsChunkPlugin({
      name: 'utils',
      chunks: ['indexa', 'indexb']
    }),
    new CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    // 固定下來模塊的moduleId
    new HashedModuleIdsPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}

在這裏插入圖片描述

待補充:children字段和async字段的作用

注:

**hash: ** 一個隨機值,每次打包都會改變,建議用於開發。

**chunkHash: ** 根據文件內容生成一個隨機值,建議用於生產便於緩存。

**Infinity: **創建一個公共chunk,但是不包含任何模塊,內部是一些webpack生成的runtime代碼和chunk自身包含的模塊(如果chunk存在的話)。

**多CommonsChunkPlugin:**第二次使用CommonsChunkPlugin插件的時候如果不指定chunks默認針對前一個CommonsChunkPlugin插件生成的chunk做提取。

總結:

  1. node_modules中第三方庫的提取可以通過miniChunks傳入function來控制。
  2. 分離之後chunkHash還會變是應爲moduleId在改變可以使用插件HashedModuleIdsPlugin來固定下來
  3. manifest和runtime文件提取可以通過miniChunks: Infinity 來完成

參考

webpack4:連奏中的進化

Webpack4之SplitChunksPlugin規則

詳解CommonsChunkPlugin的配置和用法

CommonsChunkPlugin中children和async屬性詳解

Webpack2中的NamedModulesPlugin與HashedModuleIdsPlugin

runtime和manifest

hashed-module-ids-plugin

NamedModulesPlugin

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