原文链接:vueschool.io/articles/vu…
本系列将深入研究我们在
Webpack 是如何打包的?
本系列的大部分重点都放在如何使 js bundle 更小上,首先我们需要了解 Webpack 是如何打包的。
Webpack 打包时会创建依赖关系图,该图基于 import 链接了所有的文件。假设在 Webpack 配置中指定了 main.js 作为入口点,它将成为依赖关系图的根,该文件中导入的每个 js 模块都将成为图中的节点,同时这些节点中导入的每个模块也都将成为其节点。Webpack 使用依赖关系图来检测输出的 bundle 中应该包含哪些文件,输出的 bundle 只是一个(或多个)JavaScript 文件,其中包含依赖关系图中的所有模块。这些 bundle 本质上是整个应用程序的 JavaScript。
下图可以说明整个过程:

很明显,项目越大,初始的 JavaScript bundle 就越大。bundle 越大,用户下载和解析所需的时间就越长,用户等待的时间越长,就越有可能离开网站。根据 Google 的说法,如果页面加载时间超过3秒以上,53%的移动端用户会离开。
综上所述,更大的 bundle = 更少的用户,这可以直接转化为潜在收入的损失。
延迟加载
所以如何减少 bundle 的大小呢?答案很简单:延迟加载和代码拆分。
顾名思义,延迟加载指的是延迟加载应用程序的部分 chunks,在真正需要时才去加载;代码拆分是将应用程序拆分为这些延迟加载块的过程。
大多数情况下,用户访问网站时,并不需要立即从 JavaScript bundle 中获取所有代码。例如,我们不需要花费宝贵的资源为初次访问网站的访客加载 "My Page" 区域,或者不是每个页面都需要的 modals, tooltips 等等。
每个页面加载时都下载、解析和执行整个 bundle 是一种浪费,延迟加载使我们可以拆分 bundle 且只提供所需的部分,这样用户就不会浪费时间来加载和解析根本用不到的代码。
要查看应用实际用了多少 JavaScript 代码,可以打开 DevTools -> cmd + shift + p -> 输入 coverage -> 点击 “record”。

标记为红色的是当前路由不需要的可以延迟加载的内容,如果使用了 source map,可以点击列表中的任一文件,查看哪些部分没有调用。可以看到,即使 vuejs.org 都有很大的改进空间。
下面我们来看下,如何在自己的 Vue App 中使用延迟加载。
动态导入
通过 Webpack 动态导入可以很轻松地实现延迟加载。
如果使用以下标准方式导入 JavaScript:
// cat.js
const Cat = {
meow: function () {
console.log("Meowwwww!")
}
}
export default Cat
// main.js
import Cat from './cat.js'
Cat.meow()Cat 模块将作为依赖关系图中 main.js 的一个节点。如果只在某些情况下,如对用户交互的响应,才引入 Cat 模块,该怎么办?将这个模块与初始 bundle 绑定在一起是个坏主意,因为并非始终都需要,需要告诉应用何时下载这个代码块,这就是动态导入的用武之地,看下面例子:
// main.js
const getCat = () => import('./cat.js')
// later in the code as a response to some user interaction like click or route change
getCat()
.then({ meow } => meow())这里用一个返回 import 函数的函数代替之前的直接导入,该函数返回一个 Promise,Webpack 会将动态导入模块的内容打包到一个单独的文件中,然后就可以根据需要下载这个模块。
动态导入基本上隔离了要添加到依赖关系图中的节点(本例中指 Cat 模块),下面的例子更好地阐明了这种机制。
假设有一个很小的网上商店,包含4个文件:
- main.js
- product.js
- productGallery.js
- category.js
// category.js
const category = {
init () { ... }
}
export default category
// product.js
import gallery from ('./productGallery.js')
const product = {
init () { ... }
}
export default product
// main.js
const getProduct = () => import('./product.js')
const getCategory = () => import('./category.js')
if (route === "/product") {
getProduct()
.then({init} => init()) // run scripts for product page
}
if (route === "/category") {
getCategory()
.then({init} => init()) // run scripts for category page
}根据前面的知识, product 和 category 将会作为单独的 bundle,productGallery 由于没有使用动态导入,将与 product 打包到同个 bundle 中。换句话说,动态导入为依赖关系图创建了新的入口点。

延迟加载 Vue 组件
接下来,看看如何在 Vue 应用程序中使用延迟加载。
const lazyComponent = () => import('Component.vue')非常简单。以下是延迟加载 Vue 组件的常用方式:
- 调用 import 函数
const lazyComponent = () => import('Component.vue')
lazyComponent()- 按需渲染组件
<template>
<div>
<lazy-component v-if='someCondition' />
</div>
</template>
<script>
const lazyComponent = () => import('Component.vue')
export default {
components: { lazyComponent },
data() {
return {
someCondition: false
}
}
}
// Another syntax
export default {
components: {
lazyComponent: () => import('Component.vue')
}
}
</script>只有当 someCondition 变为 true 时,lazy-component 才会被加载。
总结
延迟加载是使 Web 应用性能更好、bundle 更小的最佳方法之一,本文介绍了如何延迟加载 Vue 组件,下节将学习如何使用异步路由拆分 Vue 代码及相关的最佳实践。