目录
Webpack 捆绑的工作原理
首先需要了解 Webpack 如何捆绑文件。在捆绑文件时,Webpack 会创建一个叫作依赖图的东西。它是一种图,链接所有导入的文件。假设 Webpack 配置中有一个叫作 main.js 的文件被指定为入口点,那么它就是依赖图的根。这个文件要导入的每个 JS 模块都将成为图的叶子,而这些叶子中导入的每个模块都将成为叶子的叶子。
Webpack 使用这个依赖图来决定应该在输出包中包含哪些文件。输出包是一个 JavaScript 文件,包含了依赖图中指定的所有模块。
这个过程就像这样:
在知道了捆绑的工作原理之后,我们就可以得出一个结论,即随着项目的增长,初始 JavaScript 捆绑包也会随着增大,下载和解析捆绑包所需的时间也会越长,用户等待的时间也会变长,他们离开网站的可能性也就越大。
简单地说,更大的捆绑包 = 更少的用户,至少在大多数情况下是这样的。
延迟加载
那么,在添加新功能和改进应用程序的同时,我们如何减小捆绑包的大小?答案很简单——延迟加载和代码拆分。
顾名思义,延迟加载就是延迟加载应用程序的部分内容。换句话说——只在真正需要它们时加载它们。代码拆分是指将应用程序拆分成可以延迟加载的块。
在大多数情况下,你不需要在用户访问网站后立即使用 JavaScript 包中的所有代码。假设应用程序中有三个不同的路由,无论用户最终要访问哪个更难,总是要下载、解析和执行所有这些路由,即使他们只需要其中的一个路由。多么浪费时间和精力!
延迟加载允许我们拆分捆绑包,并只提供必要的部分,这样用户就不会浪费时间下载和解析无用的代码。
要想知道网站实际使用了多少 JavaScript 代码,我们可以转到 devtools -> cmd + shift + p -> type coverage -> 单击“record”,然后应该能够看到实际使用了多少下载的代码。
标记为红色的都是当前路由不需要的东西,可以延迟加载。如果你使用了源映射,可以单击列表中的任意一个文件,看看是哪些部分没有被调用到。可以看到,即使是 vuejs.org 也还有很大的改进空间。
通过延迟加载适当的组件和库,我们将 Storefront 的捆绑包大小减少了 60%!
接下来,让我们来看看如何在 Vue 应用程序中使用延迟加载。
动态导入
我们可以使用 Webpack 动态导入(https://webpack.js.org/guides/code-splitting/)来加载应用程序的某些部分。让我们看看它们的工作原理以及它们与常规导入的区别。
标准的 JS 模块导入:
// main.js
import ModuleA from './module_a.js'
ModuleA.doStuff()
它将作为 main.js 的叶子被添加到依赖图中,并被捆绑到捆绑包中。
但是,如果我们仅在某些情况下需要 ModuleA 呢?将这个模块与初始捆绑包捆绑在一起不是一个好主意,因为可能根本就不需要它。我们需要一种方法来告诉应用程序应该在什么时候下载这段代码。
这个时候可以使用动态导入!来看一下这个例子:
//main.js
const getModuleA = () => import('./module_a.js')
// invoked as a response to some user interaction
getModuleA()
.then({ doStuff } => doStuff())
我们来看看这里都发生了什么:
我们创建了一个返回 import() 函数的函数,而不是直接导入 module_a.js。现在 Webpack 会将动态导入模块的内容捆绑到一个单独的文件中,除非调用了这个函数,否则 import() 也不会被调用,也就不会下载这个文件。在后面的代码中,我们下载了这个可选的代码块,作为对某些用户交互的响应。
通过使用动态导入,我们基本上隔离了将被添加到依赖图中的叶子(在这里是 module_a),并在需要时下载它(这意味着我们也切断了在 module_a.js 中导入的模块)。
让我们看另一个可以更好地说明这种机制的例子。
假设我们有 4 个文件:main.js、module_a.js、module_b.js 和 module_c.js。要了解动态导入的原理,我们只需要 main 和 module_a 的源代码:
//main.js
import ModuleB from './mobile_b.js'
const getModuleA = () => import('./module_a.js')
getModuleA()
.then({ doStuff } =&