独立托管 Web 字体

2023-02-21 ⏳5.1分钟(2.0千字) 🕸️

本站使用霞鹜文楷字体,最早还是在班班的频率1博客上看到的,非常喜欢。本站最早使用 CDN 加载该字体。但因为 jsDelivr2 在国内日益不稳定,我最近决定用自己的服务器独立托管霞鹜文楷。本文就梳理这其中的来龙去脉。

最近发现 elemecdn 上有霞鹜文楷的镜像3,加载速度很快。大家可以考虑使用。

因为字体相关的请求实在太多,本站已经切换到 elemecdn。

2023年11月28日收到 Don 留言:

elemecdn 在海外访问延时很大,建议换成 cdn.staticfile.org

当时测了一下,确实不错,就改用 staticfile 了。

但今天(2023年12月20日)发现 staticfile 抽风,还是改回自托管吧。

汉字之觞

通常浏览器只能使用系统自带的字体渲染文字。因为不同的系统安装的字体也不尽相同,所以相同的页面在不同的浏览器下显示效果也有不少差异。于是就诞生了 Web 字体,也叫网页字体。

Web 字体的基本思想是让浏览器从网络上自动下载需要的字体,然后再渲染文字。这不但可以统一网页在不同系统上的展示效果,还可以让设计师随心所欲地使用各种字体来表达情感。

但是,这么美好的东西在汉字文化圈却遇到了不小的麻烦。因为拼音文字一般只很少的字母(比如拉丁字母有二十四个),再加上部分标点符号和大字形式,它们的字体文体通常都不大。但常用汉字就有三四千,如果再加上几种主要的字体,那单个字体文体可能会达到几兆甚至几十兆字节之巨,很难应用到 Web 字体领域。也正因如此,汉字圈的 Web 字体发展比较迟缓😂

但我打开频率网站的时候发现它的字体加载速度奇快无比,就非常纳闷。一番研究后发现,原来 Web 字体支持 unicode-range4 属性。简单来说我们可以将一个大的字体文件拆分成多个小文件,每个文件对应一组 @font-face 再用 unicode-range 指定文件的字符范围。比如下面的一段字体声明:

@font-face {
  font-family: 'LXGW WenKai Lite';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('./files/subset-4.woff2') format('woff2');
  unicode-range: U+1f1e9-1f1f5
}

浏览器在渲染文字时如果碰到了1f1e9-1f1f5范围内的 Unicode 字体才会下载对应的文件。这样就在一定程度上解决了单个中文字体体积过大的问题。所以班班的网页字体加载很快。

霞鹜文楷是一款开源字体5,基于日本的 Klee One 字体。该项目的主要工作是添加其中缺失的汉字6,并不关注 Web 字体等衍生技术。好在 Chawye Hsu 创建了专门的项目7来支持 Web 字体。

这里最主要的工作就是划定字体拆分规则,然后生成对应的字体文件和 CSS 描述文件。 Chawye 将生成后的文件托管到 jsDelivr8 上。因为字体和 CSS 文件比较多,所以就直接使用 jsDelivr 上的版本。

切分字体

如果要自托管,就需要自己生成 CSS 文件和字体文件切片。这部分 chawyehsu 已经在项目中提供了 python 脚本。

克隆项目源码

git clone bhttps://github.com/chawyehsu/lxgw-wenkai-webfont.git

创建 virtualenv,免得把系统的 python 库弄乱。用完就扔🤪

python3 -m venv venv
# 切换到新环境
source venv/bin/active

按装 python 依赖库

pip install -r requirements.txt

最后生成资源文件:

python -m update --package lxgw-wenkai-webfont

所有文件都保存在 packages/lxgw-wenkai-webfont 目录,将它们复制 Web 服务器然后引用就可以了。

为何自托管

但为什么又要自行托管呢?萌生此想法的直接原因是有一次打开博客感觉字体加载很慢。的确,自从 jsDelivr 的 ICP 备案过期后就大不如从前了。而且随着 Web 技术的发展,使用 CDN 的优势也越来越不明显。

曾经使用 CDN 加载公共静态资源是标准做法,因为同样一个 js 库(比如 jQuery),很多网站都在用。如果网络甲跟乙都使用相同版本的 jQuery,那么用户先访问甲网站之后再访问乙网站就不需要重复加载 jQuery。

但这种方式有几个问题。第一,就是公共库通常都有多个版本,而不同网站使用相同版本且在相同的 CDN 服务的概率比较小。所以加速效果有限。第二,现在的浏览器都支持 HTTP/2 协议,该协议可以通过复用底层 TCP 连接降低加载耗时,但使用三方 CDN 就必须创建新的 TCP 连接。第三,也是最重要的一点,使用共享 CDN 资源可能会泄漏用户隐私!

我开始也不太理解怎么能泄漏隐私呢?看了这篇文章9算是大致明白了。

假设黑客得知某网站A使用公共 CDN 加载某静态资源。他可以在自己的网站上也加载相应的资源,然后引诱用户访问自己的网站。如果用户访问了,就通过脚本下载前面说的静态资源。如果加载时间小于某个阀值则证明该用户曾经访问过网站A,这就泄露了用户的隐私。

因为有隐私风险,所以浏览器厂商引入了 partitioning the cache 也就是分区缓存。简单来说就是对于相同的资源,不同的网站使用不同的缓存。也就是说,虽然网站 A 和 B 都依赖相同 CDN 上的某个资源,但用户访问 A 所加载的资源并不能在访问 B 时使用。也就说,相同的 CDN 资源对不同的网站来说就是不同的资源,根本不能实现加速的效果。

结合分区缓存以及前两个问题,再加上 jsDelivr 时不是抽风,所以我决定自己独立托管 Web 字体。因为我的网站也开启了 HTTP/3 所以加载速度应该不会差太多,而且更稳定。唯一的问题是需要消耗额外的流量。但我的虚拟主机每月都剩余很多流量,不用白不用。

以上就是本文的全部内容。我们可以看到,随着技术的更新,很多曾经的最佳实践已经不再必要甚至变成了负优化,所以技术人要持续学习,不断适应新的环境。


  1. https://pinlyu.com↩︎

  2. jsDelivr 曾经是唯一在国内拥有 ICP 备案的免费 CDN 服务商,所以可以在国内设置加速节点,速度飞快。但它是公益 CDN,可以免费为开源项目加速,所以很快就被玩滥了。很多人直接用它做图床,甚至用它传播不符合中国大陆法令的内容。最终它的备案被注销,然后泯然众人矣😂↩︎

  3. https://npm.elemecdn.com/lxgw-wenkai-webfont@1/↩︎

  4. https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range↩︎

  5. https://github.com/lxgw/LxgwWenKai↩︎

  6. 日本使用汉字的数量远远小于中国,所以 Klee One 缺少很多汉字。↩︎

  7. https://github.com/chawyehsu/lxgw-wenkai-webfont↩︎

  8. https://www.jsdelivr.com/package/npm/lxgw-wenkai-lite-webfont↩︎

  9. https://www.jefftk.com/p/shared-cache-is-going-away↩︎