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就暫時分享到這裏,這僅代表個人的觀點,如想了解更多請掃描二維碼

在這裏插入圖片描述
最後友情提醒大家要戴口罩,勤洗手,儘量減少外出,做好預防 措施,遠離新型冠狀病毒。

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