PWA初探
什么是PWA
PWA(Progressive Web Apps):渐进式 Web app
PWA 旨在增强 Web 体验,能让用户在访问一个web的时候感觉在使用app一样。
PWA可以看作是一系列技术的结合体,它通过Manifist解决了首屏白屏、沉浸式的问题,更可以通过配置Manifist将web加到桌面上,使其像在访问原生app一样。并且,通过Service Worker解决了网络加载问题,可以使用户在离线的环境下也可以访问。并且service worker强大的网络请求拦截可以帮助用户更好的原生APP体验。
服务工作线程受 Firefox 和 Opera 支持。
Microsoft Edge 现在表示公开支持。
可以在 Jake Archibald 的 is Serviceworker ready 网站上查看所有浏览器的支持情况。
service worker的作用
主要有:
1 网络代理,转发请求,伪造响应;
2 离线缓存;
3 消息推送;
4 后台消息传递
Service Workers: PWA 的关键
Service Worker 是 Chrome 团队提出的Web API,主要用于做持久的离线缓存。
Service Worker这个概念可能比较难懂,它其实是在后台启动的一条服务worker 线程。
它不可以访问页面上的DOM元素,没有页面上的API,但是可以拦截所有页面上的网络请求,包括页面导航,请求资源,Ajax请求。
配合Cache Storage API,可以对页面发送的请求进行管理,这就是为什么Service Worker能让站点离线的原因。
在将来,基于它可以实现消息推送,静默更新以及地理围栏等服务,但是目前它首先要具备的功能是: 拦截和处理网络请求,包括可编程的响应缓存管理。
Service Workers 可以让你全权控制网站发起的每一个请求,这为许多不同的使用场景开辟了可能性。
Service Worker 运行在 worker 上下文中,这意味着它无法访问 DOM,它与应用的主要 JavaScript 运行在不同的线程上,所以它不会被阻塞。它们被设计成是完全异步的,因此你无法使用诸如同步 XHR 和 localStorage 之类的功能。
Service Worker 的几个特征
- 只能使用 HTTPS( 避免出现中间人攻击的情况)
- 运行在它自己的全局脚本上下文中
- 不绑定到具体的网页
- 无法修改网页中的元素,因为它无法访问 DOM
Service Worker 生命周期
https://developers.google.com...
- Installing:发生在SW注册后,调用install事件进行静态资源的缓存
- Installed:SW的完成安装,并且等待其他的Service Worker被关闭
- Activating:在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。
- Activated:一旦安装完成,如果注册的js没有变化,则显示为已激活
- Redundant:Service Worker的生命周期结束。
可以看到生命周期分为这么几个状态: 安装中, 安装后, 激活中, 激活后, 废弃
这里特别说明一下,进入废弃 (redundant) 状态的原因可能为这几种:
- 安装 (install) 失败
- 激活 (activating) 失败
- 新版本的 Service Worker 替换了它并成为激活状态
1.当用户首次导航至 URL 时,服务器会返回响应的网页。
2.当你调用 register() 函数时, Service Worker 开始下载。在注册过程中,浏览器会下载、解析并执行 Service Worker 。如果在此步骤中出现任何错误,register() 返回的 promise 都会执行 reject 操作,并且 Service Worker 会被废弃。
3.一旦 Service Worker 成功执行了,install 事件就会激活。
4.一旦安装这步完成,Service Worker 便会激活, 并控制在其范围内的一切。如果生命周期中的所有事件都成功了,Service Worker 便已准备就绪,随时可以使用了!
需要注意的是,当第一次加载页面时,Service Worker 还没有激活,所以它不会处理任何请求。只有当它安装和激活后,才能控制在其范围内的一切。这意味着,只有你刷新页面或者导航到另一个页面,Service Worker 内的逻辑才会启动。
需要注意的是:首次注册 Service Worker 的页面将不会被控制,直到该页面再次被加载。
一旦 Service Worker 处于控制之下,它将处于以下状态之一:
- 它将处理当页面发出网络请求或消息时发生的 fetch 和 message 事件。
- 它将被终止以节省内存
生命周期的具体细节:
https://developers.google.com...
PWA 极简入门
PWA.zip14.70 KB已存到云盘下载
yarn && yarn start
运行起来:
注册服务工作线程
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
我们首先检查浏览器实际上是否支持 Service Workers 。如果支持,则在页面加载后注册位于
/sw.js
的服务工作线程。
每次页面加载无误时,即可调用 register()。
浏览器将会判断服务工作线程是否已注册并做出相应的处理。
register()方法的一个重要细节是 Service Worker 文件的位置。
在上面的例子中,可以看到 Service Worker 文件位于域的根目录。这意味着 Service Worker 的范围将是整个网站。换句话说,服务工作线程将接收此网域上所有事项的 fetch 事件。
如果我们在 /example/sw.js 处注册服务工作线程文件,则服务工作线程将只能看到网址以 /example/
开头(即 /example/page1/ , /example/page2/ )的页面的 fetch 事件。
安装服务工作线程
基础示例:
self.addEventListener('install', function(event) {
// Perform install steps
});
在 install 回调的内部,我们需要执行以下步骤:
1.打开缓存。
2.缓存文件。
3.确认所有需要的资产是否缓存。
var cacheStorageKey = 'minimal-pwa-8';
// self 为当前 scope 内的上下文
self.addEventListener('install', function(e) {
console.log('Cache event!')
// waitUntil用于在安装成功之前做一些预装逻辑
// 安装内容建议轻量级,避免安装失败
e.waitUntil(
// 使用 cache API 打开指定的 cache 文件
caches.open(cacheStorageKey).then(function(cache) {
console.log('Adding to Cache:', cacheList)
// 添加要缓存的文件
// 缓存文件全部安装成功后,installing会变成installed,安装失败进入redundant状态
return cache.addAll(cacheList)
}).then(function() {
// 跳过waiting,直接进入active
console.log('Skip waiting!')
return self.skipWaiting()
})
)
});
在这里您可以看到,我们以所需的缓存名称调用
caches.open()
,之后再调用
cache.addAll()
并传入文件数组。 这是一个 promise 链。
event.waitUntil()
方法带有 promise 参数并使用它来判断安装所花费的时间以及安装是否成功。
激活
安装成功后,会进入激活状态,此时触发active事件,通过active事件可以做一些预处理: 比如对旧版本的更新,或对无用缓存的清理.
self.addEventListener('activate', function(e) {
console.log('Activate event')
e.waitUntil(
Promise.all(
caches.keys().then(cacheNames => {
return cacheNames.map(name => {
if (name !== cacheStorageKey) {
return caches.delete(name)
}
})
})
).then(() => {
console.log('Clients claims.')
// 通过clients.claim方法,更新客户端上的server worker
return self.clients.claim()
})
)
})
关于缓存
这里用的就是 cacheStorage 缓存,它提供了一个ServiceWorker类型的工作者或window范围可以访问的所有命名缓存的主目录, 并维护字符串的映射名称到相应的 Cache 对象。
主要方法包括:
有了这些方法你可以对你的缓存进行操作。目前还在草案状态,仅火狐和谷歌浏览器支持此特性。
http 缓存 / Manifest / Service Worker 三种 cache 的关系
三种缓存都使用时, 会以service worker 优先, 因为sw 把请求拦截了, 优先做处理,如果缓存库里有, 就直接返回, 没有就走正常请求。
然后就到了Manifest 层,Manifest缓存里有的话, 就直接取,没有的话就去请求。
然后会到HTTP 缓存里面取, 没有的话,就发请求去获取, 服务端根据HTTP的etag 或者Modified Time , 返回304 或者 200 + 数据内容。
性能测试:
开箱即用的插件
offline-plugin
https://lavas.baidu.com/guide...
在测试环境的配置:
// ...
const OfflinePlugin = require("offline-plugin");
// ...
new OfflinePlugin({
AppCache: false, // 不启用appCache
safeToUseOptionalCaches: true, // Removes warning for about `additional` section usage
caches: {
main: [
'**/*.js',
],
additional: [
':externals:'
]
},
externals: [],
excludes: ['**/.*', '**/*.map', '**/*.gz', '**/manifest-last.json'],
autoUpdate: true,
updateStrategy: 'all',
ServiceWorker: {
output: './service-worker.js', // 输出目录
publicPath: '/service-worker.js', // sw.js 加载路径
scope: '/',
minify: true, // 开启压缩
events: true // 当sw状态改变时候发射对应事件
},
}),
// index.html
import OfflinePluginRuntime from 'offline-plugin/runtime';
OfflinePluginRuntime.install({
// 监听sw事件,当更新ready的时候,调用applyUpdate以跳过等待,新的sw立即接替老的sw
onUpdateReady: () => {
console.log('SW Event:', 'onUpdateReady');
OfflinePluginRuntime.applyUpdate();
},
onUpdated: () => {
console.log('SW Event:', 'onUpdated');
window.swUpdate = true;
},
});
更多信息:
https://juejin.im/entry/5a1c3...
https://lavas.baidu.com/doc/o...