1. 程式人生 > >Webpack實戰(八):教你搞懂webpack如果實現程式碼分片(code splitting)

Webpack實戰(八):教你搞懂webpack如果實現程式碼分片(code splitting)

2020年春節已過,本來打算回鄭州,卻因為新型冠狀病毒感染肺炎的疫情公司推遲了上班的時間,我也推遲了去鄭州的時間,在家多陪娃幾天。以前都是在書房學習寫部落格,今天比較特殊,抱著電腦,在樓頂晒著太陽,陪著家人,寫著部落格。

前面的幾篇文章主要告訴大家如何安裝、配置webpack、webpack實現樣式分離等,今天這篇文章主要跟大家分享如果webpack如何實現程式碼分片。

現在工程專案中,實現高效能應用的其中重要的一點就是讓使用者每次只加載必要的資源,優先級別不太高的資源採用延遲載入等技術漸進地進行載入獲取。

Webpack 作為打包工具所特有的一項技術就是程式碼分片技術,通過這項技術我們可以把程式碼按照特定的形式進行拆分,使用按需載入資源,不必要全部載入下來。
程式碼分片可以有效降低首屏載入資源的大小,但是我們同時又面臨著其他問題,比如如何對專案模組進行分片,分片後的資源如何進行管理等等。今天我們需要對這些問題進行分析解決。

### 利用入口劃分程式碼

在Webpack中,配置引數中每個入口都將生成一個對應的資原始檔,通過入口的配置我們可以進行一些簡單有效的程式碼拆分。

對於專案中常常會引入一些第三方庫和工具,這些一般不會改動的,可以把它們單獨放在一個入口中,由該入口的資源不會經常更新,因此可以有效利用客戶端快取這些資源,讓使用者不必在每次請求頁面時候都重新載入。

//webpack.config.js
entry: {
    index: './index.js',
    lib: ['lib-1', 'lib-2']
}

//index.html
<script src="dist/lib.js"></script>
<script src="dist/index.js"></script>

這種拆分方法主要適合於那些將介面繫結在全域性物件上的庫,因為業務程式碼中的模組無法直接引用庫中的模組,二者屬於不同的依賴樹。

對於多頁面應用來說,我們可以利用入口劃分的方式拆分程式碼。比如,為每一個頁面建立一個入口,並放入只涉及該頁面的程式碼,同時再建立一個入口來包含所有公共模組,並使每個頁面都進行載入。但是這樣仍會帶來公共模組與業務模組處於不同依賴樹的問題。另外,很多時候不是所有的頁面都需要這些公共模組。這就需要我們利用webpack專用的外掛來解決這種問題了。

CommonsChunkPlugin

CommonsChunkPlugin是webpack4之前內部自帶的外掛,webpack4之後用的是SplitChunks。CommonsChunkPlugin主要是用來提取第三方庫和公共模組,避免首屏載入的bundle檔案或者按需載入的bundle檔案體積過大,從而導致載入時間過長,是一把優化專案的利器。

優點:

  • 開發過程中減少了重複模組打包,可以提升開發速度;
  • 減小整體資源體積;
  • 合理分片後的程式碼可以更有效地利用客戶端快取。
    首頁通過一個簡單的例子也說明,假設我們當前專案中有a.js 和b.js 兩個入口檔案,並且都引入了react,下面是未使用CommonsChunkPlugin的配置
//webpack.config.js
module.exports = {
    entry: {
        a: './a.js',
        b: './b.js'
    },
    output: {
        filename: '[name].js'
    }
}

//a.js
import React from 'React'
... //省略

// b.js 
import React from 'React'
... //省略

如果打包,從打包的資源體積可以看出,react被分別打包到a.js 和b.js中。

更改webpack.config.js,新增CommonsChunkPlugin配置

const webpack = require('webpack');
module.exports = {
    entry: {
        a: './a.js',
        b: './b.js'
    },
    output: {
        filename: '[name].js'
    },
    plugins: [
    new webpack.optimize.CommonsChunkPlugin({
    name: 'commons',
    filename: 'commons.js'
})
]
}

在配置檔案的頭部引入Webpack,接著使用其內部CommonsChunkPlugin函式建立了一個外掛例項,並傳入配置物件,配置引數可以理解為

  • name:用於指定公共chunk的名字
  • filename: 提取後的資原始檔名
    打包後可以看到,產出的資源中多了一個commons.js,而a.js 和b.js檔案的體積也減少了,這是由於把react及依賴的模組提到commons.js的原因。
    不過我們需要注意的是,我們需要在頁面引入其他j s之前,先引入公用的commons.js檔案。

在提取公共模組方面,CommonsChunkPlugin可以滿足很多場景的需求,但是它也有一些欠缺的地方。
1)一個CommonsChunkPlugin只能提取一個vendor,假如我們想提取多個vendor則需要配置多個外掛,這會增加很多重複的配置程式碼。

2)前面我們提到的manifest實際上會使瀏覽器多載入一個資源,這對於頁面渲染速度是不友好的。

3)由於內部設計上的一些缺陷,CommonsChunkPlugin在提取公共模組的時候會破壞掉原有Chunk中模組的依賴關係,導致難以進行更多的優化。比如在非同步Chunk的場景下CommonsChunkPlugin並不會按照我們的預期正常工作。

optimization.SplitChunks

optimization.SplitChunks(簡稱SplitChunks)是Webpack 4為了改進CommonsChunk-Plugin而重新設計和實現的程式碼分片特性。它不僅比CommonsChunkPlugin功能更加強大,還更簡單易用。

配置檔案web pack.config.js為:

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  context: path.join(__dirname, './src'),
  entry: {
    index: './index.js'
  },
  output: {
    // path: path.join(__dirname, 'dist'),
    filename: 'index.js',
    publicPath: '/dist/'
  },
  mode: 'development',
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', {
          loader: 'css-loader',
          options: {
            modules: {
              localIdentName: '[path][name]__[local]--[hash:base64:5]',
            }
          }
        }]
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            presets: [
              [
                'env', {
                  modules: false
                }
              ]
            ]
          }
        }
      }
    ],
  }
}

需要引入的兩個index.js檔案和index2.js檔案

// index
import index2 from  './index2.js';
import React from 'react'
document.write('index.js', React.version);

//index2
import React from 'react'
document.write('index2.js', React.version);

使用optimization.splitChunks替代了CommonsChunkPlugin,並指定了chunks的值為all,這個配置項的含義是,SplitChunks將會對所有的chunks生效(預設情況下,SplitChunks只對非同步chunks生效,並且不需要配置)
打包結果如下圖:

原本我們打包的結果應該是index.js,但是由於SplitChunks的存在,又生成了一個vendors~index.index.js,並且把react提取到了裡面。
執行的效果如下圖:

在使用CommonsChunkPlugin的時候,我們大多數時候是通過配置項將特定入口中的特定模組提取出來,也就是更貼近命令式的方式。而SplitChunks的不同之處在於我們只需要設定一些提取條件,如提取的模式、提取模組的體積等,當某些模組達到這些條件後就會自動被提取出來。SplitChunks的使用更像是宣告式的。

SplitChunks預設情形下的提取條件:

  • 提取後的chunk可被共享或者來自node_modules目錄。這一條很容易理解,被多次引用或處於node_modules中的模組更傾向於是通用模組,比較適合被提取出來。
  • 提取後的Javascript chunk體積大於30kB(壓縮和gzip之前),CSS chunk體積大於50kB。這個也比較容易理解,如果提取後的資源體積太小,那麼帶來的優化效果也比較一般。
  • 在按需載入過程中,並行請求的資源最大值小於等於5。按需載入指的是,通過動態插入script標籤的方式載入指令碼。我們一般不希望同時載入過多的資源,因為每一個請求都要花費建立連結和釋放連結的成本,因此提取的規則只在並行請求不多的時候生效。
  • 在首次載入時,並行請求的資源數最大值小於等於3。和上一條類似,只不過在頁面首次載入時往往對效能的要求更高,因此這裡的預設閾值也更低。

SplitChunks提取方式

SplitChunks 預設的提取方式是非同步提取,當我們在chunks上配置引數為all的時候,不是非同步資源也可以提取。

SplitChunks 配置

optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minRemainingSize: 0,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 6,
      maxInitialRequests: 4,
      automaticNameDelimiter: '~',
      name: true,
      automaticNameMaxLength: 30,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }

(1)匹配模式通過chunks我們可以配置SplitChunks的工作模式。它有3個可選值,分別為async(預設)、initial和all。async即只提取非同步chunk,initial則只對入口chunk生效(如果配置了initial則上面非同步的例子將失效),all則是兩種模式同時開啟。

(2)匹配條件minSize、minChunks、maxAsyncRequests、maxInitialRequests都屬於匹配條件,

(3)命名配置項name預設為true,它意味著SplitChunks可以根據cacheGroups和作用範圍自動為新生成的chunk命名,並以automaticNameDelimiter分隔。如vendors~a~b~c.js意思是cacheGroups為vendors,並且該chunk是由a、b、c三個入口chunk所產生的。

(4)cacheGroups可以理解成分離chunks時的規則。預設情況下有兩種規則——defaultVendors和default。defaultVendors用於提取所有node_modules中符合條件的模組,default則作用於被多次引用的模組。我們可以對這些規則進行增加或者修改,如果想要禁用某種規則,也可以直接將其置為false。當一個模組同時符合多個cacheGroups時,則根據其中的priority配置項確定優先順序。

總結

有關webpack實現程式碼分片的幾種方法:合理地規劃入口,使用Commons-ChunkPlugin或SplitChunks就暫時分享到這裡,這僅代表個人的觀點,如想了解更多請掃描二維碼


最後友情提醒大家要戴口罩,勤洗手,儘量減少外出,做好預防 措施,遠離新型冠狀病毒