demo5源码地址
package.json文件所用依赖,npm install 安装:

  1. {
  2. "script": {
  3. "dev": "webpack --mode development",
  4. "build": "webpack --mode production"
  5. },
  6. "devDependencies": {
  7. "clean-webpack-plugin": "^2.0.0",
  8. "webpack": "^4.29.6",
  9. "webpack-cli": "^3.2.3"
  10. },
  11. "dependencies": {
  12. "lodash": "^4.17.11"
  13. }
  14. }

我们在src/文件夹下创建index.js文件

  1. import _ from 'lodash'
  2. console.log(_.join(['a','b','c']))

目录结构为:
image.png
配置webpack.config.js文件

  1. let path = require('path');
  2. const CleanWebpackPlugin = require('clean-webpack-plugin');
  3. module.exports = {
  4. entry: {
  5. main:'./src/index.js', // 入口文件
  6. },
  7. output: {
  8. publicPath: __dirname + '/dist/', // js 引用的路径或者 CDN 地址
  9. path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
  10. filename: '[name].bundle.js', // 代码打包后的文件名
  11. chunkFilename: '[name].js' // 代码拆分后的文件名
  12. },
  13. plugins: [
  14. new CleanWebpackPlugin() // 默认情况下,此插件将删除 webpack output.path中所有的文件,以及每次重建后所有未使用的webpack的产品。
  15. ]
  16. }

运行 npm run build 打包
image.png
在index.html中使用打包后的文件

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <script src="./dist/main.bundle.js"></script>
  11. </body>
  12. </html>

使用浏览器打开 index.html 文件,进入控制台,可以看到如下信息:a,b,c

如果我们再改动业务代码,将 index.js 中的代码改为

import _ from 'lodash'

console.log(_.join(['a', 'b', 'c'], '***'))

再打包,刷新页面可以看到 a***b*****c**
image.png

我们引用的第三方框架和我们的业务代码一起被打包,这样会存在着什么问题
假设lodash为11M,业务代码也为11M,打包后就是2M

浏览器每次打开页面,都要先加载2M的文件,才能显示业务逻辑,这样会使得加载时间变长。

业务代码更新会比较频繁,第三方代码基本不会改变,这样重新打包后,假设为2M用户重新打开网页后,又会加载2M的文件

浏览器是有缓存的,如果文件没有变动的话,就不用去再发送http请求,直接从缓存中取,这样在刷新页面或者是第二次进入的时候可以加快网页加载的速度。

怎么解决呢?可以利用webpack中代码分割

在webpack4.0之前是使用commonsChunkPlugin来拆分公共代码,4.0之后废弃,并用 splitChunksPlugins

在使用 splitChunksPlugins 之前,首先要知道 splitChunksPlugins 是 webpack 主模块中的一个细分模块,
无需 npm 引入**

现在我们来配置webpack.config.js文件

let path = require('path');

const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  entry: {
    main:'./src/index.js',  // 入口文件
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路径或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
    filename: '[name].bundle.js', // 代码打包后的文件名
    chunkFilename: '[name].js' // 代码拆分后的文件名
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  plugins: [
    new CleanWebpackPlugin() // 默认情况下,此插件将删除 webpack output.path中所有的文件,以及每次重建后所有未使用的webpack的产品。
  ]
}

上面高亮的代码段就是告诉webpack,要做代码分割了,这里的chunk:‘all’是分割所有的代码,包括同步代码和异步代码,webpack默认是chunks: ‘async’分割异步代码

我们使用npm run dev来打包开发环境下的代码,这样代码就不会压缩,方便我们来观察,可以看到代码被分割成两个文件了
image.png
打开dist/main.bundle.js文件,在最底部可以看到src/index.js,里面放的是业务逻辑代码,但是并没有lodash的代码
image.png

打开dist/vendors~main.js文件,在最上面可以看到lodash模块
image.png

再次打开页面,控制台也输出了内容,这样就实现了Code Splitting(代码分割)

其实没有webpack的时候,也是有代码分割的,不过是需要我们自己手动的分割,而现在使用了webpack,通过这种配置项的方式,它会自动帮我们去做代码分割

仔细看分割完的代码名称,vendors~main.js,我们对分割完的名称进行更改

还是在splitChunks的配置项中,添加cacheGroups对象

optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          name: 'vendors'
        }
      }
    }
  }

再次打包就可以看到效果了,cacheGroups 的默认配置会定义venders和default
image.png

test: /[\/]node_modules[\/]/,使用正则过滤,只有node_modules引入第三方库才会被分割
image.png
为了验证默认配置,我们将splitChunks属性设置为空对象,再次打包
image.png

打包完发现只有一个文件,这是为什么?
image.png
因为chunks默认为async,只会分割异步的代码,而我们之前写的都是同步的代码,先import lodash,再去写业务逻辑,现在使用异步的方式来做,将index.js中的代码改为如下:

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)
})

image.png

这里分割出了0.js和main.bundle.js,0是以id为编号来命名

所以一般我们设置chunk是为all,异步,同步代码都在打包
现在我们将webpack官网上的默认配置拷贝到我们的webpack.config.js中来分析一下

optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }

webpack 代码分割的配置是这样的,比如我们要分割 jQuery 和 lodash 这样的第三方库,它会先经过 chunks、minSize、maxSize、minChunks 等等,满足条件后生成 jQuery 和 lodash 两个文件,然后放入 cacheGroup 中缓存着,再根据你在 cacheGroup 中配置的来决定是将两个文件整合到一个文件打包,还是单独分开打包,比如上面代码中的 vendors,就是将 node_modules 中所有的第三方库都打包到 vendors.js 文件中,如果你还想继续分割可以这么做

cacheGroups: {
  lodash: {
    name: 'lodash',
    test: /[\\/]node_modules[\\/]lodash[\\/]/,
    priority: 5  // 优先级要大于 vendors 不然会被打包进 vendors
  },
  vendors: {
    test: /[\\/]node_modules[\\/]/,
    priority: -10
  },
  default: {
    minChunks: 2,
    priority: -20,
    reuseExistingChunk: true
  }
}

再次打包,就可以看到lodash被分割出来了,以后使用第三方库都可以用这种配置来单独分割成一个 js 文件,比如 element-ui注意设置 priority 的值很重要,优先级越高的会越先被打包
image.png
如果 index.js 引入了 A.js 和 B.js,同时 A、B 又引入了 common,common 被引入了两次,可以被称为公共模块
目录结构为:
image.png
代码如下:

// 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 './a.js'
import './b.js'

// 异步代码
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)
})

上面那种异步的写法可能比较绕,现在精简一下,并且 webpack 对异步代码通过注释可以直接修改打包后的名称,以下代码全部以异步的形式引入

// 异步代码
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {
  console.log(a)
})

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

import(/* webpackChunkName: 'use-lodash'*/ 'lodash').then(function(_) {
  console.log(_.join(['1', '2']))
})

将 minChunks 设置为 2,最小公用 2 次才分割

optimization: {
  splitChunks: {
    chunks: 'all',
    minSize: 30000,
    maxSize: 0,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
      lodash: {
        name: 'lodash',
        test: /[\\/]node_modules[\\/]lodash[\\/]/,
        priority: 10
      },
      commons: {
        name: 'commons',
        minSize: 0, //表示在压缩前的最小模块大小,默认值是 30kb
        minChunks: 2, // 最小公用次数
        priority: 5, // 优先级
        reuseExistingChunk: true // 公共模块必开启
      },
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        priority: -10
      },
      default: {
        minChunks: 2,
        priority: -20,
        reuseExistingChunk: true
      }
    }
  }
}

image.png
这里分割出了lodash和我们在注释中定义的use-lodash,前者是第三方库,后者是用第三库写的业务代码,也能被分割出来。
image.png

image.png
这里之所以会自动引入分割后的依赖,可以查看打包后的 main.bundle.js 文件
image.png

常用的配置项在下面的表格中,更多配置详情见官网
image.png

参考文章

webpack4 系列教程 (三): 多页面解决方案 — 提取公共代码
webpack 官网