记一次项目的优化

前言

性能很重要,所以优化一直是一个很大的话题。而且根据项目和需求的不同,优化的策略也不同。项目优化之前做的一直不多,这次着手准备优化一下之前写的一个项目。

字体渲染优化

分析

先贴一张最开始的分析图

首先分析优化的方向,打开Network分析一下资源的加载情况,可以非常明显的看到字体加载上花了很长时间,平均加载8s,甚至更久。也因此在用户没有缓存的情况下,由于字体没有加载出来,而导致字体出现时间比其它DOM出现时间晚的多,而且首屏加载所等待的时间较长。因此,首先优化字体的渲染势在必行。

方案

分析出来首要优化目标之后,就是针对情况进行优化。其中我发现了一个现象,按道理讲字体渲染出来应该是在字体文件加载完毕之后才会出现。但是实际情况并非如此,而是大致在其它DOM出现后的近4s时就已经出现了。

由于首页(登录页)字体比较少,我并未发现此时渲染出来的字体和下载字体的区别。直到偶然看一些字体优化策略的文章之后才发现,浏览器实行的是FOUT和FOIT策略(文章上讲IE和Edge实行FOUT策略,其它实行FOIT策略),但我经过测试Chrome下并非完全的FOIT策略,而是采取FOUT和FOIT折中的策略。即3s内如果字体加载出来,那么加载出来之前隐藏文本,如果3s内没有加载出来,那么则显示降级字体。

那么此时了解了浏览器对字体渲染的策略之后,就可以下手了。项目里引入了fontfaceobserver,一个字体加载器。可在字体加载完后,执行编写的js。

我们几乎可以确定,在无缓存情况下,3s内Chrome下字体文件加载不出来(Chrome需要近7s,而Firefox仅需2s多)。即使能加载出来,也需要一定时间,和其它DOM显示有一定时间间隔。那么如何让他们同时渲染出来?中间也考虑了几种方案

  • 方案一:登录页默认使用安全字体,0.5s内如果能加载出来下载字体,则使用下载字体,否则登陆页就使用安全字体。

该方案其实就是FOIT策略的变形,不过不同之处是加载不出来就不换了(因为登录页就没几个字,字体切换如果不细看的话几乎看出来。)但是并不是FOIT策略,因为使用FOIT策略的话,字体切换虽然不细看看不出来,但是假如用户在填写账号密码的时候字体发生了切换,那么这个时候是非常明显的,几乎可以很明显的察觉到字体抖动,体验非常不好。因此我将时间点缩到了0.5s(如果有字体缓存,那么还是可以直接使用下载字体)。

这个方案虽然比较合适,但是这个方案也有缺点,大部分情况下(用户进入时是有字体缓存的),那么登陆页在0.5s时会发生一个轻微的字体抖动,虽然在不知情的情况下几乎察觉不出来,但在我看来仍不是一个最佳方案。

  • 方案二:登录页默认使用下载字体,如果0.5s内不能加载出来,那么则使用降级安全字体。

该方案是方案一的另一种思路,也就是FOUT的变形,不同之处是将3s缩减到了0.5s,经过多次测试之后,缩减到了0.1s,0.1s在有缓存的情况下足以加载出来(再小的话有缓存也加载不出来)。该方案可以说D字体和其它DOM同时出现,可以说是体验非常好了。而且等用户将登录信息填写完整之后,字体已经加载好了,进入内容页直接就是下载字体。

其它

同时在这一阶段,将ttf字体转成了woff2和woff,直接缩小了一半多。同时也将图片进行了压缩,同样压缩了一半多。同时服务端又开启了gzip将js和css文件进一步压缩,加载速度又提升了一截。中间也用了uglifyjs试图进一步压缩js,但是效果并不明显,应该是编译之后已经压缩过一次的缘故。

贴一下第一阶段优化之后的加载情况

可以看到,提升了很多,几乎可以说是天壤之别。但其实除了字体渲染策略上考虑了一番以外,别的对资源的压缩之类的并没有花很多功夫,但是提升确实是很显著的。因此静态资源的压缩确实是对优化项目有很大的提高。

懒加载

分析

首先,项目可以简单分为前台页面(一般用户使用的),以及后台管理页面(管理员管理上传之类的)。那么,此时采用单页应用就存在一个问题,那就是,对于普通用户来讲,是不需要进入后台的,但是单页应用不做处理的时候,在首屏直接会将所有文件加载,这对用户体验来讲,是不合适的。因此,需要进行更改。

方案1:懒加载,即将后台的一个大组件以及其包含的三个组件懒加载,令其空闲时加载,减少首屏引入的包体积。

先放一张拆分后的结构,当然经过拆分后发现其实我把问题想简单了,想当然的觉得将共用的抽离出来是最优解。但其实未必,后面也将写一些优化过程中产生的一些想法。

这里vue-cli默认为初始化渲染需要的文件生成preload提示,即预加载。我们这里使用vue-router的懒加载,vue-cli会将将异步组件单独打包生成chunk,并添加prefetch提示,打开network分析可看出,带有prefetch的会在后面加载,当用户进入后台时,此时发起的请求会直接从缓存中拿出来。

中间也发现了一个小问题,发现在开发环境下,css按照style里引入,而在生产环境下是用link引入的。经查阅后才发现MiniCssExtractPlugin这个插件在开发环境下热重载会有问题,所以默认在生产环境下才开启。这里也发现了使用vue-cli确实方便了很多,但是也有很多东西没有明显的配置文件,并不清楚内部如何配置的,后面也是自己都实现了一遍,才大致了解。

继续说懒加载,如此操作产生一个问题,每一个懒加载的组件都会被分成单独的chunk,发现其单独的js和css的chunk里都有重复引入的部分,如下图所示。也就是说进入后台的三个页面,会重复加载其共用的组件。这里经过查阅发现webpack4的SplitChunksPlugin插件默认最下30k才会单独打包。

在最开始,我并不理解webpack的默认策略为什么要这样制定,重复引入组件按道理讲肯定是造成了一些损耗。但其实少考虑了一点,按目前所作的方法,将懒加载的组件单独chunk,但其实每一个chunk才不到8k,甚至有的1k,2k这样子。那么我们为了不重复引入这2k,3k的组件而将其单独抽离出来一个chunk-common,是会额外增加一次http请求的,这样其实未必更优。

其实关于优化,我认为是没有最优的方法的,只有根据业务和需求的不同,尽可能地采取最合适或者说最能适应大部分用户的方案。

在思考代码分离方案时候,首先第三方引入的依赖可以单独打成一个chunk,比如vue,vuex等等这些,这些几乎每个组件都要使用,将其和自己写的业务组件打成一个包很不合理。然后就是引用频率较高的组件或者文件可以单独打成一个chunk,但是还要根据其包的大小具体考虑是否需要单独打包。然后就是一些懒加载引入的单独业务代码了。这里有一篇文章,觉得整理的很好,看后也有很多收获->链接

我上面把包拆分的太颗粒化了,其实未必比都打到一个包里更优,因为后台页面的大小其实并没有很大。那么后面我将尝试,将多个chunk适当合并,即减少首屏加载,也同样减少过多chunk造成的不必要的http请求。

(未完待续。。。。。)