本文内容使用 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)太多或者太大,浏览器需要花一些时间才能将它们下载下来。
通过“开发人员工具”查看加载时间
打开浏览器的 开发人员工具
- 网络
,然后访问网站,来查看网页需要加载哪些东西,以及它们的大小和耗时。
查看时需要勾选
禁用缓存
来模拟首次到访网站的浏览者的真实状态。
如果界面和我的略有不同,可以点击右上角齿轮图标按钮,勾选使用大请求行
。
通过 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
很大,而它主要又由 ant-design-vue
、@ant-design/icons/lib
和 moment
等内容组成。如果能把这些大个的文件减少,那生成的 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 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)
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 代码:
这样有一个比较严重的问题——对 SEO 不友好。你以为所有人看到的都和浏览器里显示的一样,但搜索引擎看到的确是这样(没有上面图里那篇博文的截图了,下面是百度抓取另一篇博文《使用 Nuxt.js 搭建博客前端》的内容):
而且搜索引擎可能会直接忽略 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 以内,效果很棒。
更重要的是,这增加了网页被搜索引擎发现与收录的机会。