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

前言

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

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

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

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

  • By page(按照页面切分)

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

  • By condition(按条件加载)

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

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

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

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

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

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:

<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:

<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:

<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:

<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许可协议。转载请注明出处!