Progressive Web Apps

  • Web 技术编写出一个网页应用
  • App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能
  • 本质上是 Web App
  • 兼具 Web App 和 Native App 的优点

特点: 可靠、安全、粘性

安装 http-server

npm install –global http-server

用法

http-server [path] [options]

注意:默认情况下,缓存处于打开状态。添加-c-1为禁用缓存的选项。

http-server -c-1

默认访问地址:http://localhost:8080

安装 ngrok

ngrok 下载地址

简单应用示例

  1. index.html main.css

  2. 添加 manifest.json 文件

定义应用的名称, 图标等信息

  1. 添加 Service Worker

网页已经关闭的情况下还可以运行, 用来实现页面的缓存和离线, 后台通知等等功能

1
2
3
4
5
6
if(navigator.serviceWorker != null){
navigator.serviceWorker.register('sw.js').then(
(registration) => {
console.log('Registered events at scope: ', registration.scope)
})
}

Service Worker 全局变量

self: 表示 Service Worker 作用域, 也是全局变量
caches: 表示缓存
skipWaiting: 表示强制当前处在 waiting 状态的脚本进入 activate 状态
clients: 表示 Service Worker 接管的页面

  1. navigator.serviceWorker.register(‘sw.js’) 注册安装 Service Worker
1
2
3
4
5
6
7
8
9
// 注册完成安装时,抓取资源写入缓存

self.addEventListener('install', e => {
e.waitUntil(
caches.open(cacheStorageKey)
.then(cache => cache.addAll(cacheList))
.then(() => self.skipWaiting())
)
})

self.skipWaiting() 方法是为了在页面更新的过程当中, 新的 Service Worker 脚本能立即激活和生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activate')
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== cacheStorageKey) {
console.log('[ServiceWorker] Removing old cache', key)
return caches.delete(key)
}
}))
})
)
return self.clients.claim()
})

调用 self.clients.claim() 取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存

查看 Service Worker

DevTools 的 Application 标签可以看到 Service Worker

功能和特性

独立 worker 线程,独立于当前网页进程,有独立的 worker context
一旦被 install,永远存在,除非 uninstall
具备唤醒,待机功能(存在坑)
可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程获取(包括网络离线状态)
离线内容开发者可控
可以向客户端推送消息
不能直接操作Dom
处于完全考虑,必须 HTTPS 环境下工作
异步实现,通过 Promise

Lavas

基于 Vue 的 PWA (Progressive Web Apps) 完整解决方案

参考文档

踩坑记录

navigator.onLine 只会在机器未连接到局域网或路由器时返回false,其他情况下均返回true

Fetch

  1. Fetch 是基于promise,network error 时返回 rejected 的 promise,ftp 协议不支持 Fetch

  2. 默认情况下不包括请求中的任何cookie

    fetch(‘/url’, { credentials: ‘include’ })

  3. 上传JSON时忘记设置 {'Content-Type': 'application/json'}

    fetch(‘/url’, {

    method: 'POST',   
    body: JSON.stringify({text: 'bacon'}),  
    headers: {'Content-Type': 'application/json'}})
    .then(res => res.json())
    .then(console.log)
  4. 字符串手动拼接一个复杂的查询参数

应该使用 URLSearchParams 类

  1. 获取返回值

    fetch(‘./url’).then(res =>{

     return res.json()

    }).then(data=>{

     console.log(data)

    })

  2. 兼容性问题

由于 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham
引入 Promise 的 polyfill: es6-promise
引入 fetch 探测库:fetch-detector
引入 fetch 的 polyfill: fetch-ie8
可选:如果你还使用了 jsonp,引入 fetch-jsonp
可选:开启 Babel 的 runtime 模式,现在就使用 async/await

  1. 不支持timeout,不支持 progress,特别是 fetch 在跨域问题上与传统跨域的处理方式的区别

let myHeaders = new Headers({
‘Access-Control-Allow-Origin’:’*’,
“Content-Type”:’text/plain’
})

fetch(url,{
method:’GET’,
headers:myHeaders,
mode:’cors’
}).then(res=>{
return res.text()
}).then(data=>{
console.log(data)
})

mode设置成:‘no-cors’ 请求时,reponse 响应中 type 显示为 opaque,则这时候该使用 jsonp 或者后端代理

  1. App Shell 模型、离线 UX 注意事项