PWA 是什么?

PWA[1] 全称 Progressive Web App,即渐进式 WEB 应用。

  • 可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏;
  • 实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能;
  • 实现了消息推送。

这篇教程主要针对前两个功能。

本博客曾经采用 WordPress 搭建,故这里一并阐述如何使 WordPress 下的网站支持 PWA。

WordPress 下使网站支持 PWA

Manifest 添加至主屏幕

在主题根目录 vi header.php,在 <head></head> 之间添加:

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

在站点根目录 vi manifest.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "应用名",
"short_name": "应用短名",
"description": "应用描述",
"display": "standalone",
"start_url": "/",
"theme_color": "Hex 主题色",
"background_color": "Hex 背景色",
"icons": [
{
"src": "icon/appicon.png",
"sizes": "512x512"
}
]
}
  • "name": 必填,显示应用名称;
  • "short_name": 可选,在 APP Launcher 和新标签页显示。如果没有设置,则使用 "name": 的值;
  • "background_color": 在启动 WEB 应用程序和加载应用程序的内容之间创建了一个平滑的过渡。

mkdir iconcd icon,放入文件应用图标 appicon.png。

这时由于没有启用 Service Worker,Manifest 也没有作用。下面将帮助你启用 Service Worker。

在主题根目录 vi footer.php,在 <footer></footer> 之间添加:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
// 注册 service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) {
// 注册成功
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function (err) {
// 注册失败 :(
console.log('ServiceWorker registration failed: ', err);
});
}
</script>

在站点根目录 vi service-worker.js

1
2
3
4
5
6
7
8
9
10
11
12
13
self.addEventListener('install', event => {
console.log('install',event);
event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', event => {
console.log('activate',event)
event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', event => {
console.log('fetch',event)
});

完成。去浏览器清除缓存,你试试地址栏右边是不是多了个安装应用的按钮呢?

你也可以浏览器里按 f12 打开开发者工具,到 Application 栏进行调试。如果用的安卓手机,推荐 Kiwi Browser,菜单里也有开发者工具。

Service Worker 缓存

Service Worker 高级在于它能离线缓存和动态缓存。

vi service-worker.js # 上次编辑的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 版本修改的时候会触发 activate,将旧版本的缓存清理掉。
var OFFLINE_PREFIX = "offline-";
var CACHE_NAME = "main_v1.0.0";

var cacheName = "helloWorld"; // 缓存的名称
// install 事件,它发生在浏览器安装并注册 Service Worker 时。
self.addEventListener("install", (event) => {
/* event.waitUtil 用于在安装成功之前执行一些预装逻辑
但是建议只做一些轻量级和非常重要资源的缓存,减少安装失败的概率
安装成功后 ServiceWorker 状态会从 installing 变为 installed */
event.waitUntil(
caches.open(cacheName).then((cache) =>
cache.addAll([
// 如果所有的文件都成功缓存了,便会安装完成。如果任何文件下载失败了,那么安装过程也会随之失败。
"/index.php",
])
)
);
console.log("install", event);
event.waitUntil(self.skipWaiting());
});

/**
为 fetch 事件添加一个事件监听器。接下来,使用 caches.match() 函数来检查传入的请求 URL 是否匹配当前缓存中存在的任何内容。如果存在的话,返回缓存的资源。
如果资源并不存在于缓存当中,通过网络来获取资源,并将获取到的资源添加到缓存中。
*/
self.addEventListener("fetch", function (event) {
console.log("fetch", event);
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
return response;
}
var requestToCache = event.request.clone();
return fetch(requestToCache).then(function (response) {
if (!response || response.status !== 200) {
return response;
}
var responseToCache = response.clone();
caches.open(cacheName).then(function (cache) {
cache.put(requestToCache, responseToCache);
});
return response;
});
})
);
});

self.addEventListener("activate", (event) => {
console.log("activate", event);
event.waitUntil(self.clients.claim());
var mainCache = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (
mainCache.indexOf(cacheName) === -1 &&
cacheName.indexOf(OFFLINE_PREFIX) === -1
) {
// When it doesn't match any condition, delete it.
console.info("SW: deleting " + cacheName);
return caches.delete(cacheName);
}
})
);
})
);
return self.clients.claim();
});

至此,WordPress 下的 PWA 已经启用完毕。剩下唯一可能要自定义的,是上方的第 15 行。

2023-05-23 更新:

如果出现 POST 方法不能 Fetch 的错误,请将上方的 Fetch 事件代码改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
self.addEventListener("fetch", function (event) {
console.log("fetch", event);
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
return response;
}

var requestToCache = event.request.clone();

// 只缓存GET请求的响应
if (event.request.method === "GET") {
return fetch(requestToCache).then(function (response) {
if (!response || response.status !== 200) {
return response;
}

var responseToCache = response.clone();
caches.open(cacheName).then(function (cache) {
cache.put(requestToCache, responseToCache);
});

return response;
});
}

// 对于其他请求方法,直接通过网络获取响应
return fetch(requestToCache);
})
);
});

hexo-theme-butterfly 下使网站支持 PWA

要为 hexo-theme-butterfly 配置 PWA,你需要如下步骤:

  1. 打开 hexo 工作目录;
  2. 运行 npm install hexo-offline --save 或者 yarn add hexo-offline[2]
  3. 在根目录 vi hexo-offline.config.cjs,并增加以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// offline config passed to workbox-build.
module.exports = {
globPatterns: ['**/*.{js,html,css,png,jpg,gif,svg,webp,eot,ttf,woff,woff2}'],
// 静态文件合集,如果你的站点使用了例如 webp 格式的文件,请将文件类型添加进去。
globDirectory: 'public',
swDest: 'public/service-worker.js',
maximumFileSizeToCacheInBytes: 10485760, // 缓存的最大文件大小,以字节为单位。
skipWaiting: true,
clientsClaim: true,
// runtimeCaching: [ // 如果你需要加载 CDN 资源,请配置该选项,如果没有,可以不配置。
// // CDNs - should be CacheFirst, since they should be used specific versions so should not change
// {
// urlPattern: /^https:\/\/cdn\.example\.com\/.*/, // 可替换成你的 URL
// handler: 'CacheFirst'
// }
// ],
manifestTransforms: [removeIndex]
}

/* 移除 URL 末尾的 index.html */
async function removeIndex(manifestEntries) {
const manifest = manifestEntries.map(entry => {
entry.url = entry.url.replace(/(^|\/)index\.html$/, '/');
return entry;
});
return { manifest };
}
  1. 由于本博客使用的配置有简化,很多地方有注释,你可以根据自身情况删去注释符号,或者根据官方文档完善你的配置。

  2. vi _config.butterfly.yml

1
2
3
4
5
6
7
pwa:
enable: true
manifest: /pwa/manifest.json
# apple_touch_icon: /pwa/apple-touch-icon.png
# favicon_32_32: /pwa/32.png
# favicon_16_16: /pwa/16.png
# mask_icon: /pwa/safari-pinned-tab.svg
  1. cd sourcemkdir pwacd pwavi manifest.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "应用名",
"short_name": "应用简称",
"description": "应用描述",
"theme_color": "Hex 主题色",
"background_color": "Hex 背景色",
"display": "standalone",
"scope": "/",
"start_url": "/",
"id": "/",
"icons": [{
"src": "/pwa/512.png",
"sizes": "512x512",
"type": "image/png"
}]
}

注意,manifest.json 与 Hexo 插件 hexo-tag-hint 有冲突(原因是 hexo-tag-hint 会在所有文件头部引用相应 css),要使用 PWA,务必先 npm remove hexo-tag-hint

另外,此处的 manifest.json 与 WordPress 的 manifest.json 略有差别,因为框架不同,我写它的时间不同。

随后,把应用图标 512.png 放入 pwa 文件夹里。

你也可以通过 Web App Manifest 快速创建 manifest.json(Web App Manifest 要求至少包含一个 512*512 像素的图标)。

  1. f12 调试,或者安装 Chrome 插件 Lighthouse 进行检查。

至此,hexo-theme-butterfly 下的 PWA 已经启用完毕。


  1. Google Tools for Web Developers ↩︎

  2. 更多内容请查看 hexo-offline 的官方文档。 ↩︎