1. 代码分离的方法
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间
常用的代码分离方法有三种:
- 入口起点:使用
entry配置手动地分离代码。缺点:如果项目有多个入口,那么这些多个入口共享的文件会分别在每个包里面重复打包,所以需要第二种防止重复的分离方法 - 防止重复:使用
Entry dependencies或者SplitChunksPlugin去重和分离 chunk - 动态导入:通过模块的内联函数调用来分离代码
2. 入口起点(静态导入)
这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们之后将会解决这些问题。先来看看如何从 main bundle 中分离 another module(另一个模块):
在 src/js 文件中新建 anthor-module.js
// 使用 lodash 之前需安装依赖 npm i lodashimport _ from 'lodash'console.log(_.join(['Another', 'module', 'loaded!'], ' '))
配置entry使之有多个入口
const path = require('path')module.exports = {entry: {index: './src/js/index.js',anthor: './src/js/antoer-module.js'},output: {filename: 'js/[name].bundle.js',path: path.resolve(__dirname, './dist'),clean: true}}
执行npx webpack并观察控制台:
我们发现lodash.js被打包到了 anthor.bundle.js 中,而且打包后的 dist/js 中有两个出口文件(因为有两个入口文件),dist/index.html 中也引入了两个 js 文件

接着我们在 index.js 文件中也引入并使用lodash
// 导入 js 文件import _ from 'lodash'import hello from './hello.js'// 加载 js 格式文件hello()console.log(_.join(['index', 'module', 'loaded!'], ' '))
附:hello.js
function hello() {return new Promise((resolve, reject) => {setTimeout(() => {resolve('Hello Webpack5 !!!')}, 1000)})}async function getString() {const str = await hello()console.log(str)}export default getString
执行npx webpack并观察控制台:
所以产生了一些问题:
- 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中,造成重复引用
- 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来
3. 防止重复(静态导入)
现在我们需要将多个 bundle.js 文件中引入的重复模块给单独抽离出来,有两种方法可以实现在多个 chunk 之间共享模块:
① 配置 dependOn option
entry: {index: {import: './src/js/index.js',dependOn: 'shared'},anthor: {import: './src/js/another-module.js',dependOn: 'shared'},shared: 'lodash'},
执行npx webpack并观察控制台:
以及打包后 dist 中的情况(js文件夹下和 index.html):
② SplitChunksPlugin
SplitChunksPlugin插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk
entry: {index: './src/js/index.js',anthor: './src/js/another-module.js'},// 配置 optimization.splitChunksoptimization: {splitChunks: {chunks: 'all'}}
执行npx webpack并观察控制台:
发现lodash已经被抽离出来了,接着观察打包后的 dist 文件夹:
4. 动态导入
当涉及到动态代码拆分时,webpack 提供了两个类似的技术:
- 比较推荐选择的方式,是使用符合 ECMAScript 提案 的
import()语法来实现动态导入 - 是 webpack 的遗留功能,使用 webpack 特定的
require.ensure
看第一种方式:
在 js 文件夹下创建 async-module.js ,并在 index.js 文件中引入
function getComponent() {return import ('lodash').then(({ default: _ }) => {const divEle = document.createElement('div')divEle.innerHTML = _.join(['Hello', 'webpack5'], ' ')return divEle})}getComponent().then( divEle => {document.body.appendChild(divEle)})
import '../js/async-module.js'
module.exports = {entry: './src/js/index.js',optimization: {}}
执行npx webpack并观察控制台:
发现lodash已经被抽离出来了,接着观察打包后的 dist 文件夹:

注:用import()语法实现动态导入并不会在打包后的 index.html 中引入
静态导入很好用啊,为什么要用动态导入呢?动态导入的应用场景在哪?应用动态导入又有什么好处呢?
5. 懒加载
动态导入的应用场景之一
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后(如:点击按钮),立即引用或即将引用另外一些新的代码块(不操作就不会引入)。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载
在 js 文件夹下创建一个math.js文件,在主页面中通过点击按钮调用其中的函数:
export const add = (a, b) => {return a + b}export const minus = (a, b) => {return a - b}
index.js 中实现懒加载
// 懒加载 js 模块const div1 = document.createElement('div')const btn1 = document.createElement('button')btn1.innerHTML = '点击执行加法运算'div1.appendChild(btn1)document.body.appendChild(div1)btn1.addEventListener('click', () => {import ('../js/math.js').then(({add}) => {console.log(add(1, 2))})})
执行npx webpack后观察 dist:
发现 math.js 已经被单独的抽离出来了,导出的文件名不好看,所以我们可以自定义导出文件的文件名,只需要在懒加载时追加一句魔法注释/* webpackChunkName: '自定义文件名' */
// 修改一下代码btn1.addEventListener('click', () => {import(/* webpackChunkName: 'math' */ '../js/math.js').then(({ add }) => {console.log(add(1, 2))})})

再进入浏览器中观察模块的加载情况:
发现初始化时并没有加载模块,当执行操作时才加载模块,实现了资源的按需加载
6. 预获取/预加载模块
动态导入的应用场景之一
Webpack v4.6.0+ 增加了对预获取和预加载的支持
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 “resource hint(资源提示)”,来告知浏览器:
prefetch(预获取):将来某些导航下可能需要的资源 preload(预加载):当前导航下可能需要资源
沿用第五点懒加载中的代码,仅需追加一下懒加载时的魔法注释(webpackPrefetch: true或webpackPreload: true),就可以实现预获取/预加载
// 修改一下代码btn1.addEventListener('click', () => {import(/* webpackChunkName: 'math', webpackPrefetch: true */ '../js/math.js').then(({ add }) => {console.log(add(1, 2))})})
执行npx webpack后 dist 没有发生改变,再进入浏览器查看资源加载的情况:
- 预获取

发现页面加载的时候还没有执行点击按钮操作 math.bundle.js 已经加载了,当我们进行操作时,它又加载了一次。这么做有什么意义呢?乍一看还不如懒加载,其实它的工作原理是这样的:它在浏览器的中引入了一个,这是告诉浏览器当页面资源加载完了之后,在网络空闲的时候将<link rel="prefetch" href=" ... ">标签中路径下的文件加载下来,在进行操作的时候可以快速使用,是一种比懒加载还要优秀的模式
- 预加载
效果与懒加载类似:加载页面的时候不加载,只有当操作的时候再加载资源,也不会在浏览器的标签上添加标签
与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。所以当页面需要请求的资源过多或者网路速度过慢,preload chunk 加载方式会分流导致页面加载速度慢,用户体验差
- preload chunk 具有中等优先级,并立即下载;prefetch chunk 在浏览器闲置时下载
- preload chunk 会在父 chunk 中立即请求,用于当下时刻;prefetch chunk 会用于未来的某个时刻
- 浏览器支持程度不同
