本文仅适用于 hexo 主题: Butterfly , 版本: 4.3.0

前言

本篇文章用于记录关于本博客 Butterfly 主题的所有美化/配置,后续如有新增变更会持续更新

CDN - 静态资源加速

为什么需要改 CDN ?

  1. 国内因复杂的网络环境 ,框架默认使用的 cdn 服务位于国外,有时访问不了
  2. 加速资源加载 ,减少用户等待资源加载的时间

我们可以使用来自 官网 [1]提供的配置模板:

供应商 格式 备注
Staticfile(七牛云) https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file} 同步 cdnjs
BootCDN https://cdn.bootcdn.net/ajax/libs/${cdnjs_name}/${version}/${min_cdnjs_file} 同步 cdnjs
Baomitu(360) 最新版本: https://lib.baomitu.com/${cdnjs_name}/latest/${min_cdnjs_file} 同步 cdnjs
指定版本: https://lib.baomitu.com/${cdnjs_name}/${version}/${min_cdnjs_file}
Elemecdn 最新版本: https://npm.elemecdn.com/${name}@latest/${file} 同步 npm
指定版本: https://npm.elemecdn.com/${name}@${version}/${file}

此处我们使用 七牛云 用于博客加速:
需要注意的是我们需要修改 fancybox_css_v4fancybox_v4 的链接指向,因为其默认格式不正确

CDN:
internal_provider: custom
third_party_provider: custom
custom_format: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file}
version: false
option:
fancybox_css_v4: https://cdn.staticfile.org/fancyapps-ui/4.0.18/fancybox.min.css
fancybox_v4: https://cdn.staticfile.org/fancyapps-ui/4.0.18/fancybox.umd.min.js

PWA - 离线化浏览

为什么需要 PWA ?

  1. 访问不受网络影响 :比如打开了本篇文章,不妨试试断开网络后刷新当前页面,你会发现页面依然可以展示
  2. 加速资源加载访问 :我们可以选择策略,当访问资源时,存在缓存则返回,再去发起网络请求资源更新缓存,因博客变化变更周期不大,因此资源文件/文章可长期缓存,不过缺点是发生变更后,需要多刷新一次

PWA 启用需要什么?[2]

  1. 网站为 https 或者 127.0.0.1(本地)
  2. manifest.json 网页清单
  3. 一个在设备上代表应用的图标
  4. 注册 Service Worker

我们可以使用 googleworkbox 使博客支持 PWA 便于 离线化访问
相关 workbox 策略参考官网 [3],支持的策略有:

策略名 流程图 描述

StaleWhileRevalidate

StaleWhileRevalidate

  1. 页面发起的请求被 SW 代理
  2. SW 查看缓存是否存在,若存在则转 3,若不存在则转 4
  3. SW 获取缓存内容返回请求,转 4
  4. SW 向网络发起请求,若返回的内容合法则更新缓存

CacheFirst

CacheFirst

  1. 页面发起的请求被 SW 代理
  2. SW 查看缓存是否存在,若存在则直接返回,若不存在则转 3
  3. SW 向网络发起请求,若返回合法则更新缓存,并返回

CacheOnly

CacheFirst

  1. 页面发起的请求被 SW 代理
  2. SW 查看缓存是否存在,若存在则直接返回,若不存在则返回失败

NetworkFirst

NetworkFirst

  1. 页面发起的请求被 SW 代理
  2. SW 向网络发起请求,若返回合法则缓存,并返回,若请求失败,则转 3
  3. SW 从缓存中获取,并返回

NetworkOnly

NetworkFirst

  1. 页面发起的请求被 SW 代理
  2. SW 向网络发起请求,若返回合法则缓存,并返回,若请求失败则返回失败

我选用 StaleWhileRevalidate 策略,因为它既 不会更新太慢 ,也 不会影响加载速度 ,同时还兼顾了 离线化阅读 的需求

此处部分配置参考了这篇 文章[4],并加以改进

为此,我们还需要以下几个步骤以启用 PWA :

  1. 配置 PWAmanifest.json
  2. 配置生成 workboxsw.js 文件,配置 service worker 的策略文件
  3. 启用 butterfly 的 pwa 配置,配置 sw.js 注册

1) 配置 PWAmanifest.json

为什么要配置 manifest.json ?

  1. PWA 要求的
  2. manifest.json 里包含提供了一些信息(如名称,作者,图标和描述),便于 Web 应用程序安装到设备的主屏幕时所展示

具体 manifest.json 要求请参考 官网 [5]
我们需要各个尺寸图标,用于代表应用的图标,将自己喜爱的图片裁剪成所需尺寸,放置于 source/images/pwa 下,再按照如下样例所配
本博客的 manifest.json [6] 配置如下所示(该文件放置于 source/manifest.json):

{
"name": "报时's Blog",
"short_name": "Boss Blog",
"theme_color": "#49b1f5",
"background_color": "#49b1f5",
"display": "standalone",
"scope": "/",
"start_url": "/",
"id": "/",
"icons": [
{
"src": "/images/pwa/36.png",
"sizes": "36x36",
"type": "image/png"
},
{
"src": "/images/pwa/48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "/images/pwa/96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/images/pwa/144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/images/pwa/192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/images/pwa/512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"splash_pages": null
}

2) 配置生成 workboxsw.js 文件,配置 service worker 的策略文件

安装 workbox:

yarn add workbox-build -D

添加 scripts/events/pwa.js 文件,注册 before_generate 事件,在 hexo 生成文件之前生成 sw.js:

const path = require("path");
const workbox = require("workbox-build");
const fs = require('fs');

hexo.extend.filter.register('before_generate', async () => {
// 删除过期的 workbox 文件
fs.existsSync(hexo.public_dir) && fs.readdirSync(hexo.public_dir).forEach(file => {
if (file.match(/^workbox-[^.]+.js(.map)?$/)) {
fs.unlinkSync(path.join(hexo.public_dir, file));
}
});
// 根据配置的策略生成 sw.js
let offline = require(path.join(hexo.base_dir, '/hexo-offline.config.cjs'));
await workbox.generateSW(offline);
}
)

添加 hexo-offline.config.cjs 策略配置文件,其配置文件格式参考官网 [7]

// offline config passed to workbox-build.
module.exports = {
globPatterns: [],
// 静态文件合集,如果你的站点使用了例如 webp 格式的文件,请将文件类型添加进去。
globDirectory: 'public/',
swDest: 'public/sw.js',
maximumFileSizeToCacheInBytes: 20971520, // 缓存的最大文件大小,以字节为单位。
skipWaiting: true,
clientsClaim: true,
cleanupOutdatedCaches: true,
runtimeCaching: [ // 如果你需要加载 CDN 资源,请配置该选项,如果没有,可以不配置。
// CDNs - should be CacheFirst, since they should be used specific versions so should not change
{
urlPattern: /^http(s)?:\/\/hm\.baidu\.com\/.*/,
handler: 'NetworkOnly',
method: 'GET'
},
{
urlPattern: /^http:\/\/127\.0\.0\.1(:[0-9]+)?\/.*/,
handler: 'NetworkOnly',
method: 'GET'
},
{
urlPattern: /.*/,
handler: 'StaleWhileRevalidate',
method: 'GET'
}
]
}

此步过后,当我们执行 hexo g 后,我们会有 public/sw.js 以及 public/workbox-*.js 两个文件,到此 service worker 文件生成完了
接下来我们需要在各个页面去注册执行 sw.js

3) 启用 butterfly 的 pwa 配置,配置 sw.js 注册

编辑主题配置文件 _config.butterfly.yml 启用 pwa,如下:

pwa:
enable: true
manifest: /manifest.json
apple_touch_icon: /pwa/apple-touch-icon.png
favicon_32_32: /images/pwa/32.png
favicon_16_16: /images/pwa/16.png
mask_icon: /pwa/safari-pinned-tab.svg

主题配置文件 _config.butterfly.yml 中配置加入脚本,为 pwa 提供注册以及提醒更新功能,如下:

inject:
head:
- '<link rel="stylesheet" href="/third/pwa/pwa.css">'
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 src="/third/pwa/pwa.js"></script>'

添加提示更新的 css 文件,source/third/pwa/pwa.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
}

添加用于注册 service worker 以及提示 service worker 更新的脚本:

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");
}));

hexo 配置文件 _config.yml 中,排除手动加入的资源文件解析:

skip_render:
- manifest.json
- third/**

scripts/events/pwa.js 处配置 hexogenerate事件拦截 ,生成 sw.js 以及 workbox-*.js,文件路径为 :

const path = require("path");
const workbox = require("workbox-build");
const fs = require('fs');

hexo.extend.filter.register('before_generate', async () => {
// 删除过期的 workbox 文件
fs.existsSync(hexo.public_dir) && fs.readdirSync(hexo.public_dir).forEach(file => {
if (file.match(/^workbox-[^.]+.js(.map)?$/)) {
fs.unlinkSync(path.join(hexo.public_dir, file));
}
});
// 根据配置的策略生成 sw.js
let offline = require(path.join(hexo.base_dir, '/hexo-offline.config.cjs'));
await workbox.generateSW(offline);
}
)

启用更新 提示框 ,并配置提示背景色:

# Snackbar (Toast Notification 弹窗)
# https://github.com/polonel/SnackBar
# position 彈窗位置
# 可選 top-left / top-center / top-right / bottom-left / bottom-center / bottom-right
snackbar:
enable: true
position: top-right
bg_light: '#1f1f1f' # The background color of Toast Notification in light mode
bg_dark: '#49b1f5' # The background color of Toast Notification in dark mode

我们在本地访问生成的页面时,打开 应用程序,见到如下便可视为 成功注册
pwa 启用
service worker 发生变化时 (当 sw.js 字节发生变更),会出现如下图所示的提示:
pwa 更新提示


  1. butterfly 官方 CDN 配置 ↩︎

  2. PWA 安装要求 ↩︎

  3. workbox-strategies 官方 ↩︎

  4. hexo-pwa 配置参考 ↩︎

  5. Web App Manifest 标准 ↩︎

  6. 本博 manifest.json ↩︎

  7. workbox 配置格式 ↩︎