小白也能用的 SW 构建插件
本教程基于hexo-swpp@2.4.2
编写
a.b.c
中的a
或b
发生变化,请务必重新阅读一遍文档!!!请务必使用最新的稳定版本!旧版本很可能包含某些功能漏洞!!!
若版本号后方带有beta.*
字样,请勿使用!
介绍
该插件用于在 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 |
|
然后添加配置项,下面列出的是插件支持的所有配置项,非必填项可省略不写:
1 |
|
下面列出的配置项为必填项,未列出的为可选项:
1 |
|
下面列出的配置项为包含缺省值的配置项及其缺省值:
1 |
|
接着还需要在博客根目录创建一个 JS 文件——sw-rules.js
,该文件内需按照下面的格式填写缓存规则以及修改函数:
sw-rules.js
必须要创建,且其中必须包含cacheList
,就算对象内容都是空的也要写上,不然无法构建!
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 参数不同而不同的话请勿缓存,否则会导致拉取到错误的内容!!!
逃生门
该配置我们可以称之为“逃生门”。这个配置后应填写一个整数(缺省为 0),在 sw 检测到用户没有加载过这个版本时,就会强制刷新用户所有缓存。这个选项主要是提供给想要自定义 DOM 端 JS 的用户使用的,因为缓存的更新是需要 DOM 端发送更新请求到 SW 端的,所以有些时候因为 DOM 端代码有误会导致永远无法触发缓存更新,这时候就可以通过逃生门强制更新缓存。
填写逃生门时,随意填写一个未填写过的正整数即可,不填或填0
表示不启用逃生门。
2.4.1
(不包括) 版本前的逃生门的功能存在缺陷,在 DOM 端无法发送update
信息到 SW 端时无法触发逃生门,2.4.1
版本开始修正了该问题,现在无论 DOM 端是否发送信息 SW 端都能正常触发逃生门。
注意:如果你是从 1.4.0 及更早的版本更新上来的用户,请务必填写 escape,因为老版本插件中生成的 DOM 端 JS 无法正常执行!!!
Request 篡改
如果你需要在发起网络请求前修改请求头的信息,直接在sw-rules.js
中添加modifyRequest
函数即可。
1 |
|
CDN 竞速
CDN 竞速开启后会同时向多个 URL 发起网络请求,使用速度最快的一个 URL 返回的数据。开启该功能后,将增加客户端的网络和 CPU 压力,请酌情开启。
想要启用 CDN 竞速,需要在配置中将cdnRacing
设置为true
,并在sw-rules.js
中添加如下语句:
1 |
|
这个函数体只是给出了一个样例,读者可自行编写。
备用 URL
备用 URL 和 CDN 竞速有异曲同工之妙,不过备用 URL 是在上一个链接下载时长超过指定时间后再发起对下一个 URL 的访问。在发起新的访问时不会中断对上一个 URL 的访问,只有在某一个访问成功拉取内容后才会中断对其它 URL 的访问。
如果需要开启此功能,需要在配置项中将spareUrl
设置为true
。开启时,建议将速度最快、受众群体最大的 URL 放在开头,否则会严重影响加载速度。
下面给出一个代码样例:
1 |
|
阻塞响应
有时候,我们可能希望屏蔽一些 URL 的访问,这时候可以通过直接返回 208 来阻塞这些 URL。
注意:当 URL 被屏蔽后,拉取文件的过程中并不会抛出异常,如果拉取文件的代码没有做错误处理,在处理文件内容时可能会报错。
在sw-rules.js
中添加如下代码即可启用阻塞响应功能:
1 |
|
URL 美化问题
注意,本插件不支持部分 URL 开启 URL 美化,部分 URL 关闭 URL 美化。如果部分 URL 开启了美化,且你的缓存规则同时匹配了开启了美化的 URL 和未开启美化的 URL,则会导致部分 URL 的缓存无法正常更新!
插件通过hexo
内的pretty_urls
设置项判断网站是否启用 URL 美化,请务必保证部署到网站的效果和配置项中的一致。
如果你无法做到统一 URL 风格,请不要缓存与配置项风格不一样的 URL!
外部文件监听
插件支持监听一下文件内引入的外部文件的更新:
- 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 |
|
其中:
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$"} |
运行机制及避坑指南
插件内置的 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:该选项不是必填项。
如果你是从1.4.1
前的版本升级上来的,则必须填写逃生门;
如果你想要自定义 DOM-JS,则在 DOM-JS 出现不可逆的故障时需要填写逃生门;
逃生门留空或不设置默认为 0。 - Q:如何控制缓存大小?
A:当前 SW 不支持通过代码控制缓存大小,需要自行通过缓存规则控制缓存规模。 - Q:插件执行过程中 NodeJs 提示语法错误是怎么回事?
A:版本过低,升级 NodeJs 版本即可,推荐升级到 STL 版本。 - Q:如何在线上查看 sw 的工作情况?
A:开发者工具的“应用程序”和“网络”都可以查看相关信息。 - Q:能否向自动生成的 sw 中插入一些代码?
A:目前不支持自定义代码插入位点,但是sw-rules.js
中的代码会一同被插入到 sw 中,插入的位置为代码中调用require
的位置。