webpack-代碼拆分

一、爲何要進行代碼拆分?

咱們先引入一個應用場景,而後對這個場景進行分析,瞭解爲何須要拆分代碼。
首先,安裝第三方庫lodash,而後在代碼分割.js中引入並編寫業務代碼。html

// 導入第三方庫
const _ = require('lodash')

// 業務邏輯代碼
console.log(_.join(['a', 'b', 'c']))

接着,配置webpack.config.js進行打包node

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  entry: './代碼分割.js',
  output: {
    path: path.resolve(__dirname, 'build'), // 打包文件的輸出目錄
    filename: '[name].bundle.js', // 代碼打包後的文件名
    publicPath: __dirname + '/build/', // 引用的路徑或者 CDN 地址
    chunkFilename: '[name].js' // 代碼拆分後的文件名
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ],
    
  },
  plugins: [
    new CleanWebpackPlugin() // 會刪除上次構建的文件,而後從新構建
  ]
}

打包完後會生成一個名爲main.bundle.js文件,咱們在index.html中引入,打開瀏覽器執行。
7.pngwebpack

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="./build/main.bundle.js"></script>
</body>
</html>

瀏覽器控制檯會輸出a,b,c
若是咱們改動index.js中的業務代碼,改成:git

// 導入第三方庫
const _ = require('lodash')

// 業務邏輯代碼
console.log(_.join(['a', 'b', 'c'], '***'))

控制檯結果變爲:a\*\*\*b\*\*\*cgithub

如今咱們拋出一個問題,當咱們把引入的第三方庫和業務代碼放在一塊兒打包,這樣會有什麼問題?web

假設上面第三方庫lodash大小爲1M,業務代碼爲1M,假設打包後的main.bundle.js2Mnpm

瀏覽器每次打開index.html時,都須要去加載main.bundle.js,而後執行業務代碼。加載2M代碼是很耗時的,可是瀏覽器有緩存機制,第二次加載同一文件時會從緩存中讀取,刷新頁面時網頁加載速度更快。可是事與願違,咱們的業務代碼更新很頻繁,致使不管是首次加載仍是再次加載都會很慢,那如何去解決這個問題呢?segmentfault

答案很明顯,第三方庫lodash代碼基本上是不會變的,若是咱們可以將業務代碼和第三方庫代碼分開加載,那麼第三方庫的加載就可用到緩存機制,整個頁面的加載時間也會縮短。瀏覽器

在多個js文件都引入了一樣的庫或者代碼的場景下也是能夠進行拆分,避免重複加載。緩存

webpack4 以前是使用 commonsChunkPlugin 來拆分公共代碼,v4 以後被廢棄,並使用 splitChunksPlugins

在使用 splitChunksPlugins 以前,首先要知道 splitChunksPluginswebpack 主模塊中的一個細分模塊,無需 npm 引入,只須要配置便可。

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  entry: './代碼分割.js',
  output: {
    path: path.resolve(__dirname, 'build'), // 打包文件的輸出目錄
    filename: '[name].bundle.js', // 代碼打包後的文件名
    publicPath: __dirname + '/build/', // 引用的路徑或者 CDN 地址
    chunkFilename: '[name].js' // 代碼拆分後的文件名
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  // 拆分代碼配置項
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  plugins: [
    new CleanWebpackPlugin() // 會刪除上次構建的文件,而後從新構建
  ]
}

咱們使用cnpm run dev在開發模式下打包文件,開發模式下打包不會壓縮文件,方便查看
8.png
能夠看到代碼拆分紅了兩個文件,打開main.bundle.js文件能夠看到裏賣弄存放的都是業務代碼,沒有lodash的代碼
9.png
打開vendors~main.js文件能夠看到lodash代碼都在裏面
10.png
這樣就完成了代碼拆分,拆分完的兩個文件都要引入到index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="./build/vendors~main.js"></script>
  <script src="./build/main.bundle.js"></script>
</body>
</html>

瀏覽器執行index.html文件控制檯結果:a\*\*\*b\*\*\*c

上面採用拆分代碼的模式是all,另外還有async、initial,咱們來了解一下

  • async: 只優化動態加載的代碼,其餘類型的代碼正常打包。
  • initial: 針對原始 bundle 代碼進行優化。
  • all: 針對全部代碼進行優化。
  • function(chunk)自定義拆分函數

詳情能夠參考本人另外一篇文章:
http://www.javashuo.com/article/p-nzqmzxip-ct.html

分割出來的文件名爲vendors~main.js,怎麼來的,咱們來分析一下:

當在splitChunks配置項中沒有添加cacheGroups對象中的name屬性時,默認會在文件名前面加上vendors字段。如今咱們來配置一下name屬性更改分割的文件名。

// 拆分代碼配置項
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          chunks: 'all',
          name: 'test' // 定義分割文件名
        }
      }
    }
  },

6.png
寫到這,有細心的人可能會問cacheGroups對象的做用是幹嗎的?

cacheGroups is a plain object with key being the name of chunk and value being some configuration of that chunk. By default, Webpack ships with vendors and default cacheGroups but let’s turn those off by setting their value to false, else it will just confuse you to understand code splitting.

主要概念就是cacheGroups對象中的key是分割塊的名稱,value是分割塊的相關配置。

二、如何拆分代碼?

如今有以下代碼:

// a,js
import './common'
console.log('A')
export default 'A'

// b.js
import './common'
console.log('B')
export default 'B'

// common.js
console.log('公共模塊')
export default 'common'

// index.js
// 異步代碼
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {
  console.log(a)
})

import(/* webpackChunkName: 'b'*/ './b').then(function(b) {
  console.log(b)
})

function getComponent() {
  // 使用異步的形式導入 lodash,default: _ 表示用 _ 代指 lodash
  return import('lodash').then(({ default: _ }) => {
    var element = document.createElement('div')
    element.innerHTML = _.join(['hello', 'world'], '-')
    return element
  })
}

getComponent().then(element => {
  document.body.appendChild(element)
})

目錄結構爲:
7.png
webpack配置:

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  entry: {
    main: './code_split_test/index.js'
  },
  output: {
    path: path.resolve(__dirname, 'build'), // 打包文件的輸出目錄
    filename: '[name].bundle.js', // 代碼打包後的文件名
    publicPath: __dirname + '/build/', // 引用的路徑或者 CDN 地址
    chunkFilename: '[name].js' // 代碼拆分後的文件名
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  // 拆分代碼配置項
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        lodash: {
          name: 'lodash',
          test: /[\\/]node_modules[\\/]/,
          priority: 10
        },
        common: {
          name: 'common',
          minSize: 0, //表示在壓縮前的最小模塊大小,默認值是 30kb,若是沒設置爲0,common模塊就不會抽離爲公共模塊,由於原始大小小於30kb
          minChunks: 2, // 最小公用次數
          priority: 5, // 優先級
          reuseExistingChunk: true // 公共模塊必開啓
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  },
  plugins: [
    new CleanWebpackPlugin(), // 會刪除上次構建的文件,而後從新構建
    new BundleAnalyzerPlugin()
  ]
}

打包結果:
8.png

咱們來分析一下文件之間的依賴關係:
10.png
index.js依賴於a.js、b.js、lodash.js,而且是動態加載,a.jsb.js都依賴common.js,是同步加載。

很明顯,若是咱們採用aysnc模式拆分,分割出的a.jsb.js裏面都會存在common.jscommon.js模塊不會被提取成公共模塊,得不到複用。
所以,咱們能夠採用allcommon.js模塊會被提取成共享模塊。
9.png

如今將打包後的主文件main.bundle.js引入到index.html中,奇怪的是主文件的引入並無使得分割後的文件自動引入
1.png
而後我去查看了一下主文件裏面是否含有引入分割文件的腳本代碼,發現是有的
2.png

3.png
這就很無語了,可是當我把ES6轉譯有關配置註釋掉再打包執行index.html文件,發現是可行的

// module: {
  //   rules: [
  //     {
  //       test: /\.js$/,
  //       exclude: /node_modules/,
  //       use: {
  //         loader: 'babel-loader'
  //       }
  //     }
  //   ]
  // },

4.png
這個問題究竟是爲何呢?直到如今我也尚未搞懂,但願哪位大佬注意到後能幫我解決一下疑惑。

參考文章:
https://itxiaohao.github.io/passages/webpack4-code-splitting/

相關文章
相關標籤/搜索