webpack二刷之五、生產環境優化(4.Code Splitting 代碼分包 / 代碼分割)

Code Splitting 代碼分包 / 代碼分割

webpack默認打包依然存在一些弊端。

就是所有代碼最終都會被打包到一起(一個bundle文件中)。

如果項目中代碼比較複雜,模塊比較多,打包結果就會很大(bundle體積過大),很容易超過2、3MB。

而實際情況是,項目啓動時,並不是每個模塊都需要加載進來。

但是這些模塊又被全部打包到一起,需要任何一個模塊都必須整體加載進來後才能使用。

項目運行在瀏覽器端,這樣就會浪費掉很多的流量和帶寬。

更爲合理的方案,就是「分包」「按需加載」

  • 分包:將打包結果,按照一定的規則,分離到多個bundle當中。
  • 按需加載:根據應用的運行需要,按需加載這些模塊。

這樣就會大大提高項目的響應速度和運行效率。

webpack 合併 與 分離

前面說webpack就是將代碼中散落的模塊合併到一起,從而提高運行效率。

現在又要求將它們分離開,這是「物極必反」的結果。資源太大不行,太碎了也不行。

項目中劃分模塊的顆粒度一般非常的細。

很多時候一個模塊只是提供了一個小小的工具函數,並不能形成一個完整的功能單元。

如何不將這些散落的模塊合併到一起,在運行一個小小的功能時,就會加載很多的模塊。

而當前HTTP1.1版本有很多缺陷,例如:

  1. 同域並行請求限制:不能同時對同一個域名發起多次請求
  2. 每次請求都會有一定的延遲
  3. 每次請求除了傳輸具體的內容外,還會有額外的請求頭和響應頭,當有大量請求時,這些請求頭和響應頭加在一起,也是很大的浪費

綜上所述,模塊打包(合併)是有必要的。

不過在應用越來越大之後,也要慢慢學會變通。

Code Splitting

爲了解決打包文件過大的問題,webpack支持「代碼分包」的功能,也可以稱爲「代碼分割」。

它通過把打包的模塊按照我們設計的規則打包到不同的bundle當中,從而提高應用的響應速度。

目前webpack實現 「分包」的方式主要有兩種:

  1. 多入口打包
    1. 根據業務配置不同的打包入口
    2. 配置多個入口同時打包,最終輸出多個打包結果
  2. 動態導入
    1. 採用ESM的動態導入的功能import(),實現模塊的按需加載。
    2. webpack會將這些動態導入的模塊,單獨輸出到一個bundle當中。

多入口打包 Multi Entry

多入口打包,一般適用於傳統的多頁應用程序。

最常見的劃分規則就是:

  1. 一個頁面對應一個打包入口。
  2. 對於不同頁面公共的部分,單獨提取到一個公共的結果中。

webpack配置文件的entry屬性配置類型:

  • 字符串:一個入口文件
  • 字符串數組:多個入口文件打包到一起,相當於一個入口打包
  • 對象:多個入口文件分別打包
    • key:入口的名稱
    • value:入口對應的文件路徑

每個打包入口會形成一個獨立的chunk,入口名稱就是這個chunk的name(默認是main)。

一旦配置爲多入口,輸出的文件名也需要修改:

使用[name]佔位符,動態輸出文件名,[name]最終替換爲入口的名稱,即entry的key。

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  entry: {
    index: './src/index.js',
    album: './src/album.js',
  },
  output: {
    // 使用[name]佔位符,動態輸出文件名
    // [name]最終替換爲入口的名稱,即entry的key
    filename: '[name].bundle.js',
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/index.html',
    }),
    new HtmlWebpackPlugin({
      filename: 'album.html',
      template: './src/album.html',
    }),
  ],
}

此時打包會輸出兩個html和兩個bundle文件,但是發現html文件中將兩個bundle文件全部引用了。

這是因爲html-webpack-plugin插件會自動生成一個注入所有打包結果的html。

如果需要指定輸出的html所使用的bundle,可以使用插件的chunks屬性配置:

new HtmlWebpackPlugin({
  filename: 'index.html',
  template: './src/index.html',
  chunks: ['index'],
}),
new HtmlWebpackPlugin({
  filename: 'album.html',
  template: './src/album.html',
  chunks: ['album'],
}),

每個打包入口,都會形成一個獨立的chunk

而插件的chunks屬性,通過chunk的名稱,指定需要注入哪些chunk


多入口打包缺陷:

不同入口中肯定會有公共的模塊,根據現在多入口打包的方式,就會出現在不同的打包結果中,會有相同的模塊出現。

例如上面的index.js和album.js都導入了一個公共模塊或css模塊。

解決方案:

將這些公共的模塊單獨提取輸出到一個公共的bundle中。

提取公共模塊 Split Chunks

webpack可以在optimization優化配置中開啓splitChunks功能,實現提取公共模塊打包。

optimization: {
  splitChunks: {
    // all 表示將所有的公共模塊都提取到單獨的bundle當中
    chunks: 'all'
  }
}

打包後,就會多生成一個album~index.bundle.js文件。

它存放的就是index.js和album.js中公共的模塊。

動態導入 Dynamic Imports

「按需加載」是開發瀏覽器應用中常見的需求。

一般指的按需加載數據。

這裏的按需加載指的是:應用運行過程中,需要哪個模塊時,纔會加載那個模塊。

這個方式可以極大的節省帶寬和流量。

webpack支持使用動態導入的方式,實現模塊的按需加載。

而且所有動態導入的模塊,都會被自動分包(形成一個單獨的chunk,提取到單獨的bundle當中)。

相比多入口方式,動態導入更爲靈活。

可以通過代碼的邏輯控制是否需要加載某個模塊,或何時加載。

使用ESM的import()方法導入指定的模塊,它返回一個promise。

例如:

// 入口文件 /src/index.js
const render = (query) => {
  if (query == '#posts') {
    import('./posts/posts')
    	.then(({default: posts}) => {
      	console.log(posts())
	    })
  } else if (query == 'album') {
    import('./album/album')
    	.then(({default: album}) => {
      	console.log(album())
	    })
  }
}
// PS:假設posts和album模塊中都使用了相同的公共模塊

打包會多生成3個文件,文件名爲[number].bundle.js

分別是posts和album模塊,另一個是從二者提取出來的公共模塊。

這三個文件就是有動態導入、自動分包生成的。

整個過程不需要配置任何地方,例如不需要配置optimization.splitChunks就可以實現提供公共模塊。

只需要按照ESM動態導入成員的方式導入模塊即可。

webpack內部會自動處理分包、按需加載以及提取公共模塊。

如果使用的是單頁應用開發框架(如vue,react),項目中的路由映射組件,就可以通過這種動態導入的方式,實現按需加載。

魔法註釋 Magic Comments

默認通過動態導入產生的bundle文件,它的名稱是一個序號,文件名爲[number].bundle.js

可以通過webpack特有的魔法註釋,給它們定義名稱。

具體使用就是,在import()的參數位置(前後都可以),添加一個特定格式的行內註釋:

// 格式:/*webpackChunkName:'<name>'*/
import(/* webpackChunkName: 'posts' */'./posts/posts').then(() => {})
import('./posts/posts'/* webpackChunkName: 'album' */).then(() => {})

生成文件:

  • posts.bundle.js
  • album.bundle.js
  • album~posts.bundle.js 提供公共模塊的文件也同步變化

如果多個模塊使用的相同的chunkName,那它們最終會被打包到一起,自然不需要提取公共模塊,最終只會生成一個文件。

藉助這個特點,就可以根據情況,靈活組織動態導入的模塊所輸出的文件。

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