本博客优化点总结 (二) - Ghost 路由 RSS 暗黑模式 闪白屏 响应式图片

Website Sep 11, 2021

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 图片的尺寸语法,要么改主题的源码让它支持。

一般来讲有如下几种情况需要修改:

  1. Banner 大图随设备尺寸加载不同尺寸的图片
  2. 文章列表的预览缩略图,直接用小尺寸即可
  3. 文章内的图片

针对第一种情况我遇到的麻烦主要体现在 Retina 屏幕下,img 标签的 srcsetsizes 属性在实际请求的时候会乘上像素倍率。愿意深究的同学可以参考这篇文章 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`)
      }
    })
  }
}

Tags

Jie Li

🚘 On-road / 📉 US Stock / 💻 Full Stack Engineer / ®️ ENTJ