前言

生产环境中,将项目依赖的一些第三方包替换成通过 cdn 方式外部加载,而不是打包到 vender,对于提升应用的加载、响应速度很有意义,下面我们一步步的实现这点

CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率

1. 查看打包后的资源情况

首先使用 build 命令打包,看看在使用 cdn 加载外部资源之前,应用的速度及包的体积大小情况

推荐使用 vue ui 命令启动一个 GUI 的窗口,通过此窗口进行打包,可以看到更多信息

vue ui

此命令必须在 vue 项目所在的目录下运行(最好通过管理员身份打开 powshell),如我的 vue 项目在 d:\code\vue 目录下,就将此目录作为 powshell 的工作目录,然后再运行 vue ui 命令,否则就会出现目录无法选择,或者一致加载中等问题

打开工具后,执行 build 命令对我当前的一个项目进行生产环境打包(你针对自己的项目打包即可)

image.png

image.png

我们都知道

  • 开发者编写的组件等代码会打包到 app文件中(名字会随机,比如上面是 app5e6d08ab.js)
  • 通过 import 引入的第三方依赖包,会打包到 vender 文件中

同时还会生成相关的css 和字体文件

上图左侧列出了各个文件的大小,右侧列出了第三方依赖包占用的体积

通过这个图可以查看生成文件的体积,并找到哪个依赖包占用体积较大,以及哪些文件占用体积较大,从而进行优化

从右侧可以看出,如果将部分依赖包不要打包到 vener 文件中,而是使用 cdn 节点的方式,应该可以减少文件的体积,从而提升页面响应速度

这也是使用 CDN 节点的原因

2.打包入口文件

原来的打包入口文件为 main.js

无论是开发环境的打包,还是生产环境的打包,都通过此文件

现在,我们新建两个文件

  • main-prod:生产环境打包,通过 cdn 的方式引入第三方依赖包
  • main-dev:开发环境打包,将第三方依赖包打包到 vender 文件中
  • main.js 可以删除

两个文件的代码暂时一样,都是 main.js 的复制

开发环境不重视响应速度吗?

开发环境(development)生产环境(production) 的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间

说白了就是开发环境下打包到 vender 中是有利于开发的(而且这些包都在开发者本地,加载速度可以忽略),生产环境下使用 cdn 等方式可以减少 bundle 体积

3.修改webpack配置方式

现在设置不同的打包命令走不同的入口文件,这需要修改 webpack 的打包设置

默认情况下 vue/cli 隐藏了 webpack 配置选项,所以在目录中,找不到熟悉的 webpack.config.js 文件,目的是让开发人员从繁杂的配置中解脱出来,集中精力到业务逻辑和功能开发商

直接在 vue.config.js 中修改即可,其会覆盖脚手架项目中默认的打包设置

配置参考 | Vue CLI (vuejs.org)

上面链接的文档说明了所有的可选的配置项

从文档中得知,在 vue.config.js 中修改 webpack 配置的方式可以选择

  • chainWebpack:通过链式编程方式修改
  • configureWebpack:通过操作对象、合并对象的方式修改

两种方式都是用来修改配置的,只不过实现方式不一样,如果是简单的配置,推荐使用 chainWebpack 方式,如果复杂的设置,推荐使用 configureWebpack

image.png

4.使用 chainWebpack 修改配置

下面代码根据不同的环境调用不同的入口文件

module.exports = defineConfig({
  transpileDependencies: true, // 生产环境打包不输出 map 文件
  // 通过chainWebpack 配置开发环境和生产环境的打包设置
  chainWebpack: config => {
    // 生产环境配置
    config.when(process.env.NODE_ENV === 'production', config => {
      {
        config.entry('app').clear().add('./src/main-prod.js')
      }
    })
    // 开发环境设置
    config.when(process.env.NODE_ENV === 'development', config => {
      {
        config.entry('app').clear().add('./src/main-dev.js')
      }
    })
  }

})

配置完成后,分别运行 npm run devnpm run build 命令测试配置是否成功,只要都可以运行,就证明配置没有问题

问题:为什么运行不同的命令,就会使用不同的打包入口文件,换句话说:为什么运行不同的命令,process.env.NODE_ENV 的值就会不同?

模式和环境变量 | Vue CLI (vuejs.org)

5,配置 externals

现在开始配置生产环境下使用 CDN,大概有这么几个步骤

  • 通过 externals 选项配置打包时例外,这些包不会打包到 vender 中,而是从外部加载
  • 在 index.html 中通过 script 加载这些包
  • 如果某些依赖还需要引入 css,也可以通过 link 引入 css 文件,从而删除那些 `import ‘xx.css’ 代码,从而进一步减少包的体积

通过 config.set 方法设置 webpack 的 externals 选项,

外部扩展(Externals) | webpack 中文文档 (docschina.org)

image.png

vue.config.js 中配置

  config.set('externals', {
      vue: 'Vue',
      'vue-router': 'VueRouter',
      axios: 'axios',
      lodash: '_',
      moment: 'moment',
      nprogress: 'NProgress',
      'element-ui': 'ELEMENT'
  })

externalsd的值是一个对象,当程序打包时,回到 vue.config.js 中搜寻 externals 中的所有配置项,对象中的依赖都不会打包到 vender 中,而是通过外部资源加载

语法如下

  • 键:我们要引入的包的名称,如 import XXX from 'pageckage' 中的package。
  • 值:包在 windows 全局变量中的名称。如果你有过 vue 组件的开发经验,应该知道,组件如果想除了支持 npm 方式引入外,还支持使用 script 标签方式引入,就必须将包名称注册到 window 全局变量中,这样开发者才可以使用(全局变量的名字不是随意起的)
  • 某些依赖并没有配置进来,主要是有些包比较小众,不容易在网上找到现成的 cdn 资源,可以考虑将其放在公司自己的 cdn 服务器上或者更换

每个依赖包全局变量的名字到底是什么?一种方式是去其官网查看,如 vue

image.png

6.引用外部资源

上面说过,externals 中的依赖会使用外部资源,也就是通过 script 方式引入

所以,我们就在 index.html 中引入这些链接,这些链接对应的资源加载后,就会创建 externals 中的值,并将其加载到 window 全局变量中

<link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.6/theme-chalk/index.min.css">
<link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css">
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
<script src="https://cdn.staticfile.org/vue-router/3.5.1/vue-router.min.js"></script>
<script src="https://cdn.staticfile.org/axios/0.26.1/axios.min.js"></script>
<script src="https://cdn.staticfile.org/lodash.js/4.17.21/lodash.min.js"></script>
<script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
<script src="https://cdn.staticfile.org/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdn.staticfile.org/element-ui/2.15.6/index.min.js"></script>

注意:

  • 上面使用的七牛云的cdn节点,尽量使用与 package.json 中记录的版本相同的资源
  • 七牛云上寻找对应版本的资源时,其并没有严格按照版本的数字关系排序(可能是根据版本发布顺序),请耐心寻找
  • 某些依赖包还需要 css,以前我们是通过 import 导入,现在也是用 cdn 节点方式引入(注意要删除原来 import 引入的代码),如进度条组件的css

image.png

附上完整代码

image.png

7.element-ui 的特殊性

element-ui 与 moment、axios 这些简单的包不太一样

我们原来采用的是按需引入,但是通过 下面链接引入的是elementui的完整库

<script src="https://cdn.staticfile.org/element-ui/2.15.6/index.min.js"></script>

其会暴漏一个全局变量 ELEMENT,所以,我们除了在 externals 中配置之外

还要再入口文件中,取消原来按需引入的代码,转而使用完整引入的语法

image.png

如果打包没有问题,下面操作忽略即可

另外,还要删除 babel.config.js 中按需引入需要的插件配置,否则其仍会将 element-ui 打包(同时仍然会下载外部资源)

image.png

8.重新打包对比

重新打包,发现很多文件的体积都缩小了

响应速度分析

image.png

资源占用情况分析
image.png

运行打包之后的代码,发现如下资源都是从外部下载的,已经不再 vender 文件中了

image.png

9. 两种环境下的 index.html

现在生产环境的打包已经没问题了

但此时开发模式下打包后,再运行程序,提示不能重复定义 $router 属性

image.png

原因在于:*通常是因为在项目中安装了vue-router的依赖并且用Vue.use()使用了vue-router,还在index.html页面引入了vue-router.js文件

将 index.html 中引入 vue-router 的代码移除即可

但是生产环境的打包又需要 vue-router 等外部资源

所以,最终解决方案,就是像办法实现如下效果

  • 生产环境下,index.html 保留这些资源引用代码
  • 开发环境下,index.html 不加载这些资源引用代码

很显然,需要在 index.html 中加入一个判断条件,而判断条件依赖的变量,可以在 vue.config.js 中定义

image.png

依赖于一个 html 插件,在 webpack 打包过程中,向 args 数组中添加元素,然后在 index.html 中读取整个元素的值,并进行判断

    <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.6/theme-chalk/index.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css">
    <script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
    <script src="https://cdn.staticfile.org/vue-router/3.5.1/vue-router.min.js"></script>
    <script src="https://cdn.staticfile.org/axios/0.26.1/axios.min.js"></script>
    <script src="https://cdn.staticfile.org/lodash.js/4.17.21/lodash.min.js"></script>
    <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
    <script src="https://cdn.staticfile.org/moment.js/2.29.1/moment.min.js"></script>
    <script src="https://cdn.staticfile.org/element-ui/2.15.6/index.min.js"></script>
    <% } %>

开发模式下打包,发现 index.html 中没有那些 script 和 link 标签了,程序也能正常运行了

image.png
生产环境下打包,发现 index.html 中那些 script 和 link 标签又回来了

image.png

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐