请注意:本教程基于hexo-swpp@2.8+
编写,不适用于 V3,V3 文档参考下面的链接:
请注意:如果版本号a.b.c
中的a
或b
发生变化,请务必重新阅读一遍文档!!!
请务必使用最新的稳定版本!旧版本很可能包含某些功能漏洞!!!
若版本号后方带有beta.*
字样,请勿使用!
更新日志
每行最后的方括号中的值表示该条更新所在的具体版本号,括号中的值为快速跳转链接,若链接以#
开头则是 github issue 链接。
2.8+
功能性更新:
- 支持通过
ejectValues
向 sw 插入值(快速跳转) - 允许修改 rules 文件的名称(不建议修改)
- rules 文件的拓展名可以为 cjs 或 js 两者其一
BUG 修复:
- DOM 插入
onsuccess
时错误的执行了代码【2.8.4】 - 缓存的 key 协议名称后缺少冒号【2.8.7】
- ServiceWorker 中匹配 HTML 的正则表达式存在缺陷【2.8.8】
- 生成 ServiceWorker 时报错【2.8.9】
- 当 eject 导出的字符串中包含单引号时无法正确导出【2.8.10】
- 当 escape 设置为 0 时逃生门未禁用【2.8.11】(严重漏洞!)
细微功能调整:
- 缓存匹配时统一 URL 风格(快速跳转)【2.8.5】
cacheList
中支持通过search
区分不同的 URL【2.8.6】- 缓存的 key 不再区分 http 与 https 协议【2.8.6】
- 遇到加载错误将直接中断 hexo 构建而非跳过 swpp 的执行【2.8.8】
2.6+
2.5+
功能性更新:
update.json
添加flag
字段。(快速跳转)
BUG 修复:
- 构建时报错【2.5.1】
- 当 css 中存在任意属性的值包含
url
且没有后跟()
时构建报错【2.5.2】
细微功能调整:
- 不再相应非 http 请求(比如浏览器拓展)【2.5.3】
- 支持缓存 301、302、307、308 重定向的相应【2.5.3】
older
之前没有写更新日志,省略不写。
介绍
该插件用于在 hexo 中自动构建可用的 ServiceWorker,插件内自带了一个缺省的 sw.js,也支持使用自定义的 sw。
本插件的特性:
- 本地缓存(增量更新)
- Request 篡改
- CDN 竞速
- 备用 URL
- 208 阻塞响应
- 兼容 MoOx-Pjax
- 功能强大,高度自由
我的另一篇博文已经介绍了这个 SW 的工作原理,具体介绍不再赘述,详情见:
如果你的网站使用了 CDN 且启用了 CDN 端缓存,请务必将 CDN 缓存时间调整至最大值,然后每次更新网页内容后手动刷新 CDN 缓存。
因为本插件的更新方案要求update.json
更新时,其它所有需要更新的资源均已更新,否则客户端拉取时会误以为拉取到了最新的内容,从而导致部分资源“错过”更新。
简而言之,就是update.json
必须与需要缓存的资源共享同样的 CDN 缓存周期。但是目前市面上我知道的 CDN 无法做到这一点,所以只能从下列选项中二选一
- 把所有资源的 CDN 缓存时间拉满,每次更新网站时刷新 CDN 缓存
- CDN 不缓存所有需要在客户端缓存的资源
Netlify 构建后自动刷新 CDN 缓存的教程见:《全自动博客部署方案》
请务必注意 CDN 缓存的问题!!!
详细的更新日志见:GitHub Commits
如果文档中有不懂的地方,记得在评论区中提出你的问题,不要怕问题太简单哟~~
教程
首先需要安装插件(参数使用--save
或--save-dev
均可):
1 |
|
然后添加配置项(在 hexo 配置或主题配置中添加均可):
1 |
|
接着还需要在博客根目录创建一个 JS 文件——sw-rules.cjs
(拓展名为 cjs 或 js 均可,若两个不同拓展名的文件同时存在优先使用 cjs,主题开发者可在主题根目录下创建该文件)。
sw-rules.cjs
必须要创建,且其中必须包含config
和cacheList
,就算对象内容都是空的也要写上,不然无法构建!
配置项
config
是插件的配置项,插件的所有功能的配置均在该项中进行修改,以下是插件支持的所有配置(给出值的为插件的缺省配置,必填项和特殊情况在注释中说明):
温馨提示:不要被这么长的配置吓到,其中绝大多数配置都是非必填项,看不懂的功能不启用即可。
1 |
|
1 |
|
缓存规则
cacheList
用于确定运行时哪些 URL 应当被缓存哪些 URL 不缓存,此项的代码会被复制到 sw.js 中,应当尽量保持代码简洁。声明该对象时按照如下格式声明:
1 |
|
注意!所有匹配缓存的 URL 都将进行一次格式化,所有以/index.html
结尾的 URL 都将被替换为/
,比如:
https://kmar.top/index.html
->https://kmar.top/
/abc/def/index.html
->/abc/def/
/abc/def.html
->abc/def.html
如果你只是想快速预览一下 sw,可以使用如下的代码(未列出的项目均可不填):
1 |
|
插件会在生成网站的过程中自动生成 sw.js 和 sw-dom.js 并插入注册代码,所以本地预览时支持对 sw 和 sw-dom.js 进行调试。而 json 文件则需要通过hexo swpp
指令手动生成,在执行hexo g && hexo swpp
后启用本地预览时 sw.js 可以访问到update.json
,如果没有提前生成发布目录并运行指令,本地预览时浏览器后台报404
错误属于正常现象,无需担心。(一般不建议在本地中启用本地文件的缓存,不方便本地调试。)
请在发布网站及压缩文件前执行hexo swpp
,否则可能会因为压缩导致文件md5
频繁变动。如果你的博客需要使用gulp
一类的脚本替换一部分链接,请在替换链接后执行hexo swpp
,在替换之前执行只能监听到替换前的链接。
请注意:在缓存数据时不会处理hash
和参数,所以如果请求内容会根据 URL 参数不同而不同的话请勿缓存,否则会导致拉取到错误的内容!!!
主题支持
插件支持主题自带 sw-rules.js 文件,将该文件放置在主题根目录下一并上传即可。插件运行时会尝试在/themes/[name]/sw-rules.js
和/node_modules/hexo-theme-[name]/sw-rules.js
两个位置查找 js。
主题中自带 sw-rules.js 后,用户仍然可以在博客根目录创建 js 文件。用户 js 中声明的内容的优先级高于主题 js,若两者中包含同名的 export,将使用用户 js 的内容;若主题 js 中包含了指定内容但用户 js 中没有,将使用主题 js 中的内容。
逃生门
该配置我们可以称之为“逃生门”。这个配置后应填写一个整数(缺省为 0),在 sw 检测到用户没有加载过这个版本时,就会强制刷新用户所有缓存。这个选项主要是提供给想要自定义 DOM 端 JS 的用户使用的,因为缓存的更新是需要 DOM 端发送更新请求到 SW 端的,所以有些时候因为 DOM 端代码有误会导致永远无法触发缓存更新,这时候就可以通过逃生门强制更新缓存。
填写逃生门时,随意填写一个未填写过的正整数即可,不填或填0
表示不启用逃生门。
Request 篡改
如果你需要在发起网络请求前修改请求头的信息,直接在sw-rules.js
中添加modifyRequest
函数即可。
1 |
|
CDN 竞速
CDN 竞速开启后会同时向多个 URL 发起网络请求,使用速度最快的一个 URL 返回的数据。开启该功能后,将增加客户端的网络和 CPU 压力,请酌情开启。
想要启用 CDN 竞速,需要在sw-rules.js
中添加如下语句:
1 |
|
这个函数体只是给出了一个样例,读者可自行编写。
备用 URL
备用 URL 和 CDN 竞速有异曲同工之妙,不过备用 URL 是在上一个链接下载时长超过指定时间后再发起对下一个 URL 的访问。在发起新的访问时不会中断对上一个 URL 的访问,只有在某一个访问成功拉取内容后才会中断对其它 URL 的访问。
如果需要开启此功能,在 sw-rules.js 中添加getSpareUrls
声明即可。开启时,建议将速度最快、受众群体最大的 URL 放在开头,否则会严重影响加载速度。
下面给出一个代码样例:
1 |
|
阻塞响应
有时候,我们可能希望屏蔽一些 URL 的访问,这时候可以通过直接返回 208 来阻塞这些 URL。
注意:当 URL 被屏蔽后,拉取文件的过程中并不会抛出异常,如果拉取文件的代码没有做错误处理,在处理文件内容时可能会报错。
在sw-rules.js
中添加如下代码即可启用阻塞响应功能:
1 |
|
外部文件监听
插件支持监听一下文件内引入的外部文件的更新:
- html 文档中使用
script
和link
标签引入的 URL - css 文件及 html 中内嵌的 css 中使用
url(...)
语法引入的 URL(排除注释) - js 文件中符合预定要求的 URL
当插件尝试监听 URL 时,如果 URL 没有附带通讯协议,会默认使用http
协议而非https
协议。
html 和 css 内 URL 的监听就不再多说,我们重点说 js 文件的监听。从上面配置文件的示例可以看到监听 js 内 URL 的格式如下:
1 |
|
head
和tail
项的值都为字符串,字符串的内容会直接内嵌到正则表达式中,所以如果你使用了一些特殊字符,记得使用反斜线,否则无法正常匹配。
每一对head
和tail
都会被拼接为如下格式的正则表达式:
1 |
|
该正则表达式的作用是匹配被head
和tail
包裹起来的字符串(不忽略注释),同时会向head
后和tail
前添加一个引号(通用匹配三种引号)。
注意,插件不支持拼接字符串,同时如果你使用模板字符串,也不支持使用${}
,如果被检查到字符串中包含三种引号中任意一种或多种或者$
符号,都会被视为非法 URL 跳过监听。
遇到非法 URL 时会自动跳过,不用担心正则表达式匹配到不是 URL 的内容。如果你是 butterfly 主题的用户,可以使用如下配置来捕获使用getScript()
函数引入的文件:
1 |
|
replace
如果部署服务器在国外,但是网站中部分链接只有国内才能访问,可以选择取消对这些 URL 的监控或者使用 replace 配置项将 URL 改为国外可访问的 URL。
替换时会把source
中写出的字符串替换为dist
中的内容,且一次替换完成后不会终止,会一直完成整个列表的匹配。
替换 URL 的代码如下:
1 |
|
手动更新缓存
插件捕获外部文件更新的能力有限,如果部分文件无法使用插件自动捕获更新,则需要在更新时手动添加。
当需要手动向版本文件添加信息时在 hexo 根目录中创建update.json
即可,格式如下:
1 |
|
其中:
flag
用于标记文件版本,只有当前值与上一次的值不同时该文件内容才有效global
用于标记常驻缓存是否发生更新all
用于标记是否清除所有缓存(如果global
没变就不会清除常驻缓存)change
中填写你要刷新的缓存,具体格式下面再描述,如果all
标记为了true
,change
写不写都没有区别
change
的格式为{"flag": [name], value: [args]}
,其中flag
为规则名称,value
为匹配值,value
可以为数组也可以为单个值。
目前支持以下几种规则:
flag | value | 描述 | 示例 |
---|---|---|---|
html | 无 | 清除所有 html 缓存 | {"flag": "html"} |
file | 文件名称(带拓展名) | 清除指定文件的缓存(如果不同目录下有重名文件并且想要准确清理的话需要带上部分路径名) | {"flag": "file", "value": ["main.js", "/css/index.css"]} |
str | 任意字符串 | 清除所有完整路径中包含指定字符串的缓存 | {"flag": "str", "value": "fonts"} |
reg | 正则表达式(不带两边的斜杠,不区分大小写) | 清除所有完整路径与指定正则表达式存在匹配的缓存 | {"flag": "reg", "value": "\.js$"} |
插入代码
插件自 2.6.0 开始不再将 sw-rules.js 中的所有内容复制到 sw 中,而是以白名单的形式,只有在白名单中的内容才会复制到 sw 中。
插件内置了一个白名单,这个白名单中包括了插件自身支持的几个基本的函数和对象,如果想要添加白名单,可以通过如下接口实现:
1 |
|
字符串数组的值就是要复制的内容的 key。(注意:要导出的内容必须使用module.exports
声明,否则将无法导出!)
同时还需要注意,不要在 sw-rules.js 的全局域中写只能在 sw 端执行的代码,因为在构建期间 node 也会执行这些代码,如果需要向 sw 添加一些自动执行的代码,可以使用afterJoin
和afterTheme
接口,使用方法如下:
1 |
|
插入到 sw 后会被转换为如下形式:
1 |
|
afterJoin
主题和用户都可以使用,若主题和用户同时声明了该函数,则用户声明将完全覆盖主题声明。afterTheme
是用户端独享的接口,主题禁止声明该接口。
如果想要插入一些需要根据 hexo 或 swpp 配置变化的值可以使用如下的代码:
1 |
|
以我的网站为例,上面的代码将在 sw 顶部插入如下代码:
1 |
|
如果你在cacheList
或modifyRequest
中使用了$eject
,请务必注意以下事项:
- 第二个参数的变量名必须为
$eject
,不得修改!!! - 必须使用箭头函数,不得使用
function
关键字声明cacheList
和modifyRequest
!!!
运行机制及避坑指南
插件内置的 SW 实现增量更新的方式是在网站内生成了一个描述各个版本差异的文件(下称作“版本文件”),更新时通过读取这个文件来计算需要更新哪些内容,插件的主要工作就是在生成网站时自动创建这个版本文件。
如果你使用1.3.0
之前的版本(请务必更新到最新版本),需要注意下面这种情况:
假如我们缓存了https://[npm]/hexo-swpp@1.0.2/index.js
,然后在本站中将链接的版本号替换为了1.1.0
,这时候插件是不能检测到需要删除index.js
这个文件的缓存的。客户端访问网页时虽然可以正确地访问到1.1.0
的index.js
,但是1.0.2
版本的 JS 仍然存在于缓存之中,但是永远不会被使用,这时候需要手动输入更新,告诉插件这个缓存需要删除。
当然新版本该问题也没有彻底解决,只是很大程度上的缓解了该问题,如果你是用插件无法监听到的方式引入的文件,仍然需要手动告知插件更新缓存。
那么插件是如何实现监听文件更新的呢?答案就是通过计算并存储每个文件的md5
。
插件会扫描配置文件中设置的输出目录(一般为public
)下的所有文件以及能够扫描到的你引入的文件,并从中找出需要缓存的文件,计算并存储它们的md5
值,然后存储在public/cacheList.json
当中。
那么插件是如何获取上一次生成的结果呢?为了兼容netlify
、github action
等非本地的自动构建方案,插件没有在本地存储cacheList.json
,而是直接输出到public
目录中。在下一次构建网站时会使用node-fetch
从网络拉取cacheList.json
。所以一定要保证在构建网站时网站可以被访问(请求的时候会仿造referer
和User-Agent
),否则拉取文件时会失败。
第一次构建网站的时候拉取文件的时候会出现404
警告,这是正常现象直接无视即可,但是第二次构建如果仍然出现404
则需要检查是否是哪里出了问题。
这里还有一个优化生成性能的小技巧,cacheList.json
中会存储所有满足缓存条件的文件信息,但是可能并不是所有文件都会被用户访问和缓存。比如sw.js
可能就满足你的缓存规则,但是sw.js
本身并不会走 sw,在exclude
中排除这些文件/文件夹可以优化生成性能(对用户无影响)。
同时注意maxHtml
不宜调的过大,charLimit
不宜过小,否则有可能会导致当你修改的 html 数量很多时一条更新信息就超过了charLimit
的限制,直接清掉了全站缓存,得不偿失。
反过来,charLimit
也不宜过大,否则当版本号积累后会使update.json
文件体积过大,从而拖慢客户端更新速度,还会加大服务器负担。
另外还需要注意,虽然插件内对 hexo 内置的全局变量进行了排序,但是部分主题会在页面内添加一些随机的数据(不修改网页内容连续构建得出不同的结果),这些数据会导致插件认为该页面需要刷新缓存。
此时需要用户手动解决这个问题,比如butterfly
主题会在页面内插入时间戳来实现过期文章提示的功能,我们可以选择修改主题源代码,将文章过期提示的功能移除(深度修改也可以),或者将插入时间戳的页面放入exclude
列表中,然后需要更新时手动在update.json
文件中写出更新条目。
QA
- Q:逃生门必须填吗?
A:该选项不是必填项。
如果你想要自定义 DOM-JS,则在 DOM-JS 出现不可逆的故障时需要填写逃生门;
逃生门留空或不设置默认为 0。 - Q:如何控制缓存大小?
A:当前 SW 不支持通过代码控制缓存大小,需要自行通过缓存规则控制缓存规模。 - Q:插件执行过程中 NodeJs 提示语法错误是怎么回事?
A:版本过低,升级 NodeJs 版本即可,推荐升级到 STL 版本。 - Q:如何在线上查看 sw 的工作情况?
A:开发者工具的“应用程序”和“网络”都可以查看相关信息。