这是来自 Mozilla 身份团队的“Node.JS 节日系列文章”的第 8 篇文章,共 12 篇。今天我们讨论更多关于前端性能优化的话题!
我们使用字体子集技术将 Persona 的字体占用空间减少了 85%,从 300 KB 降至 45 KB。本文详细介绍了我们如何实现这些性能改进,并提供了一些工具供您使用。
介绍 connect-fonts
connect-fonts
是一款 Connect 字体管理中间件,通过提供特定区域的字体子集文件来改善 @font-face
的性能,显著减少下载的字体大小。它还生成特定区域/浏览器的 @font-face
CSS,并管理 Firefox 和 IE 9+ 所需的 CORS 标头。字体子集从“字体包”中提供 - 包含字体子集的目录树,以及一个简单的 JSON 配置文件。一些常见的开源字体在预先生成的字体包中可用,您可以在 npm 上找到它们,创建您自己的字体包也很简单。
(感觉迷茫?我们在网上收集了一些关于 @font-face
资源的参考,您可以 参考 这些信息。)
静态与动态字体加载
当您只向所有用户提供一个大型字体时,设置网页字体并不复杂
- 生成
@font-face
CSS 并插入到您的现有 CSS 中 - 从您的 TTF 或 OTF 文件生成完整的字体系列,然后将它们放置在 web 服务器可以访问的位置
- 如果字体是从单独的域提供的,请向您的 web 服务器添加 CORS 标头,因为 Firefox 和 IE9+ 对字体实施同源策略
这些步骤相当简单;优秀的 FontSquirrel 生成器 可以为您生成所有缺失的字体文件和 @font-face
CSS 声明。您仍然需要查看 Nginx 或 Apache 文档来了解如何添加 CORS 标头,但这并不难。
如果您想利用字体子集技术来大幅提升性能,事情会变得更复杂。您将拥有每个支持区域的字体文件,并且需要动态修改 @font-face
CSS 声明以指向正确的 URL。CORS 管理仍然是必要的。这就是 connect-fonts
解决的问题。
字体子集:概述
默认情况下,字体文件包含许多字符:英语使用者熟悉的拉丁字符集;法语和德语等语言添加到拉丁字符集中的重音符号和重音字符;西里尔字母或希腊字母等其他字母表。一些字体还包含许多有趣的符号,尤其是当它们支持 Unicode 时(☃ 有谁认识吗?)。一些字体还支持东亚语言。字体文件包含所有这些内容,以便它们能够胜任地为尽可能多的受众服务。所有这些灵活性都导致了较大的文件大小;微软 Arial Unicode 拥有 Unicode 2.1 中所有语言和符号的字符,其重量惊人地达到了 22 兆字节。
相比之下,典型的网页只需要一个字体来完成一项特定任务:显示网页内容,通常只用一种语言,并且通常不包含奇特的符号。通过将提供的字体文件缩减到我们所需的子集,我们可以节省大量网页重量。
字体子集带来的性能提升
让我们比较一些常用字体和一些区域的本地化字体文件大小与完整文件大小。即使您只提供英文网站,通过提供英文子集也可以节省大量字节。
更小的字体意味着更快的加载时间,以及更短的等待时间,以便在屏幕上显示带格式的文本。如果您想在移动设备上使用 @font-face
,这一点尤其重要;如果您的用户恰好使用的是 2G 网络,节省 50 KB 可以将加载时间缩短 2-3 秒。另一个因素:移动设备缓存很小,字体子集更有可能保留在缓存中。
Open Sans 常规字体,完整字体(默认)和一些子集的大小(KB)
相同的字体,经过压缩(KB)
即使在压缩后,使用 Open Sans 的英文子集(13 KB),而不是完整字体(63 KB),也可以将字体大小减少 80%。请注意,这仅仅是针对一个字体文件进行的缩减 - 大多数网站使用多个字体文件。潜力巨大!
使用 connect-fonts
,Mozilla Persona 的字体占用空间从 300 KB 降至 45 KB,减少了 85%。 这相当于在典型的 3G 连接上节省了几秒钟的下载时间,在典型的 2G 连接上最多可以节省 10 秒。
进一步优化
如果您希望调整每一个字节和 HTTP 请求,可以将 connect-fonts
配置为返回生成的 CSS 字符串,而不是单独的文件。更进一步地说,connect-fonts
默认情况下提供尽可能小的 @font-face 声明,省略给定浏览器不接受的文件类型的声明。
示例:将 connect-fonts 添加到应用程序
假设您有一个非常简单的 express 应用程序,用于提供当前时间
// app.js
const
ejs = require('ejs'),
express = require('express'),
fs = require('fs');
var app = express.createServer(),
tpl = fs.readFileSync(__dirname, '/tpl.ejs', 'utf8');
app.get('/time', function(req, res) {
var output = ejs.render(tpl, {
currentTime: new Date()
});
res.send(output);
});
app.listen(8765, '127.0.0.1');
并使用一个非常简单的模板
// tpl.ejs
the time is <%= currentTime %>.
让我们逐步介绍如何添加 connect-fonts
以提供 Open Sans 字体,这是 多个 预制字体包之一。
应用程序更改
-
通过 npm 安装
$ npm install connect-fonts $ npm install connect-fonts-opensans
-
引入中间件
// app.js - updated to use connect-fonts const ejs = require('ejs'), express = require('express'), fs = require('fs'), // add requires: connect_fonts = require('connect-fonts'), opensans = require('connect-fonts-opensans'); var app = express.createServer(), tpl = fs.readFileSync(__dirname, '/tpl.ejs', 'utf8');
-
初始化中间件
// app.js continued // add this app.use call: app.use(connect_fonts.setup({ fonts: [opensans], allow_origin: 'http://localhost:8765' })
connect_fonts.setup()
的参数包括fonts
:要启用的字体数组,allow_origin
:我们为其提供字体的来源;connect-fonts
使用此信息为需要它的浏览器设置 Access-Control-Allow-Origin 标头(Firefox 3.5+,IE 9+)-
ua
(可选):列出我们将为其提供字体的用户代理的参数。默认情况下,connect-fonts
使用 UA 嗅探来仅为可以解析字体格式的浏览器提供字体,从而减小 CSS 大小。ua: 'all'
会覆盖此设置,为所有浏览器提供所有字体。
-
在您的路由中,将用户的区域传递给模板
// app.js continued app.get('/time', function(req, res) { var output = ejs.render(tpl, { // pass the user's locale to the template userLocale: detectLocale(req), currentTime: new Date() }); res.send(output); });
-
检测用户的首选语言。Mozilla Persona 使用 i18n-abide,locale 是另一个不错的选择;两者都可以通过 npm 获得。为了使示例简短,我们只获取 Accept-Language 标头 的前两个字符
// oversimplified locale detection function detectLocale(req) { return req.headers['accept-language'].slice(0,2); } app.listen(8765, '127.0.0.1'); // end of app.js
模板更改
现在我们需要更新模板。connect-fonts
假设路由的格式为
/:locale/:font-list/fonts.css
例如,
/fr/opensans-regular,opensans-italics/fonts.css
在本例中,我们需要
-
向模板添加一个样式表
<link>
,使其与connect-fonts
预期的路由匹配// tpl.ejs - updated to use connect-fonts
-
更新页面样式以使用新字体,我们就完成了!
// tpl.ejs continued
the time is <%= currentTime %>.
connect-fonts
生成的 CSS 基于用户的区域和浏览器。以下是 Open Sans 的“en”本地化子集的示例
/* this is the output with the middleware's ua param set to 'all' */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url('/fonts/en/opensans-regular.eot');
src: local('Open Sans'),
local('OpenSans'),
url('/fonts/en/opensans-regular.eot#') format('embedded-opentype'),
url('/fonts/en/opensans-regular.woff') format('woff'),
url('/fonts/en/opensans-regular.ttf') format('truetype'),
url('/fonts/en/opensans-regular.svg#Open Sans') format('svg');
}
如果您不想承担额外的 HTTP 请求成本,可以使用 connect_fonts.generate_css()
方法将此 CSS 返回为字符串,然后将其作为构建/压缩过程的一部分插入到您的现有 CSS 文件中。
就这样!我们的应用程序提供了一个时尚的时间戳。如果您想试用该示例代码,可以在 github 和 npm 上找到。
我们已经介绍了预制字体包,但是创建您自己的付费字体包也很简单。您可以在 connect-fonts
的 readme 上找到说明。
总结
字体子集可以为使用网页字体的网站带来巨大的性能提升;如果您在国际化的 Connect 应用程序中自托管字体,connect-fonts
会处理很多复杂性。如果您的网站尚未国际化,您仍然可以使用 connect-fonts
来提供您的本地子集,它仍然会为您生成 @font-face
CSS 和任何必要的 CORS 标头,此外您还可以获得一个流畅的升级路径,以便以后进行国际化。
未来的方向
目前,connect-fonts
基于区域处理子集。如果它还能为不需要 hinting 的平台(除 Windows 之外的所有平台)去除字体 hinting 会怎么样?如果它还能选择性地压缩字体并添加未来缓存标头会怎么样?还有很多有趣的工作要做。如果您想贡献想法或代码,我们非常欢迎!获取 源代码 并参与进来。
系列文章中的先前文章
这是一个包含 12 篇关于 Node.js 的系列文章的第 8 篇文章。之前的文章是
- 跟踪 Node.js 中的内存泄漏
- 全速加载 Node
- 使用安全的客户端会话构建简单且可扩展的 Node.JS 应用程序
- 出色的前端性能 第 1 部分 - 合并、压缩和缓存
- 构建一个不会熔化的 Node.JS 服务器
- 出色的前端性能,第 2 部分:使用 etagify 缓存动态内容
- 使用 node-convict 驯服配置
关于 Jared Hirsch
@6a68 热衷于 Persona,擅长弹奏象牙琴,鞋子总是沾满了沙子。
关于 Robert Nyman [荣誉编辑]
Mozilla Hacks 的技术布道者和编辑。发表关于 HTML5、JavaScript 和开放网络的演讲和博文。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年以来一直致力于 Web 前端开发,在瑞典和纽约市工作。他还在 http://robertnyman.com 上定期发表博文,热爱旅行和结识新朋友。
4 条评论