前言
-
页面以最快的速度获取到所有必须静态资源,渲染飞快;
-
服务器上静态资源未更新时再次访问不请求服务器;
-
服务器上静态资源更新时请求服务器最新资源,加载又飞快。
-
静态资源加载速度
-
页面渲染速度
先用一张图来概括下本文中将会涉及到的内容。
常见缓存类型
1、浏览器缓存
可在w3c的官方文档中查看所有HTTP Response Header字段的定义,跟缓存相关的主要有上图中被圈出来的几个:
-
Cache-Control:
- public:响应被缓存,并且在多用户间共享。
- private:默认值,响应只能够作为私有的缓存(e.g., 在一个浏览器中),不能再用户间共享;
- no-cache:响应不会被缓存,而是实时向服务器端请求资源。
- max-age:数值,单位是秒,从请求时间开始到过期时间之间的秒数。基于请求时间(Date字段)的相对时间间隔,而不是绝对过期时间;
-
Pragma: 只有一个用法Pragma: no-cache,它和Cache-Control:no-cache作用一模一样。(Cache-Control: no-cache是http 1.1才提供的, 因此Pragma: no-cache可以使no-cache应用到http 1.0 和http 1.1。)
-
Expires:指定了在浏览器上缓冲存储的页距过期还有多少时间,等同Cache-control中的max-age的效果,如果同时存在,则被Cache-Control的max-age覆盖。若把其值设置为0,则表示页面立即过期。并且若此属性在页面当中被设置了多次,则取其最小值。
-
Date:生成消息的具体时间和日期;
-
Last-Modified/If-Modified-Since:本地文件在服务器上的最后一次修改时间。缓存过期时把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比,如果时间一致,那么返回304,客户端就直接使用本地缓存文件。
-
Etag/If-None-Match:(EntityTags)是URL的tag,用来标示URL对象是否改变,一般为资源实体的哈希值。和Last-Modified类似,如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。Etag的优先级高于Last-Modified,Etag主要为了解决 Last-Modified 无法解决的一些问题。
- 文件也许会周期性的更改,但是他的内容并不改变,不希望客户端重新get;
- If-Modified-Since能检查到的粒度是s级;
- 某些服务器不能精确的得到文件的最后修改时间。
-
If-Modified-Since:值为之前response中Last-Modified;
-
If-None-Match:值为之前response中Etag(如果存在的话);
-
Cache-Control
-
Pragma
-
Expires
"Cache-Control" content="no-cache" />
"Pragma" content="no-cache" />
"Expires" content="0" /> 复制代码
Putting caching instructions into meta tags is not a good idea, because although browsers may read them, proxies won't. For that reason, they are invalid and you should send caching instructions as real HTTP headers.
2、HTML5 Application Cache
a、增加manifest文件
-
文件的第一行必须是 CACHE MANIFEST
-
#开头的行作为注释语句
-
网站的缓存不能超过5M
-
文件资源路径可以使用绝对路径也可以使用相对路径
-
文件列表中任意一个缓存失败都会导致整个缓存失效
-
既可以站点使用同一个minifest文件,也可以每个页面使用一个
-
CACHE:需要缓存的资源文件,浏览器会自动缓存带有manifest属性的html页面;
-
NETWORK:不需要缓存的文件,可以使用通配符;
-
FALLBACK:无法访问缓存文件的备选文件,可以使用通配符。
b、服务器配置
AddType text/cache-manifest .appcache复制代码
c、html中引用
"zh" manifest="main.manifest">复制代码
1、事件
-
cached/checking/downloading/error/noupdate/obsolete/progress/updateready
2、执行过程
-
Creating Application Cache with manifest(访问到带manifest属性的html文件,将manifest文件存储,加载html文件及其他资源文件);
-
Application Cache Checking event(检查要缓存的文件列表)
-
Application Cache Downloading event(开始下载缓存文件)
-
Application Cache Progress event (0 of 4)(依次下载缓存文件)
-
……
-
Application Cache Progress event (4 of 4)
-
Application Cache Cached event(文件缓存完毕)
-
Document was loaded from Application Cache with manifest(从缓存中读取html文件和其他静态资源文件,供页面展示)
-
Application Cache Checking event(获取新的manifest文件,检查是否更新)
- 是:重新下载缓存文件,供下次访问使用(不会影响当前浏览器展示内容)
- Application Cache Downloading event(开始下载缓存文件)
- Application Cache Progress event (0 of 4)(依次下载缓存文件)
- ……
- Application Cache Progress event (4 of 4)
- Application Cache UpdateReady event(缓存文件更新完毕)
- 否
- Application Cache NoUpdate event(啥也不做)
- 是:重新下载缓存文件,供下次访问使用(不会影响当前浏览器展示内容)
-
Document was loaded from Application Cache with manifest(从缓存中读取html文件和其他静态资源文件,供页面展示)
-
Application Cache Checking event(获取新的manifest文件,检查是否更新)
-
Application Cache Obsolete event(删除本地缓存中的所有文件,不再使用缓存)
-
Application Cache会默认缓存引用manifest文件的HTML文档,对于动态更新的html页面来说是个坑(可以使用tricky的iframe嵌入方式来避免);
-
只要缓存列表中的一个资源加载失败,所有文件都将缓存失败;
-
如果资源没有被缓存,而又没有设置NETWORK的情况下,将会无法加载,所以Network中必须使用通配符配置;
-
缓存更新后第一次只能加载manifest文件,其他静态资源需要第二次加载才能看到最新效果;
-
缓存文件清单中的文件本身更新浏览器是不会重新缓存,那怎么告诉浏览器缓存需要更新了呢?
-
- 更新manifest文件:修改注释的版本号或者日期。
- 通过Application Cache提供的接口(window.applicationCache.swapCache)来检查更新。
该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。在此刻使用这里描述的应用程序缓存功能高度不鼓励; 它正在处于从Web平台中被删除的过程。请改用 Service Workers 代替。
3、PWA(Service Worker)
Service worker is a programmable network proxy, allowing you to control how network requests from your page are handled.
-
不能访问 DOM
-
不能使用同步 API
-
需要HTTPS协议(http://localhost 或 http://127.0.0.1也可)
简单使用
复制代码
self.addEventListener('install', function(event) {
/* 安装后... */
// cache.addAll:把缓存文件加进来,如a.css,b.js
});
self.addEventListener('activate', function(event) {
/* 激活后... */
// caches.delete :更新缓存文件
});
self.addEventListener('fetch', function(event) {
/* 请求资源后... */
// cache.put 拦截请求直接返回缓存数据
});复制代码
index.html文件中引用了static/js/main.js,main.js中注册了service-worker.js。service-worker.js中我们可以看到有 precacheConfig(缓存列表)和 cacheName(版本号)两个变量。断开网络,我们看到precacheConfig列表中的文件仍能从本地加载。
更新机制
4、LocalStorage
5、CDN缓存
更新机制
prebrowsing
预加载是浏览器对将来可能被使用资源的一种暗示,一些资源可以在当前页面使用到,一些可能在将来的某些页面中被使用。作为开发人员,我们比浏览器更加了解我们的应用,所以我们可以对我们的核心资源使用该技术。
-
dns-prefetch:DNS预解析,告诉浏览器未来我们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就可以尽快地完成 DNS 解析。多在使用第三方资源时使用。
-
preconnect:预连接,完成 DNS 预解析同时还将进行 TCP 握手和建立传输层协议。
-
prerender:预渲染,预先加载文档的所有资源,类似于在一个隐藏的 tab 页中打开了某个链接 – 将下载所有资源、创建 DOM 结构、完成页面布局、应用 CSS 样式和执行 JavaScript 脚本等。
-
prefetch:预获取,使用 prefetch 声明的资源是对浏览器的提示,暗示该资源可能『未来』会被用到,适用于对可能跳转到的其他路由页面进行资源缓存。被 prefetch 的资源的加载时机由浏览器决定,一般来说优先级较低,会在浏览器『空闲』时进行下载。
-
preload:预加载,主动通知浏览器获取本页的关键资源,只是预加载,加载资源后并不会执行;
prefetch & preload
兼容性
优先级
注:prebrowsing 好用但千万不要乱用,除非你非常明确会加载要prebrowsing的文件,不然会加重浏览器负担适得其反。
应用
'/'>Home
'/features'> Features
{ /* we imperatively prefetch on hover */ }
'/about'>
{ Router.prefetch('/about'); console.log('prefetching /about!') }}>About
'/contact'>Contact (NO-PREFETCHING) 复制代码
优化尝试
1、HTML文件
2、js/css/img文件
现在一般都通过文件名进行版本控制。Webpack打包命名可根据文件内容生成文件名的hash值,每次打包只有当内容改才重新生成hash值。此种情况之下,可以在HTTP Headers设置一个较大的缓存时间,如max-age=2592000,尽量避免304请求和服务器进行请求连接。
// js
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
}
// css
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
}),复制代码
3、webfont
-
浏览器在DOMNode的CSS选择器中发现@font-face时才会下载web fonts文件,这个时候浏览器已经下载完成html/css/js文件;
-
如果在浏览器发现需要加载font文件之前就告诉浏览器下载font文件,会加快文件下载和页面加载速度。
-
React + + Mobx + Webpack
-
React-Router 单页 / bundle-loader动态加载 / 使用较大的webfont文件
-
对静态资源文件进行如上的HTTP Headers缓存配置;
-
所有的静态资源文件通过Service Worker进行缓存控制和离线化加载,示范如上不再赘述;
动态加载的js
-
index.ef15ea073fbcadd2d690.js
-
static/js/0.1280b2229fe8e5582ec5.js
-
static/js/1.f3077ec7560cd38684db.js
-
static/js/2.39ecea8ad91ddda09dd0.js
-
static/js/3.d7ecc3abc72a136e8dc1.js
webpackConfig.plugins.push(new PreloadWebpackPlugin({
rel: 'prefetch',
}));复制代码
rel属性还可以选择preload / prefetch模式。打包出来是这样:
访问页面可以看到,在不影响dom加载的情况下,浏览器预先加载了另外几个后面将会用到的js,当切换到对应路由时,也会直接从缓存取,不从服务器请求资源。
css文件
动态加载路由中css没有单独拆分而是在路由的js中,所以只能随着js优化了。
webfont文件
1、写插件
fontpreload-webpack-plugin
2、用插件
- 安装插件
npm install fontpreload-webpack-plugin --save-dev复制代码
- 在webpack的config文件的HtmlWebpackPlugin插件之后增加:
const FontPreloadWebpackPlugin = require('fontpreload-webpack-plugin');复制代码
webpackConfig.plugins.push(new FontPreloadWebpackPlugin({
rel: 'prefetch',
fontNameList: ['fontawesome-webfont'],
crossorigin: true,
}));复制代码
3、打包效果