所谓 File Prefetching 就是在一个页面加载成功后,默默去预加载后续可能会被访问到的页面的资源。
前端资源预加载其实没啥新鲜的,咱们倒腾这个事情的过程倒是颇有有意思也颇有启发性。javascript
一、建一个独立的页面,里面索引了各类须要预加载的css、js,代码相似下面这样。php
<html>
<head>
<link rel="stylesheet" href="//su.yzcdn.cn/v2/build_css/stylesheets/wap/showcase_d0fbaaef124a8691398704216ccd469a.css">
...其余须要预加载的css
</head>
<body>
<script src="//su.yzcdn.cn/v2/build/wap/common_08b03c7826.js" onerror="_cdnFallback(this)"></script>
...其余须要预加载的js
</body>
</html> 复制代码
二、 在每一个页面加入一个iframe(通常经过base模板统一加),这样每一个页面打开的时候都会加载上面这个页面。假设上面的页面的url是 https://xxx.com/common/prefetching.html
那么咱们每一个页面底部都有这么一行代码:css
<iframe src="https://youzan.com/common/prefetching.html" sytle="display:none;"></iframe> 复制代码
要验证某个file prefetching的方案是否真的有效,无非就是如下几步: (假设A页面使用了showcase_d0fbaaef124a8691398704216ccd469a.css
,而B页面不会)html
cache-control
信息判断该静态文件是否已通过期了,若是没有,会以 if-modified-since
、Etag
等信息做为 request headers 向服务器请求这个文件,服务器若是认为文件没有变过,会返回Http code为304,浏览器因而直接读cache。具体不展开啦,能够看 《HTTP caching 》 和 《Understanding HTTP/304 Responses》。让chrome终端打开的时候cache功能依旧有效:Chrome终端的配置里把Disable cache (while DevTools is open)
的勾选去掉
清空全部cache:地址栏里输入 chrome://settings/clearBrowserData
打开后勾上 Cached images and files
点 Clear browsing data
查看浏览器当前cache的资源列表:chrome://cache/
前端
目前看来,上面这个 File Prefeching 的方案是有效的。不过这种是最简陋的试验版,存在几个问题:java
因而,咱们上线使用的版本是这样的:web
一、有一段每一个页面都会被执行到的js:chrome
// 打开一个iframe,下载以后页面可能须要的js/css
setTimeout(function() {
var lastOpenTime = 0;
var nowTime = (new Date()).getTime();
try {
lastOpenTime = window.localStorage.getItem('staticIframeOpenTime');
} catch (e) {}
if (lastOpenTime > 0 && (nowTime - lastOpenTime < 24 * 3600 * 1000)) {
// 24小时打开一次iframe
return;
}
var iframe = $('<iframe>').css('display', 'none');
iframe
.attr('src', 'https://youzan.com/common/prefetching.html')
.appendTo(document.body);
try {
window.localStorage.setItem('staticIframeOpenTime', nowTime);
} catch (e) {}
}, 3000);
// 延时3秒钟加载prefetching.html复制代码
二、prefetching.html 里的资源想办法让他下载但不执行,基本上都是把这些css/js文件当作其余类型的文件来加载,最后参照了《Preload CSS/JavaScript without execution》这篇文章,prefetching.html 中加载js文件的代码大概是这样的:segmentfault
<script type="text/javascript">
window.onload = function () {
var i = 0,
max = 0,
o = null,
preload = [
'须要预加载的文件路径'
],
isIE = navigator.appName.indexOf('Microsoft') === 0;
for (i = 0, max = preload.length; i < max; i += 1) {
if (isIE) {
new Image().src = preload[i];
continue;
}
// firefox不兼容 new Image().src 这种方式,因此除了IE都借用 object 来加载
o = document.createElement('object');
o.data = preload[i];
o.width = 0;
o.height = 0;
document.body.appendChild(o);
}
};
</script> 复制代码
经过对预加载的js文件只下载不执行
、延时加载prefetching.html
、借助localstorage的记录一天只加载一次prefetching.html
,基本上解决了版本一的3个问题。后端
移动页面全站上线后,平均loaded时间减小了0.15s,首屏时间没有数据,不过收益应该是可观的
不过,这个版本上线后,咱们发现页面在prefetching的时候会假死,最后定位到是由于object加载js致使的(具体为何会这样还没细究),考虑到咱们主要的页面都是在手机端访问的,基本上都是webkit内核(Image的方式在firefox中不兼容也不甚关系),因此咱们决定改用Image来加载全部JS。
这个版本除了解决第二个版本的假死问题,还加入了dns-prefetch,关于这部分的背景和思路能够参考我另一篇文章:《预加载系列一:DNS Prefetching 的正确使用姿式》。
<!DOCTYPE html>
<html>
<head>
<?php // dns prefething here ?>
<link rel="dns-prefetch" href="//youzan.com/">
...
<?php // css prefething here ?>
<link rel="stylesheet" href="//su.yzcdn.cn/v2/build_css/stylesheets/wap/showcase_d0fbaaef124a8691398704216ccd469a.css">
...
</head>
<body>
<?php // js prefething here ?>
<script type="text/javascript">
(function(){
window.onload = function () {
var i = 0,
max = 0,
preloadJs = [
'js文件路径',
...
];
for (i = 0, max = preloadJs.length; i < max; i += 1) {
new Image().src = preloadJs[i];
}
};
})();
</script>
</body>
</html> 复制代码
上线后,丝丝润滑无痛无痒,完美
注意哦,重点来咯!
尽早加载css是减小首屏时间的关键(引伸阅读),直接把css inline到html里是个不错的方案。可是,这种方案的缺点是没法充分利用浏览器缓存。因此,咱们尝试在现有的File Prefetching 的基础上,再进一步,让首次访问足够快(用css line),后续访问又能利用起浏览器缓存。
咱们对一部分重点页面的css文件改用相似加载js的方式去加载,并在加载成功的回调里加一条cookie记录标示该css文件已经被下载。这样在后端输出html的时候,能够根据cookie的信息知道这几个css文件是否是已经在浏览器里cache住了。若是是则正常输出一个标签。若是不是,说明用户是第一次访问这个页面,则直接把css文件的内容inline到html里以求最快出首屏。固然,也会出现从cookie上看客户端已经cache了某个css文件,但实际上没有的状况,因为这种状况下html里输出的仍是一个link标签,并不会影响正常的流程。
相关代码大概是这样的,须要的朋友能够参考下:
var loadCss = function(key, url) {
var image = new Image();
var date = new Date();
date.setTime(+date + 1 * 86400000);
// 由于下载的不是图片,实际触发的是onerror事件
image.onload = image.onerror = function () {
document.cookie = key + '=' + url.slice(url.indexOf('build_css')) + ';path=/;domain=.youzan.com;expires=' + date.toGMTString();
};
image.src = url;
}
preloadCss = {
key1: '文件路径',
key2: '文件路径2'
...
}
for (var key in preloadCss) {
loadCss(key, preloadCss[key]);
}复制代码
在作 File Prefetching 的过程中,每个版本的优化都是不一样的人在作的:
A起了个头 ->
B改进到能上线的标准 ->
发现有问题,C改进了它 ->
D又在这个基础上作出了最后一个版本。
这种感受很是好:)
本文首发于有赞技术博客:tech.youzan.com/file-frefet…