对于一个网站来说打开速度是一个很重要的指标,只是大部分时间内我们的精力可能都用来对付需求了,特别是当我们做的是一些内部的项目时,我们常常的会忽略了这一方面的优化。其实要对一个页面的打开速度做出一些比较常见的优化并没有想象中的困难,本文将带你做一些既不费力也不费时间的优化操作,这些操作中涉及到压缩,缓存,preload加载关键资源,prefetch缓存懒加载资源与一些引用组件的建议及常见的工具库处理。
开启gzip压缩
当我们使用webpack打包并压缩js代码后,往往某些js(比如vendor)依然会很大,可能会达到1mb左右的大小,虽然以现在的带宽来说如果我们是pc端项目这也不是什么大问题,但是这里面明显是存在着相当大的优化空间的,gzip就是一种形式。
在浏览器的请求头里包含着这样一句话
Accept-Encoding:gzip, deflate
,这告诉我们浏览器是可以识别gzip压缩的,使用gzip压缩后的文件将大大减小,很多情况下甚至能压缩70%。现在的服务基本上都是使用nginx做转发的,对于我们来说开启gzip其实相当容易,只要配置如下的代码就可以了。
server {
gzip on;
gzip_types text/xml text/css text/plain text/javascript application/javascript application/x-javascript;
}
上图能看到没开启gzip时vendor近800k,而开启gzip后大概只有250k。
可以说如果我们连gzip都没有开启的话,其他任何优化都显得有点多余,因为大概没有另外一种优化方式能压缩如此高的比例。
浏览器缓存
除了常见的gzip压缩外,另一个可以利用的优化点就是浏览器的缓存。我会顺带着介绍一下浏览器的缓存方式,但是不会过于详细,有兴趣的同学可以额外去找一些资料学习。
浏览器分为两种缓存,强缓存与协商缓存(也被称为弱缓存),其中协商缓存不用我们自己配置,下面我们通过连续两次刷新页面来观察一下协商缓存。
Last-Modified: sometime
location ~* \.(css|js)$ {
proxy_set_header Host $host;
proxy_pass http://tomcat_xxx;
expires 7d;
}
location ~* \.(jpg|jpeg|png|gif|webp)$ {
proxy_set_header Host $host;
proxy_pass http://tomcat_xxx;
expires 30d;
}
注意我们这里使用的是tomcat,你可能需要的配置与我这个并不一样,但是这并不关键,我们主要需要的是expires这项配置,他表示了我们希望缓存的时间,我们配置的js与css缓存时间为10天,而图片则缓存30天。一起打开浏览器看一下效果。
from memory cache
表明此时是从内存中直接取出缓存,并没有发送http请求,这对一些图片与我们的依赖包vendor相当有用,我们完全可以给这两个资源设定一个较大的缓存时间,这样当用户访问第一次后,这些资源始终会保持在用户的缓存中,就算我们之后更改了很多我们的业务代码,只要依赖没有更改,用户只用加载一些小的业务代码文件就可以了,对于较大的vendor则依然可以从缓存中获取。
我们可以简要的总结一下浏览器的缓存方式并增加一些注意的点。浏览器会首先检测强缓存,如果命中则直接返回缓存文件,不会发送http请求,如果没有命中则去检查弱缓存,当弱缓存命中时返回304状态码,浏览器依然从缓存中获取资源,如果弱缓存也没有命中则返回200状态码重新加载服务器上的资源。
注意点:
-
强缓存、弱缓存只是名字上的区别并没有什么强弱之分,其实对于一般的浏览器来说刷新就会使你当前请求资源的强缓存失效,因为刷新的时候会请求头中会携带一个
max-age=0或是no-cache
,注意我这里说的当前请求资源指的一般是你页面的html文档,但是对于文档中外链的js与img等,不会因为刷新导致强缓存失效。不过如果你直接请求的是一个js文件,那么刷新后这个js文件强缓存也会失效。 - 既然强缓存不会发起http请求,那么服务器资源有变更的情况怎么办。其实webpack生成的hash码就是帮我们解决这个问题用的,当外链的app.123456.js变成了app.654321.js浏览器自然会重新发起请求,这也提示了我们尽量不要去改变vendor导致vendor的hash变更产生缓存失效的问题。
对于关键资源的优先加载与一些懒加载资源的预加载
由于我们的技术栈是vue,所以以下示例我们用vue来进行演示,但是本质上无论是什么技术栈都是一样的。 假设我们的项目是单页面应用那么首先应该优化的点就是路由的懒加载,也就是说不要一次性的将所有代码一起返回,只有切换到当前路由时我们才去请求当前路由对应的代码。对于vue-cli初始化的项目来说配置十分的简单,在router中更改一下import的方式就可以。
const router = new Router({
routes: [
{
path: '/',
redirect: '/a',
},
{
path: '/a',
component: () => import('../components/a/index.vue'),
},
{
path: '/b',
component: () => import('../components/b/index.vue'),
},
]
现在我们就可以根据我们访问的router动态的加载js文件了。但是这样其实还有优化的空间,假设我们现在请求路由a,加载了vendor等公共js与a本身的js,那么在访问a页面的空余时间里为什么我们不将b路由的js也对应的加载到浏览器的缓存中那,这样当用户切换到b路由时就可以不用在发送http请求而是直接使用缓存中的文件就可以了。
在这里我们要用到一个webpack插件,
PreloadWebpackPlugin
,这个插件的作用是帮助我们对应的生成
<link rel="preload" href="xxxx">
与
<link rel="prefetch" href="xxxx">
标签,其中preload中href的资源浏览器会优先的进行加载,关于preload的作用mdn文档是如此说的。
在浏览器的主渲染机制介入前就进行预加载。这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。
具体相关其实就是浏览器的关键路径的知识,这里不详述,可以另找资料。
而对于prefetch的href浏览器会进行预加载,同样这里引用mdn文档中的话对其描述
其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。网页向浏览器提供一组预取提示,并在浏览器完成当前页面的加载后开始静默地拉取指定的文档并将其存储在缓存中。当用户访问其中一个预取文档时,便可以快速的从浏览器缓存中得到。