『翻译』基于 Vue.js 与 Webpack 的三种代码分割范式

原文链接:3 Code Splitting Patterns For VueJS and Webpack

前言

代码分割是提升单页应用初始加载速度的重要方式之一。因为用户不用在第一次进入应用时下载所有代码,用户能更快的看到页面并与之交互。这会改善用户体验,尤其在移动端;而且这对 SEO 有很大帮助,因为 Google 会降低加载速度慢的网站权重。

上周我写了一篇关于Vue.js 与 Webpack 如何分割代码的文章,长话短说,每个组件都封装在单个文件中,那很容易分割代码,当你导入模块时,Webpack 可以创建一个分割点,并且 Vue 也可以很方便的加载一个异步组件。

我认为代码分割最困难的部分不是如何让它工作起来,而是何时何地让它工作。我想说,当设计你的应用时,就要将代码分割作为架构考虑进去。

在这篇文章中,我将介绍目前 Vue.js 的三种代码分割方式:

  • By page(按照页面切分)

  • By page fold(按照页面的可见区域折叠切分)

  • By condition(按条件加载)

注:这篇文章最初于2017/07/08发表在Vue.js开发博客上。

1.By page(按照页面切分)

按照页面切分是思路最清晰的。这个简单的应用有三个页面:

我们假设每个组件都是一个单独的文件,比如:Home.vue, About.vueContact.vue,然后我们可以使用 Webpack 的动态 import(dynamic import) 功能拆分成单独的构建文件。当用户访问不同页面时,Wenpack 会异步加载并请求改页文件。

如果你使用 vue-router,这很容易实现,因为你的页面已经在单独的组件里了。

1
2
3
4
5
6
7
8
const Home = () => import(/* webpackChunkName: "home" */ './Home.vue');
const About = () => import(/* webpackChunkName: "about" */ './About.vue');
const Contact = () => import(/* webpackChunkName: "contact" */ './Contact.vue');
const routes = [
{ path: '/', name: 'home', component: Home },
{ path: '/about', name: 'about', component: About },
{ path: '/contact', name: 'contact', component: Contact }
];

看看我们编译代码时的统计数据,每个页面都在它们自己的文件里,但要注意到有个重要的bundle文件叫 build_main.js,它包含了所有的公共代码以及异步加载其他文件的逻辑。无论用户访问哪个路由,都必须先加载它。

现在我访问 http://localhost:8080/#/contact 加载 Contact 页面,我查看 Network 菜单,发现下列文件被加载:

注意 build_main.js 这一栏的 initiator 值为 (index)。这意味着 index.html 请求了这个脚本,这正是我们所期盼的。但是 build_1.jsinitiator 却是 bootstrap_a877…,这是 Webpack 脚本负责的异步加载文件。当你使用 Webpack 的动态导入功能,这个脚本会自动加入构建。最重要的一点是: build_1.js 不会阻塞初始页面的加载。

2.By page fold(按照页面的可见区域折叠切分)

折叠以下(Below the “fold”)代表页面初始时不可见的部分。你可以异步加载这些内容,因为用户通常需要一两秒钟才能阅读完折叠以上的内容,尤其是在第一次访问站点时。

在这个实例应用中,我考虑把折叠线设在刊头下。那么让我们在页面初始化时加载导航栏和刊头,它们之下的所有内容,稍后再加载。我会创建一个名叫 BelowFold 的组件,提取出相关的代码如下:

Home.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div>
<div class="jumbotron">
<h1>Jumbotron heading</h1>
...
</div>
<below-fold></below-fold>
<!--All the code below here has been put into-->
<!--into the above component-->
<!--<div class="row marketing">
<div class="col-lg-6">
<h4>Subheading</h4>
<p>Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.</p>
...
</div>
...
</div>-->
</div>
</template>
<script>
const BelowFold = () => import(
/* webpackChunkName: "below-fold" */ './BelowFold.vue'
);
export default {
...
components: {
BelowFold
}
}
</script>

BelowFold.vue:

1
2
3
4
5
6
7
8
9
10
<template>
<div class="row marketing">
<div class="col-lg-6">
<h4>Subheading</h4>
<p>Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.</p>
...
</div>
...
</div>
</template>

当我们编译代码时,可以看到 below-fold 被打包成了单独的文件:

提示:below-fold 小到只有1.36k,看起来似乎不值得把它单独分离出来。因为现在只是一个很小的演示应用。在真实的应用中,页面的大部分内容都在折叠以下,因此可能有大量的代码,它包括 JSCSS 以及所有子组件。

3.By condition(按条件加载)

另一个选择方案是按条件加载。比如:模态框、Tab页、菜单等。

这个应用有个模态框,当你按下”Sign up today”按钮时会弹出它:

和之前一样,我们只是将模态框代码移动到它自己的单个文件组件中:

Home.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div>
<div class="jumbotron">...</div>
<below-fold></below-fold>
<home-modal v-if="show" :show="show"></home-modal>
</div>
</template>
<script>
const BelowFold = () => import(
/* webpackChunkName: "below-fold" */ './BelowFold.vue'
);
const HomeModal = () => import(
/* webpackChunkName: "modal" */ './HomeModal.vue'
);
export default {
data() {
return {
show: false
}
},
components: {
HomeModal,
BelowFold
}
}
</script>

HomeModal.vue:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<modal v-model="show" effect="fade">...</modal>
</template>
<script>
import Modal from 'vue-strap/src/Modal.vue';
export default {
props: ['show'],
components: {
Modal
}
}
</script>

注意我在模态框上加了 v-if。布尔值 show 用来开启/关闭模态框,并且它也用来判断是否渲染模态框本身。因为初始化页面时 showfalse,只有当模态框打开时,才会下载代码。

这很合适,因为如果用户没有打开模态框,那这块代码是不会下载的。唯一的缺点是:它有很小的用户体验成本,当用户按下按钮后必须等待文件下载完成。

再次编译,下面是现在的输出结果:

啊哈,我们又节省了5KB的首屏流量…

结论

除了以上三种代码分割的方法,我相信一定还有其他方法去实现,只要你运用自己的想象力!

本文译者:余震(Freak)
译文出处:Rockjins Blog
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN许可协议。转载请注明出处!

坚持,您的支持将鼓励我继续爬下去!