本博客优化点总结 (二) - Ghost 路由 RSS 暗黑模式 闪白屏 响应式图片
2017 年的时候写过一篇 本博客优化点总结 - js 字体 归档 评论系统 CDN,因为内容已经比较多了,所以再新开一篇来写写怎么处理 Ghost 的路由还有 RSS 订阅限制文章数量,以及暗黑模式下闪白屏问题的修复。
优化 Ghost 的路由
默认 Ghost 的文章路由是网站域名+${slug},如果你想针对不同路由做不同的操作的话,这样就很难实现。最好的办法就是在文章前面加一个前缀。普通页面和文章列表页因为一共也没几个,不处理也没什么问题,这样整个博客的路由如下:
- 文章:/post/${slug}
- 作者:/author/${slug}
- 标签:/tag/${slug}
配置文件如下:
routes:
collections:
/:
permalink: /post/{slug}/
template: index
taxonomies:
tag: /tag/{slug}/
author: /author/{slug}/
修改路由之后,最重要的就是设置重定向了。如果你在 Nginx 上做重定向,把 /post/a-b-c
重定向到 /a-b-c/
很容易,但是你想反过来就很难判断了。还好 Ghost 提供一个内部的重定向机制,后面我会分享代码自动处理。
Ghost 4 引入一个订阅的功能,并且强制开启,我根本用不到订阅功能只能眼睁睁看着页面加载多余的 js 拖慢速度。于是我退回到了 Ghost 3,并且使用当前用户的 pm2 来管理,再也不需要 ghost 智障的用户权限管理。所以以下代码适用于 Ghost 3,其他用户需要自行修改。生成之后保存为 json 并且传到 Ghost 后台即可。
import urllib.request, json
from dateutil import parser
import ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
website = 'xxxx.com'
content_api_key = 'xxxxx'
with urllib.request.urlopen('https://' + website + '/ghost/api/v3/content/posts/?key=' + content_api_key + '&limit=all', context=ctx) as url:
data = json.loads(url.read().decode())
redirections = []
for post in data['posts']:
published_date = parser.parse(post['published_at'])
redir = dict()
redir['from'] = '^/' + post['slug'] + '/'
redir['to'] = '/post/' + post['slug'] + '/'
redir['permanent'] = True
redirections.append(redir)
print(json.dumps(redirections))
输出的 json 大概长这个样子,需要注意的一点是如果你不包括 ^
字符,Ghost 会无限重定向循环。
[{
"from": "^/exporting-authy-passwords-to-other-authenticators/",
"to": "/post/exporting-authy-passwords-to-other-authenticators/",
"permanent": true
}, ...]
如果你还需要替换其他服务里面的 url,比如自己文章引用自己文章的链接,或者评论系统的 url,可以参考如下代码。
/*
* @Author: jetl
* @Date: 2021-09-10 17:28:35
* @Last Modified by: jetl
* @Last Modified time: 2021-09-11 11:43:43
*/
const redirectsFile = 'redirects.json'
const sourceFile = 'Comment.0.jsonl'
const targetFile = 'newdb.jsonl'
const fs = require('fs')
var redirects = fs.readFileSync(redirectsFile, 'utf8')
const dict = {}
JSON.parse(redirects).forEach((i) => {
dict[i.from.replace('^/', '/')] = i.to
})
var source = fs.readFileSync(sourceFile, 'utf8')
for (const [key, value] of Object.entries(dict)) {
const removeStr = key
const regex = new RegExp(removeStr, 'g')
source = source.replace(regex, value)
}
fs.writeFileSync(targetFile, source)
优化 RSS 订阅流量
不得不说现在无节操的 RSS 厂商真的多,一个月一次更新的破博客每天刷几千遍,一次流量 200kb 算的话,一天就能跑将近 1GB。RSS 订阅给网站带不来任何有效流量反而增加流量费用。『给RSS爱好者们的一封信』为什么我们应该提高RSS刷新间隔? 这篇文章写的就很不错。
废话不多说,Ghost 官方不支持修改 RSS 输出文章限制的,我已经翻遍了官方论坛和 GitHub issues,发现老外也不怎么注意这个问题,所以如果想改,只能改代码了。
core/frontend/services/rss/generate-feed.js
// 第 27 行可以限制摘要输出的文字
L27: description: post.custom_excerpt || post.meta_description || downsize(htmlContent.html(), {words: 35}),
// 第 56 行可以直接禁止输出全文。如果你想保守点,也可以写 downsize(htmlContent.html(), {words: 150}) 输出 150 个字/单词。
L56: _cdata: ''
core/frontend/services/routing/controllers/rss.js
// rss controller 里面有一个查数据库的配置,默认只有 page: 1,默认的 limit 是 15。这里可以改写 limit 为任何你想要的数字。
L31: limit: 10,
暗黑模式优化
在网上逛了一圈,发现很多博主的网站在暗黑模式下,站内跳转新页面或者刷新,都会触发一个闪白屏。在晚上没开灯的情况下堪比闪光弹。
尝试过各种办法,包括给 body 属性加黑,还有禁用动画(当时怀疑是动画掉帧导致白屏),全都没用。最终解决方案如下:
body {
visibility: hidden;
opacity: 0;
}
window.addEventListener('DOMContentLoaded', () => {
const currentSavedTheme = localStorage.getItem('theme')
document.documentElement.setAttribute('data-theme', currentSavedTheme !== null ? currentSavedTheme : 'light')
document.body.style.visibility = 'visible'
document.body.style.opacity = 1
})
在 body 标签里面把整个页面隐藏掉,在 js 中监听 DOMContentLoaded 事件,再把它显示出来。不可以在 onload 方法里面做,因为 onload 要更晚一些调用,还是会有一瞬间白屏。
清理无效图片
首先在博客里面导出 json 数据备份,用正则表达式匹配如下字符串,注意匹配完毕之后,清点一下数量和不使用正则只搜索 pupboss.com/image
的数量对比,如果两者一致就说明没漏什么后缀。如果漏了,就用文本编辑器小地图看看那行没选中,补上后缀即可。
// pupboss.com 为固定字符串,.*? 使用贪婪模式匹配符合条件的后缀名
pupboss.com.*?.(jpg|png|gif|jpeg|svg|mp4|m4v)
将使用中的图片名列表存成 txt,再把所有文件导出存成一个 txt,随后使用如下代码。
/*
* @Author: jetl
* @Date: 2021-09-12 20:38:23
* @Last Modified by: jetl
* @Last Modified time: 2021-09-12 20:51:51
*/
const fs = require('fs')
var exists = fs.readFileSync('exist.txt', 'utf8').split('\n')
var allItems = fs.readFileSync('all.txt', 'utf8').split('\n')
function getArrDifference(arr1, arr2) {
return arr1.concat(arr2).filter(function(v, i, arr) {
return arr.indexOf(v) === arr.lastIndexOf(v);
});
}
const diff = getArrDifference(exists, allItems).sort()
const diffStr = diff.join('\n')
fs.writeFileSync('r.txt', diffStr)
最后就可以在 r.txt
里面找到要删除的图片了。如果你嫌一个个删太麻烦,可以在 r.txt
的每一行前面加一个 rm ./
最终效果如 rm ./image/xxx/xxx.jpg
。把文件名改成 sh
后缀传到服务器,执行 chmod +x r.sh
和 ./r.sh
即可。
图片尺寸自适应
图片自适应有两种实现方式,要么改 Ghost 的源码让它支持 CDN 图片的尺寸语法,要么改主题的源码让它支持。
一般来讲有如下几种情况需要修改:
- Banner 大图随设备尺寸加载不同尺寸的图片
- 文章列表的预览缩略图,直接用小尺寸即可
- 文章内的图片
针对第一种情况我遇到的麻烦主要体现在 Retina 屏幕下,img
标签的 srcset
和 sizes
属性在实际请求的时候会乘上像素倍率。愿意深究的同学可以参考这篇文章 Responsive images with srcset and sizes。
经过一些尝试,发现如下的配置可以比较完美的适配小屏幕。其中 !medium
后缀在配置图片处理的时候,宽度可以指定到 800 甚至 600 px。
<img
srcset="
{{img_url background}}!medium 1000w,
{{img_url background}} 2000w
"
sizes="100vw"
src="{{img_url background}}"
alt=""
/>
针对第二种情况,在代码里写死后缀即可,示例如下:
<img class="m-tag-card__picture" src="{{img_url feature_image}}!medium" loading="lazy" alt="">
针对第三种情况,目前没有特别好的方案,最好的办法就是改 Ghost 源码,但是这样后期维护很麻烦。不改源码的话,也可以在 DOMContentLoaded 的时候,执行 js 脚本批量替换图片 url,但是后果就是前面几张图改了也没用,因为 DOMContentLoaded 的时候浏览器已经并发下载页面的图片了。
但是实际表现也还凑合,因为小屏幕设备上一共也显示不了多少内容,配合懒加载机制至少后面几张图可以加载小图片,代码如下:
export const dynamicImageSize = () => {
if (window.innerWidth <= 800) {
const imgs = document.querySelectorAll('img')
imgs.forEach((img) => {
if (img.getAttribute('src').includes('static.pupboss.com') && !img.getAttribute('src').includes('!')) {
img.setAttribute('src', `${img.getAttribute('src')}!medium`)
}
})
}
}