pwa介绍

渐进式网络应用程式(英语:Progressive Web Apps,简称:PWA)是一种普通网页或网站架构起来的网络应用程式,但它可以以传统应用程式或原生移动应用程式形式展示给用户。这种应用程式形态视图将目前最为现代化的浏览器提供的功能与行动装置的体验优势相结合。

当你的网站实现了 PWA,那就代表了

  • 用户可以添加你的博客到电脑 / 手机的桌面,以原生应用般的方式浏览你的博客
  • 用户本地可以自动生成缓存,二次访问速度大大加快
  • 用户可以离线浏览你的博客

下面的 PWA 实现方法借助了 Gulp 插件,在站点有内容更新时,可以弹窗提醒用户刷新页面。

背景

本文使用butterfly主题作为演示

虽然官方对pwa做了很好的集成,但是文档中介绍的并不全面,而且还有许多坑需要自己踩一遍,特此记录下

配置

本文使用Gulp 和 WorkBox组合进行配置pwa

  • 克隆现成的butterfly demo项目,默认集成了hexo-offlinegulp

https://github.com/jerryc127/butterfly.js.org.git

  • 去除hexo-offline,安装workbox-build
1
2
npm uninstall hexo-offline
npm install workbox-build --save-dev
  • pwa配置文件属性详解:
1
2
https://juejin.cn/post/6844903543615258632
https://developer.mozilla.org/en-US/docs/Web/Manifest

开启主题相关设置

1
2
3
4
5
6
7
8
9
10
11
12
# PWA
# See https://github.com/JLHwung/hexo-offline
# 参考: https://cloud.tencent.com/developer/article/1834030
# ---------------
pwa:
enable: true
manifest: /wang-xiaowu/pwa/site.webmanifest
theme_color: "#fff"
apple_touch_icon: /wang-xiaowu/pwa/apple-touch-icon.png
favicon_32_32: /wang-xiaowu/pwa/favicon-32x32.png
favicon_16_16: /wang-xiaowu/pwa/favicon-16x16.png
mask_icon: /wang-xiaowu/pwa/safari-pinned-tab.svg

所需文件,图片等资源可在此网站进行生成。Favicon Generator for perfect icons on all browsers (realfavicongenerator.net)

Favicon Generator for perfect icons on all browsers 图文教程

  • 进入网站
  • 选择图片
  • 创建所有图标
  • 调整 Windows 磁贴图标配色
  • 设置图片相对于 source 目录的存放路径
  • 设置 Web App 名称
  • 生成 README.md(此步可以不选,如何选了记得不要把它放在博客下面,避免生成html,可能会报错)
  • 选择生成

项目改造

下载资源包,并放在上面配置的source目录下,我配置的是/wang-xiaowu/pwa/, 这里不会生成512图标,但是pwa建议需要一个,所以可以自己改个512的图标放里面,并配置site.webmanifest

关于manifest,还有两个地方需要配置

start_url和scope的解释见 https://juejin.cn/post/6844903543615258632

改造gulpfile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const workbox = require("workbox-build");
gulp.task('generate-service-worker', () => {
return workbox.injectManifest({
swSrc: './sw-template.js',
swDest: './public/sw.js',
globDirectory: './public',
globPatterns: [
"**/*.{html,css,js,json,woff2}"
],
modifyURLPrefix: {
"": "./"
}
});
});
// 執行 gulp 命令時執行的任務
gulp.task('default', gulp.parallel(
'compress', 'minify-css', 'minify-html', 'generate-service-worker'
))

创建 sw-template.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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
const workboxVersion = '5.1.3';

importScripts(`https://storage.googleapis.com/workbox-cdn/releases/${workboxVersion}/workbox-sw.js`);

workbox.core.setCacheNameDetails({
prefix: "Serok's Blog"
});

workbox.core.skipWaiting();

workbox.core.clientsClaim();

workbox.precaching.precacheAndRoute(self.__WB_MANIFEST,{
directoryIndex: null
});

workbox.precaching.cleanupOutdatedCaches();

// Images
workbox.routing.registerRoute(
/\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico)$/,
new workbox.strategies.CacheFirst({
cacheName: "images",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);

// Fonts
workbox.routing.registerRoute(
/\.(?:eot|ttf|woff|woff2)$/,
new workbox.strategies.CacheFirst({
cacheName: "fonts",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);

// Google Fonts
workbox.routing.registerRoute(
/^https:\/\/fonts\.googleapis\.com/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: "google-fonts-stylesheets"
})
);
workbox.routing.registerRoute(
/^https:\/\/fonts\.gstatic\.com/,
new workbox.strategies.CacheFirst({
cacheName: 'google-fonts-webfonts',
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);

// Static Libraries
workbox.routing.registerRoute(
/^https:\/\/cdn\.jsdelivr\.net/,
new workbox.strategies.CacheFirst({
cacheName: "static-libs",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);

workbox.googleAnalytics.initialize();

注意:把 prefix 修改为你博客的名字(最好用英文)。

添加 js 进主题

1
2
3
4
5
inject:
head:
- '<style type="text/css">.app-refresh{position:fixed;top:-2.2rem;left:0;right:0;z-index:99999;padding:0 1rem;font-size:15px;height:2.2rem;transition:all .3s ease}.app-refresh-wrap{display:flex;color:#fff;height:100%;align-items:center;justify-content:center}.app-refresh-wrap a{color:#fff;text-decoration:underline;cursor:pointer}</style>'
bottom:
- '<div class="app-refresh" id="app-refresh"> <div class="app-refresh-wrap"> <label>✨ 网站已更新最新版本 👉</label> <a href="javascript:void(0)" onclick="location.reload()">点击刷新</a> </div></div><script>function showNotification(){if(GLOBAL_CONFIG.Snackbar){var t="light"===document.documentElement.getAttribute("data-theme")?GLOBAL_CONFIG.Snackbar.bgLight:GLOBAL_CONFIG.Snackbar.bgDark,e=GLOBAL_CONFIG.Snackbar.position;Snackbar.show({text:"已更新最新版本",backgroundColor:t,duration:5e5,pos:e,actionText:"点击刷新",actionTextColor:"#fff",onActionClick:function(t){location.reload()}})}else{var o=`top: 0; background: ${"light"===document.documentElement.getAttribute("data-theme")?"#49b1f5":"#1f1f1f"};`;document.getElementById("app-refresh").style.cssText=o}}"serviceWorker"in navigator&&(navigator.serviceWorker.controller&&navigator.serviceWorker.addEventListener("controllerchange",function(){showNotification()}),window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.js")}));</script>'

运行查看效果

1
hexo clean && hexo g && gulp && hexo d

而且手机或者电脑浏览器浏览栏也会有提示你安装应用

问题

浏览器出现Manifest: found icon with no valid size的警告或者错误

参考: https://sharechiwai.com/post/2020-05-05-manifest-found-icon-with-no-valid-size/

原因: 要求最小的 size 是 192 x 192

解決方法 : 移除manifest.json文件icons列表中 比 192 x 192 小的对象