继之前的一篇《记前端项目首屏加载优化(打包篇)》之后,这次来讲一讲我的首屏加载在网络方面的优化😏。

写在前面

资源加载是一个网站的展示在用户浏览器的必经之路,资源的请求次数和响应时间决定了网站的加载体验。本篇主要针对请求次数和响应时间聊一聊优化过程。

网站分析与测试环境

有很多工具可以检测网站的网络请求:比如WebPage Speed TestInstant Website Test,这些工具可以分析网站的加载速度、安全性、可靠性等很多方面。而我是希望能够在本地开发过程中分析并优化,所以我还是选择Chrome Devtool 里的Network panel 进行分析。

本地开发环境的资源跟线上是有所不同的,本地的资源一般没有压缩,而线上有压缩,这样就会造成测试环境不真实,所以需要在本地模拟线上的打包环境,编译出跟线上环境一样的包来加载,这样分析出来的结果才有意义。

在上一篇《记前端项目首屏加载优化(打包篇)》有写到过在本地打包一个和线上环境一致的压缩配置,在package.json加入下面的配置:

1
2
3
4
"scripts": {
...
"local_production": "cross-env NODE_ENV=local_production npm run build"
}

然后在webpack配置里面判断process.env.NODE_ENV === ‘local_production’,便构建出production环境的包即可。

gzip压缩

打包出来的包大小1.7M,所有的依赖包index.xxx.js有748k这么大,所以需要开启gzip压缩,可以大大减小加载大小,首先安装express 的gzip包 compression:

1
npm install -D compression

然后添加express中间件:

1
2
var compression = require('compression');
app.use(compression());

打包后启动服务器,浏览器访问,这时候index.xxx.js已经压缩到212kb😎

image.png
image.png

开启http2.0

http1.x时代的优化折磨好长一段时间,各种奇淫技巧为了弥补http1的短板,影响着我们的开发专注度,好在http2已经开始盛行,相信不久的将来可以完全替代http1。

现在基本主流的浏览器都支持http2.0了

http2-浏览器支持
http2-浏览器支持

http2.0大幅提升了加载性能,相比http1增加了多路复用、二进制分帧、header压缩等特性

开启http2也很简单,前提是开启了https协议,只要在Nginx配置文件中找到你要开启http2.0的域名server模块,然后将 listen 443 ssl;改成 listen 443 ssl http2; 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {

listen 443 ssl http2;
server_name domain.com;

ssl_certificate /path/to/public.crt;
ssl_certificate_key /path/to/private.key;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #允许的协议
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; #加密算法(CloudFlare 推荐的加密套件组)
ssl_prefer_server_ciphers on; #优化 SSL 加密套件
ssl_session_timeout 10m; #客户端会话缓存时间
ssl_session_cache builtin:1000 shared:SSL:10m; #SSL 会话缓存类型和大小
ssl_buffer_size 1400; # 1400 bytes to fit in one MTU

检查请求是否已经开启http2,可以在chrome的network面板点鼠标右键,选中protocol,就可以看到该请求是否开启了http2,图中h2就代表http2:

http2
http2

顺便提一句,很多第三方的SDK都没有开启http2🤔

CDN加速

项目开发中用到的静态资源和打包后的资源都可以传到cdn以提高加载速度,我的项目是用七牛的上传脚本,在npm run build之后执行上传脚本将dist目录上传到cdn上,注意在webpack配置里要配置publish_path对应上传cdn后的域名路径。具体看七牛的上传脚本文档

构建线上环境的publicPath的值可以是cdn的域名+路径的形式,结尾要加/
"https://cdn.xxx.com/static/

webpack的output里配置publicPath

1
2
3
4
output: {
path: DIST_PATH,
publicPath: publicPath,
},

webpack的图片资源Loader应添加相应配置publicPath

1
2
3
4
5
6
7
8
9
10
{
test: /\.(jpg|gif|ico|png|svg|mp4|mp3)$/,
use: `url-loader?limit=10&name=asset/[name]_[hash:5].[ext]&publicPath=${config.PUBLIC_PATH_ASSET}`,
exclude: /(node_modules)/,
},
{
test: /\.(woff|eot|ttf)\??.*$/i,
loader: `url-loader?limit=1000&name=fonts/[name].[hash].[ext]&publicPath=${config.PUBLIC_PATH_ASSET}`,
exclude: /(node_modules)/,
},

无模块化js的按需加载

有些时候我们引入的第三方插件是不能被webpack优化按需加载,或者是没有提供模块化加载的(只能通过script标签引入的)插件,比如像tinymce富文本编辑器,由于它的加载方式是靠script标签引入tinymce.js,并靠tinymce.js里自身的加载机制去另外加载主题theme.js或者相关plugin.js。

这种第三方插件无法做成模块化。这样的话我们是不是就只能在入口index.html加script标签引入它,被迫在首屏加载这个暂时用不到的大文件🤔?

我找到了webpack-require-http来帮忙😎,他可以帮我用require语句帮我加载远程js,并提供了回调🤝,于是我便可以控制在什么时候加载tinymce🤘。

我的项目是按路由按需加载的,首页没有用到富文本编辑器所以没必要加载,而论坛页需要用到富文本编辑器,于是我们可以在react-router路由配置里的添加onEnter钩子,在进入论坛页的时候才加载tinymce。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在需要tinymce编辑器的路由时提前加载
const require_tinymce = (nextState, replace, cb) => {
require('https://cdn.bootcss.com/tinymce/4.7.13/jquery.tinymce.min.js').then(() => {
cb();
});
};

// router-config.js
{
path: 'forum/:forum_id',
component: forum_route,
onEnter: require_tinymce,
},

这样子如果访问首页的话,首屏就少加载了这个big guy,经测试,在模拟1M/s 的网速下访问网站,出现首屏界面的时间快了2s左右,2s! 😱…,这可以说是最大力度的一次优化了。

缓存

缓存有很多种方式,大部分是服务器端处理的,而客户端处理的则一般是把资源缓存在本地。

比较有效的本地缓存一般是用application-cache或者service worker将网站的资源缓存到本地,再次访问时直接调用本地的缓存资源,几乎是本地打开的速度,但是我的项目里的资源用到了CDN配置,所有的资源都在CDN外链中,而刚才提到的两种缓存方式都不支持缓存跨域资源😞。

这时候就得权衡一下了🤔,如果是首次访问网站的浏览器,加载时间cdn服务器会比自身服务器快一些,而二次加载则后者更快,只是网站缓存需要自己维护一套配置,如果配置不得当,会落下很严重的坑😂,所以目前我还是先选择相对保守的CDN加载,等以后慢慢磨合🙂。