Progressive Web Apps 是 Google 提出的用前沿的 Web 技术为网页提供 App 般使用体验的一系列方案。

这篇文章里我们来完成一个非常简单的 PWA 页面。

一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能。下面的教程基于 Migrate your site to a Progressive Web App 和 Google 给出的 sample 示例。完整代码访问 minimal-pwa 查看。

准备工作

建议安装 http-server 和 ngrok 以便调试和查看。

准备一个 HTML 文件, 以及相应的 CSS 等:

<head>
<title>Minimal PWA</title>
<meta name="viewport" content="width=device-width, user-scalable=no" />
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<h3>Revision 1</h3>
<div class="main-text">Minimal PWA, open Console for more~~~</div>
</body>

添加 manifest.json 文件

为了让 PWA 应用被添加到主屏幕, 使用 manifest.json 定义应用的名称, 图标等等信息。

{
"name": "Minimal app to try PWA",
"short_name": "Minimal PWA",
"display": "standalone",
"start_url": "/",
"theme_color": "#8888ff",
"background_color": "#aaaaff",
"icons": [
{
"src": "e.png",
"sizes": "256x256",
"type": "image/png"
}
]
}

然后在 HTML 文件当中引入配置:

<link rel="manifest" href="manifest.json" />

添加 Service Worker

Service Worker 在网页已经关闭的情况下还可以运行, 用来实现页面的缓存和离线, 后台通知等等功能。sw.js 文件需要在 HTML 当中引入:

<script>
if (navigator.serviceWorker != null) {
navigator.serviceWorker.register('sw.js')
.then(function(registration) {
console.log('Registered events at scope: ', registration.scope);
});
}
</script>

后面我们会往 sw.js 文件当中添加逻辑代码。在 Service Worker 当中会用到一些全局变量:

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

处理静态缓存

首先定义需要缓存的路径, 以及需要缓存的静态文件的列表, 这个列表也可以通过 Webpack 插件生成。

var cacheStorageKey = 'minimal-pwa-1'

var cacheList = [
'/',
"index.html",
"main.css",
"e.png"
]

借助 Service Worker, 可以在注册完成安装 Service Worker 时, 抓取资源写入缓存:

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

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

处理动态缓存

网页抓取资源的过程中, 在 Service Worker 可以捕获到 fetch 事件, 可以编写代码决定如何响应资源的请求:

self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response != null) {
return response
}
return fetch(e.request.url)
})
)
})

真实的项目当中, 可以根据资源的类型, 站点的特点, 可以专门设计复杂的策略。fetch 事件当中甚至可以手动生成 Response 返回给页面。

更新静态资源

缓存的资源随着版本的更新会过期, 所以会根据缓存的字符串名称(这里变量为 cacheStorageKey, 值用了 "minimal-pwa-1")清除旧缓存, 可以遍历所有的缓存名称逐一判断决决定是否清除(备注: 简化的写法, Promise.all 中 return undefined 可能出错, 见评论):

self.addEventListener('activate', function(e) {
e.waitUntil(
Promise.all(
caches.keys().then(cacheNames => {
return cacheNames.map(name => {
if (name !== cacheStorageKey) {
return caches.delete(name)
}
})
})
).then(() => {
return self.clients.claim()
})
)
})

在新安装的 Service Worker 中通过调用 self.clients.claim() 取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面之后会被停止。

查看 Demo

执行命令:

http-server -c-1 # 注意设置关闭缓存, 这里用参数 -c-1
# 用另一个终端
ngrok http 8080

桌面浏览器可以直接通过 http://localhost:8080 访问, 从 DevTools 的 Application 标签可以看到 Service Worker。

由于 Service Worker 限制了使用 HTTPS 地址或者 localhost 地址, 在 Android Chrome 打开需要借助 ngrok 生成的 HTTPS 地址, 这样才能把 demo 添加到首屏。添加到首屏之后, 即便在离线状态下, 页面也可以打开。

从 DevTools 可以看到, 普通页面刷新时, 列表当中的静态资源都是从 Service Worker 获取的:

更新页面

页面被缓存之后, 就需要适当处理缓存失效时页面的更新。在这个 Demo 当中, 被缓存的资源是无法发起请求判断是否被更新的, 只有 sw.js 会自动根据 HTTP 缓存的机制尝试去判断应用是否被更新。

所以当页面发生修改时, 要同时对 sw.js 文件进行一次修改。比如在 HTML 当中更新版本到 2:

<h3>Revision 2</h3>

同时 sw.js 文件当中也要进行一次修改, 保证文件发生改变, 同时缓存的名称也变改变了:

var cacheStorageKey = 'minimal-pwa-2'

然后重新打开一次页面, 这个时候渲染的页面依然是旧的, 不过可以从 DevTools 看到 sw.js 被安装和激活。之后关闭页面, 再次打开, 就可以见到网页上的显示版本变成了 2。

注意: Demo 当中如果直接启动 http-server 而不使用 -c-1 关闭缓存, sw.js 可能被缓存住, 导致更新方案失败。这种情况下存在 Caches API 和 HTML caching 两层缓存, 需要进行清理才能完成更新。

更多

你还可以实现一个 App Shell, 可以用 Service Worker 实现后台通知等功能。

参考你的首个 Progressive Web App 了解更加详细的编写 PWA 应用的方式。

来个饿了么的链接给大家体验下 https://h5.ele.me/msite/

PWA 入门: 写个非常简单的 PWA 页面的更多相关文章

  1. PWA(Progressive Web App)入门系列:(一)PWA简单介绍

    前言 PWA做为一门Google推出的WEB端的新技术,长处不言而喻.但眼下对于相关方面的知识不是非常丰富.这里我推出一下这方面的新手教程系列.提供PWA方面学习. 什么是PWA PWA全称Progr ...

  2. PWA入门:手把手教你制作一个PWA应用

    摘要: PWA图文教程 原文:PWA入门:手把手教你制作一个PWA应用 作者:MudOnTire Fundebug经授权转载,版权归原作者所有. 简介 Web前端的同学是否想过学习app开发,以弥补自 ...

  3. UWP入门(一) -- 先写几个简单控件简单熟悉下(别看这个)

    原文:UWP入门(一) -- 先写几个简单控件简单熟悉下(别看这个) 1. MainPage.xmal <Grid Background="{ThemeResource Applica ...

  4. Axis2 webservice入门--写个简单的webservice

    上一篇介绍了webservice开发前的准备.下面开始写webservice.如果不了解axis2请看上一篇,如果是新手:建议一边看一边写代码,自己动手完成这个过程. 一.新建一个web项目 二.新建 ...

  5. Hadoop基础-MapReduce入门篇之编写简单的Wordcount测试代码

    Hadoop基础-MapReduce入门篇之编写简单的Wordcount测试代码 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本文主要是记录一写我在学习MapReduce时的一些 ...

  6. 用qpython3写一个最简单的发送短信的程序

    到目前为止并没有多少手机应用是用python开发的,不过qpython可以作为一个不错的玩具推荐给大家来玩. 写一个最简单的发送短信的程序,代码如下: #-*-coding:utf8;-*- #qpy ...

  7. 只是一个用EF写的一个简单的分页方法而已

    只是一个用EF写的一个简单的分页方法而已 慢慢的写吧.比如,第一步,先把所有数据查询出来吧. //第一步. public IQueryable<UserInfo> LoadPagesFor ...

  8. 【spring】-- 手写一个最简单的IOC框架

    1.什么是springIOC IOC就是把每一个bean(实体类)与bean(实体了)之间的关系交给第三方容器进行管理. 如果我们手写一个最最简单的IOC,最终效果是怎样呢? xml配置: <b ...

  9. 利用HttpClient写的一个简单页面获取

    之前就听说过利用网络爬虫来获取页面,感觉还挺有意思的,要是能进行一下偏好搜索岂不是可以满足一下窥探欲. 后来从一本书上看到用HttpClient来爬取页面,虽然也有源码,但是也没说用的HttpClie ...

随机推荐

  1. Javascript常用语法 (一)

    判断成员是否是一个函数: if (typeof options.sourceMapName === 'function') { mapNameGenerator = options.sourceMap ...

  2. BAT,你好!字幕组,再见!——也许要跟美剧说再见了~

    对于美剧迷来说,上周的星期六(2014 年11 月 22 日)是黑色的一天. 11 月 22 日,人人影视暂时关站,并发布公告称网站正在清理内容.虽然这不是人人影视第一次关站清理,而且人人影视还给出提 ...

  3. LNMP(Linux+Nginx+MySQL+PHP)centos6.4安装

    nginx命令 停止nginx服务:# /etc/init.d/nginx stop 启动nginx服务:# /etc/init.d/nginx start 编辑nginx配置文件:# vim /et ...

  4. Mybatis源码分析之SqlSession和Excutor(二)

    通过上一篇文章的分析我们,我初步了解了它是如何创建sessionFactory的(地址:Mybatis源码分析之SqlSessionFactory(一)), 今天我们分析下Mybatis如何创建Sql ...

  5. C#.NET常见问题(FAQ)-如何让文本框textbox内容靠右显示

    对于TextBox,我可以设置Text-Align属性为right,就可以让文字靠右了   对于Label而言,需要修改AutoSize为False,并修改TextAlign为MiddleRight, ...

  6. android中使用通知功能

    本文实现一个功能:点击一个按钮,发送一个系统通知功能 添加一个Activity activity_main.xml: <?xml version="1.0" encoding ...

  7. C/C++中的值传递,引用传递,指针传递,指针引用传递

    在面试过程中,被面试官问到传值和传引用的区别,之前没有关注过这个问题,今天在网上找了一篇包含代码和图片的讲解文章,浅显易懂,遂转载备忘. 1. 值传递 void f( int p){ printf(& ...

  8. google protocol buffer 简介 版本 安装 使用 实例

    一.简介 protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台.google 提供了三种语言的实现:java.c++ 和 python,每一种实现 ...

  9. 获取请求真实ip

    /** * Copyright (c) 2011-2014, James Zhan 詹波 (jfinal@126.com). * * Licensed under the Apache License ...

  10. Gradle基本使用(1):安装、IDEA使用

    安装配置Gradle Gradle 二进制版下载地址: https://gradle.org/install/ 下载后解压,并将 GRADLE_HOME/bin  添加到系统 path变量,即可:   ...