开发专栏
实战减小 Vue 项目的打包体积
1 年前
2507
Nuxt.js Vue JavaScript 前端开发 SEO Ant Design Vue

通过减小webpack打包的文件大小,按需加载antd vue的组件和icons,替换体积较大的类库,减少HTML嵌入的CSS内容等方案,优化首屏加载速度。

本文内容使用 NuxtJS 2.14.1 (其中 vue 为 2.6.11, webpack 为 4.44.1) 和 Ant Design of Vue 1.6.4。因为版本和不同类库造成的差异请大家多进行搜索。

问题

用 NuxtJS 开发博客以来,一直有一些体验上的问题,这次正好有时间,便来优化一下,目前的问题主要有:

  • 依赖的 js css 文件较大
  • 服务器端渲染生成的 HTML 较大

导致访问速度较慢,特别是首次加载。

依赖的 js css 文件较大

首屏加载过慢有很多原因,最常见的就是网页依赖的资源(例如 vue 和各种类库的 js)太多或者太大,浏览器需要花一些时间才能将它们下载下来。

通过“开发人员工具”查看加载时间

打开浏览器的 开发人员工具 - 网络 ,然后访问网站,来查看网页需要加载哪些东西,以及它们的大小和耗时。

查看时需要勾选 禁用缓存 来模拟首次到访网站的浏览者的真实状态。
如果界面和我的略有不同,可以点击右上角齿轮图标按钮,勾选 使用大请求行

JS过大,加载缓慢

通过 Size 列可以看到,60a2df....js 这个 js 文件大小为 594 k(相当于半兆),由于采用了 gzip 压缩,它的原始大小为 2.4MB,是一个相当大的文件了,而且花了5秒才下载完成,体验非常不好。

分析文件内容

由于这个文件在 /_nuxt 下,且名字为随机生成,这应该是 webpack 打包生成的文件。NuxtJS 内置了 webpack-bundle-analyzer分析打包后的文件。

打开 nuxt.config.js 并找到 build 属性,添加 analyze: true 以启用分析模式:

module.exports = {
  build: {
    analyze: true
  }
}

然后在控制台中输入 npx nuxt build --analyze 等待分析完成。

vendors.app.js 很大

可以看到 vendors.app.js 很大,而它主要又由 ant-design-vue@ant-design/icons/libmoment 等内容组成。如果能把这些大个的文件减少,那生成的 vendors.app.js 文件体积也会减小。

将 antd vue 改为按需加载

从分析结果中可以看到 ant-design-vue 下面有很多的组件,虽然我并没有使用到,但也包含在了里面,应该是因为在引入 antd vue 时直接全量引入导致的。通过查找 antd vue 的 按需加载文档 了解到,可以使用 import Button from 'ant-design-vue/lib/button' 的方式来加载组件,那么只需要修改 NuxtJS 的 antd vue plugin 就好了。

我没有按文档使用 babel-plugin-import,因为我希望 antd.css 不要打包,我自己通过 CDN 来加载,这个后面会说。

找到 plugins/antd-ui.js 文件,原来的写法是直接引用整个 antd vue

import Vue from 'vue'
import Antd from 'ant-design-vue/lib'    

Vue.use(Antd)

现在改为按需引入需要的组件

import Vue from 'vue'
import Antd from 'ant-design-vue/lib'    

import { message, notification } from 'ant-design-vue'

import Button from 'ant-design-vue/lib/button'
import Layout from 'ant-design-vue/lib/layout'
// 后略

Vue.prototype.$message = message
Vue.prototype.$notification = notification

Vue.use(Button)
Vue.use(Layout)
// 后略

注意需要手动为 Vue 加上 $message$notification 才能正常使用

减小 antd icons 占用的空间

antd icons 很大

antd vue 依赖 2.1.1 版本的 antd icons,而这 icons 也太大了……搜索到比较好的解决方案是 #12011 Svg icons make bunlde size too large 说的,根据它的指引进入到 reduce-antd-icons-bundle-demo 可以看见解决方案。

是将我们需要使用的图标整合到一个 js 文件里,然后再通过 webpack 的 alias 用我们的 js 文件替代掉原始的占空间很大的 antd icons 文件。

首先,在 assets 文件夹下建立 antd-icons.js 文件(其实放在哪个文件夹、文件叫什么没有强制要求),把我们用到的图标都添加进去,包括控件上依赖的(比如 Popconfirm 气泡确认框前面那个感叹号图标):

export {
  default as WechatOutline
} from '@ant-design/icons/lib/outline/WechatOutline'
export {
  default as ExclamationCircleOutline
} from '@ant-design/icons/lib/fill/ExclamationCircleFill'
// 后略

需要注意的是要区分好图标属于什么类型的,需要分别从 @ant-design/icons/lib/outline@ant-design/icons/lib/fill 或者 @ant-design/icons/lib/twotone 里引入。

图片描述

控件依赖的图标可以通过“开发人员工具”的元素查看器查看到。

然后在 nuxt.config.js 中为 webpack 设置 alias

var path = require('path');

module.exports = {
  build: {
    extend(config, ctx) {
      // antd icons 按需加载,能大幅降低 antd icons 占用的空间,提高加载速度
      config.resolve.alias['@ant-design/icons/lib/dist$'] = path.resolve(__dirname, './assets/antd-icons.js')
    }
  }
}

这样 antd icons 就变成我们自己编写的精简版了。

减小 moment 占用的空间(使用 dayjs 代替 moment)

moment 也占了60多k

antd 团队有一个名为 antd-dayjs-webpack-plugin 的插件可以将 moment 替换为体积很小的 dayjs,非常方便。

首先安装 dayjs 和该插件:

npm i dayjs --save
npm i antd-dayjs-webpack-plugin --save-dev

然后为 NuxtJS 添加 dayjs 插件,用于配置默认地区(如果已经配置过则忽略这一步)。在 plugins 文件夹中新建 dayjs.js

import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'

dayjs.locale('zh-cn')

接着在 nuxt.config.js 中添加上面创建的 dayjs plugin 以及向 webpack 添加 antd-dayjs-webpack-plugin

var AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin')

module.exports = {
  plugins: [
    '@/plugins/dayjs'
  ],
  build: {
    plugins: [
        new AntdDayjsWebpackPlugin()
    ]
  }
}

这样就把压缩后 60 多 k 的 moment 替换成了不到 4k 的 dayjs 了。

使用 CDN

对于原始文件

正如前文提到的 antd.css(压缩后有 55k 以上,很大),因为我没有做任何修改,所以直接从 CDN 引入这个文件会更好,在这里我强烈推荐 jsDelivr,能从 GitHub、npm 直接加速,而且国内也有节点,十分稳健。

直接向 nuxt.config.js 中添加 antd.css 的引用:

module.exports = {
  link: [
    { rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/ant-design-vue@1.6.4/dist/antd.css' }
  ]
}

对于动态生成的文件

直接从 CDN 引入的文件并非越多越好,webpack 将我们依赖的类库打包在一起能获得更好的加载性能(浏览器用于访问资源的线程是有限的)。所以对于剩下这些大小可以接受的类库,以及我们自己编写的代码,使用 webpack 打包后,上传到自己的 CDN 中,效果会更好。

nuxt.config.js 中可以配置 publicPath 指向 CDN 上的资源:

module.exports = {
  build: {
    publicPath: process.env.NODE_ENV === 'development' ? '/_nuxt/' : 'https://cdn.bun.plus/blog/client/'
  }
}

然后将 npm run build 命令生成的包里的 /.nuxt/dist/client/ 文件夹上传到 CDN 对应目录即可(publicPath 配置的地址对应到 /.nuxt/dist/client/ 文件夹里面的内容)。

使用 CDN 时我们注意需要开启客户端缓存以及压缩

  • 默认每次打包生成的文件名都不同,可以放心的把客户端缓存时间设置得较长,以保证回访者能在本地命中缓存,提高访问速度
  • 可将 .js.css 这类文件在 CDN 中设置为自动压缩,最大限度减小文件体积
  • 如果自己没有 CDN,可以考虑上传到 GitHub 上,然后通过 jsDelivr 来加速

小总结

鹅妹子嘤!经过上面一番操作,我们的成果非常显著:

优化后的打包大小

app.js 的大小从原来的 2.25M(压缩后575k)减小到741k(压缩后213k),减小了一倍多!

优化后的加载速度

并且在 CDN 的帮助下,100ms 以内就能返回,和原来 1M 带宽的小水管需要 5s 返回比起来提升更大!

那我们总结一下吧:

  • 确定问题原因

    • 依赖的 js 等文件过大
    • 依赖的 js 等文件加载时间过长
  • 由文件过大导致的加载时间过长

    • 尽量按需引入依赖的类库
    • 使用小巧的类库替代庞大的类库
    • 将较大的,能通过公共 CDN 引用的文件单独引用(但数量不宜过多)
    • 将大小可接受的内容打包到一起,通过自有的 CDN 加速

服务器端渲染生成的 HTML 较大

加载慢还有一个问题是因为访问到的 HTML 文件较大(可以像上面一样通过“开发人员工具”查看)。当点开服务器返回的 HTML 内容时惊呆了,博客正文内容上方全是项目依赖的,以及我自己编写的 CSS 代码:

HTML开头全是CSS

这样有一个比较严重的问题——对 SEO 不友好。你以为所有人看到的都和浏览器里显示的一样,但搜索引擎看到的确是这样(没有上面图里那篇博文的截图了,下面是百度抓取另一篇博文《使用 Nuxt.js 搭建博客前端》的内容):

HTML开头全是CSS-百度抓取

而且搜索引擎可能会直接忽略 200k 之后的内容,导致你的实际内容不被收录。

通过在 nuxt.config.js 中配置 extractCSS 就可以将 CSS 单独生成文件,而非嵌入到页面中:

module.exports = {
  build: {
    extractCSS: true
  }
}

通过把 antd.css 改用 CDN 加载,并且自定义的 CSS 不嵌入到页面上,访问《Windows Terminal 美化及通过右键菜单启动》这篇博客,服务器生成的 HTML 大小由 529k(压缩后 72k)减小到 52.8k(压缩后 12.3k),加载时间从 400ms 降低到了 200ms 以内,效果很棒。

更重要的是,这增加了网页被搜索引擎发现与收录的机会。