本文以 Butterfly 主题为范例。
使用这个方法之前,请先卸载掉其它的 PWA 插件,并安装 Gulp 和 WorkBox。
1 2
| npm install gulp-cli -g npm install workbox-build gulp --save-dev
|
本文参考利用 Workbox 实现博客的 PWA和Butterfly进阶教程。
写在前面
渐进式网络应用程式(英语:Progressive Web Apps,简称:PWA)是一种普通网页或网站架构起来的网络应用程式,但它可以以传统应用程式或原生移动应用程式形式展示给用户。这种应用程式形态视图将目前最为现代化的浏览器提供的功能与行动装置的体验优势相结合。
当你的网站实现了 PWA,那就代表了
- 用户可以添加你的博客到电脑/手机的桌面,以原生应用般的方式浏览你的博客
- 用户本地可以自动生成缓存,二次访问速度大大加快
- 用户可以离线浏览你的博客
下面的 PWA实现方法借助了 Gulp插件,在站点有内容更新时,可以弹窗提醒用户刷新页面。
开启主题相关设置
以 Butterfly 主题为例,在 butterfly.yml
中开启 PWA 选项
实例:
1 2 3 4 5 6 7 8
| pwa: enable: true manifest: /manifest.json theme_color: "#fff" apple_touch_icon: /img/pwa/apple-touch-icon.png favicon_32_32: /img/pwa/32.png favicon_16_16: /img/pwa/16.png mask_icon: /img/pwa/safari-pinned-tab.svg
|
图片尺寸要求:
- apple_touch_icon:192 * 192 像素
- mask_icon:viewBox的值必须是 0 0 16 16
如果你的主题没有内置PWA,下面附有Butterfly主题的PWA部分,可以根据你所用的主题调整下面的Pug模板,编译后插入到html生成模板的head处。
1 2 3 4 5 6 7 8 9 10 11 12 13
| link(rel="manifest" href=url_for(theme.pwa.manifest)) if(theme.pwa.theme_color) meta(name="theme-color" content=theme.pwa.theme_color) if(theme.pwa.theme_color) meta(name="msapplication-TileColor" content=theme.pwa.theme_color) if(theme.pwa.apple_touch_icon) link(rel="apple-touch-icon" sizes="180x180" href=url_for(theme.pwa.apple_touch_icon)) if(theme.pwa.favicon_32_32) link(rel="icon" type="image/png" sizes="32x32" href=url_for(theme.pwa.favicon_32_32)) if(theme.pwa.favicon_16_16) link(rel="icon" type="image/png" sizes="16x16" href=url_for(theme.pwa.favicon_16_16)) if(theme.pwa.mask_icon) link(rel="mask-icon" href=url_for(theme.pwa.mask_icon) color="#5bbad5")
|
配置 manifest.json
创建 manifest.json
,路径应与配置文件中所填路径相同。
实例及配置说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| { "name": "Serok's Blog", "short_name": "Seeker", "theme_color": "#49b1f5", "background_color": "#49b1f5", "display": "minimal-ui", "scope": "/", "start_url": "/", "icons": [ { "src": "https://snow.js.org/image/pwaicons/192.png", "sizes": "192x192", "type": "image/png" } ], "splash_pages": null }
|
default
Hexo采用的是严格 Json规范,因此 manifest.json
文件配置好后,需删除文件中的所有注释。
安装插件
在命令行中输入
1
| npm install workbox-build gulp --save-dev
|
创建 gulpfile.js
文件
在博客的根目录下,创建一个 gulpfile.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const gulp = require("gulp"); 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.task("build", gulp.series("generate-service-worker"));
|
创建 sw-template.js
文件
在博客的根目录下,创建一个 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();
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] }) ] }) );
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] }) ] }) );
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] }) ] }) );
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 修改为你博客的名字(最好用英文)。
上面的文件涵盖了大多数资源的缓存策略。如果你想缓存其他类型的资源(例如一些国内的镜像 CDN 库),或者想使用其他的缓存方式,请自行查看相关文档并添加。
添加 js 进主题
配置 butterfly.yml
, 添加需要的 css 和 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>'
|
同样,如果你使用的不是Butterfly主题,可以在所示代码的基础上修改以适配你的主题。以下是展开后的代码,便于修改调试。
以下代码请插入到头部 </head>
之前:
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
| <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 0.3s ease; } .app-refresh-wrap { display: flex; color: #fff; height: 100%; align-items: center; justify-content: center; }
.app-refresh-wrap span { color: #fff; text-decoration: underline; cursor: pointer; } </style>
|
以下代码请插入到底部 </body>
之前:
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
| <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> if ('serviceWorker' in navigator) { if (navigator.serviceWorker.controller) { navigator.serviceWorker.addEventListener('controllerchange', function () { showNotification() }) }
window.addEventListener('load', function () { navigator.serviceWorker.register('/sw.js') }) }
function showNotification() { if (GLOBAL_CONFIG.Snackbar) { var snackbarBg = document.documentElement.getAttribute('data-theme') === 'light' ? GLOBAL_CONFIG.Snackbar.bgLight : GLOBAL_CONFIG.Snackbar.bgDark var snackbarPos = GLOBAL_CONFIG.Snackbar.position Snackbar.show({ text: '已更新最新版本', backgroundColor: snackbarBg, duration: 500000, pos: snackbarPos, actionText: '点击刷新', actionTextColor: '#fff', onActionClick: function (e) { location.reload() }, }) } else { var showBg = document.documentElement.getAttribute('data-theme') === 'light' ? '#49b1f5' : '#1f1f1f' var cssText = `top: 0; background: ${showBg};` document.getElementById('app-refresh').style.cssText = cssText } } </script>
|
运行
为了方便每次部署,可以在你的博客根目录下新建 deploy.sh
,添加以下内容:
1 2 3 4 5 6 7 8 9
| #!/bin/sh echo "Start" hexo clean hexo generate gulp hexo deploy echo "Finish" echo 按任意键继续 read -n 1
|
然后每次部署就可以直接运行这个脚本了。