Posts Jekyll 网站性能优化
Post
Cancel

Jekyll 网站性能优化

近来,突然觉博客项目的 CSS 内容越来越多,多次在 HTML 与 CSS 之间增减内容,就会增加残留无效 CSS 的概率,人工筛除颇为费神。经过查找资料,发现 Chrome 的 DevTools 自带的Coverage功能可以很好的解决这个问题。好奇心驱使下,自然想着寻找更多玩法,能不能借助 DevTools 提高网站性能呢?答案是肯定的。Lighthouse是 Chrome 的性能审查扩展程序,位置在 DevTools 的Audits选项栏。根据自动化审查结果,可以知道网站性能短板所在,再由报告提供的建议去优化缺陷。

Lighthouse 审查

devtool-audits

Audits有五项审查内容,分别是Performance, Progressive Web App, Best parctices, Accessibility, SEO等,可以根据自己的需要选择。本文针对其中的Performance(性能)开聊。Chrome 打开目标网页,启动Audits,十几秒内即可完成审查。 网站的性能优化大概有以下几个方向:

  • 跨域资源预处理
  • JS 异步加载
  • 按业务分拆 JS/CSS 的调用
  • 本地资源压缩
  • 图片懒加载

下面将逐项叙述每个优化过程细节。

跨域资源预处理

对于跨域的静态资源引用,譬如字体、图标资源的引用,在<link>标签中使用属性preconnectdns-prefetch可以告诉浏览器对跨域资源给予最高优先级,在页面加载一开始就进行处理。

  • dns-prefetch 告诉浏览器在后台自动获取目标网址的 DNS 信息,这样在页面使用所需资源时就可以减少网络请求的时间。

  • preconnect 则更进一步,除了完成上述工作,还进行 TLS 判断以及 TCP 三次握手,是更为重量级的操作。示例:

    1
    
    <link href="https://cdn.domain.com" rel="preconnect" crossorigin>
    

实际应用时,两者会合并使用:

1
2
<link href="https://cdn.domain.com" rel="preconnect" crossorigin>
<link href="https://cdn.domain.com" rel="dns-prefetch">

两者合用,可以减少后续的跨域延迟。另外,对于不支持preconnect的浏览器引擎,还有dns-prefetch作后备,dns-prefetch对浏览器种类和版本的支持比前者要更广1

JS 异步加载

异步加载 JS 资源,可使用<script>属性的asyncdefer,但是只对外部调用的资源有效,即<script>标签中必须提供src属性。

async

默认情况下,当浏览器解析到 <script>标签时,会阻塞 DOM 树的解析,直至 script 内容下载完毕并执行,才会继续 DOM 树的构建 2。对页面样式展示非必要的 JS 代码,可以在<script>内部添加属性async来异步下载 script 内容,减少对 DOM 构建的阻塞。如:

1
<script src="/assets/js/common.js" async></script>

即便使用async还是会对 DOM 树解析造成阻塞,因为 JS 内容下载完毕后,会被立即执行,若此时 DOM 解析还没完成,就会被阻塞3

defer

如果要保障 JS 放在 DOM 完成解析后执行,请使用defer属性。

分场景调用 JS/CSS

在 Jekyll 中,{% include %}的应用可以增加代码的简洁性,也可能造成页面的 JS/CSS 冗余。一般 Jekyll 项目的习惯是,项目所有的 JS/CSS 都在_includes/head.html中引入,然后通过{% include head.html %}导入每个下层 layout。很多时候,部分 JS/CSS 并不是每个页面都必须的。

例如,本站的post布局需要bootstrap-toc.js,此外的page布局,home布局都不需要 toc 相关的 JS/CSS。 这时应该把bootstrap-toc相关的 JS/CSS 单独放置到post布局上引用,从而增加了其他布局的加载及渲染速度。

本地资源压缩体积

CSS 压缩

使用 Jekyll 提供的 sass pipeline 压缩,每次编译的时候会自动压缩引入的 scss 文件,十分便捷。使用方法是,在项目_config.yml文件添加 sass 配置:

1
2
3
sass:
  sass_dir: /assets/css # Override jekyll default path '/_sass'
  style:  compressed

其中sass_dir为项目 CSS 目录。

将自己编写的 CSS 3 文件后缀修改为.scss,在 CSS 文件目录/assets/css下新建一个文件,命名为styles.scss,添加导入所需的 scss。例如,导入styles.scss同目录下的main.scsssyntax.scss两个文件,则styles.scss添加内容:

1
2
3
4
5
---
---

@import "main";
@import "syntax";

项目<head>引用作相应修改,去掉原先对每个独立 CSS 文件的<link rel="stylesheet">引用,改为对styles.css的引用:

1
<link rel="stylesheet" href="/assets/css/styles.css">

引用文件后缀使用了.css,这是因为.scss编译后后缀会被自动改为.css

JS 压缩

项目中自己编写的 JS 以及第三方的 JS 文件(未压缩),可使用 YUI Compressor 压缩,压缩结果命名可以在.js后缀前添加min标识。如原文件tools.js压缩后命名为tools.min.js

1
$ java -jar yuicompressor.jar tools.js -o tools.min.js

接着使用 Jekyll 的 Assest 特征合并引用所需的 JS,在 JS 目录下新建文件common.js,使用{% include %}引入其他 JS,如本站内容为:

1
2
3
4
5
6
7
---
---

{% include_relative dist/back-to-top.min.js %}
{% include_relative dist/category-collapse.min.js %}
{% include_relative dist/search-display.min.js %}
{% include_relative dist/sidebar-toggle.min.js %}

项目 HTML 的head中使用合并文件替代分开的几个文件引用:

1
<script src="/assets/js/common.js" async></script>

HTML 压缩

对于 Jekyll,有一个 Liquid 实现的开源方案提供解决:Jekyll HTML Compressor

安装很简单,首先到 Release 下载最新版的compress.html,拷贝到项目的_layouts目录下。然后修改最顶级的默认样式 _layouts/default.html,在它的头部添加 Yaml:

1
2
3
---
layout: compress
---

然后,在_config.yml添加配置:

1
2
3
4
5
6
7
8
compress_html:
  clippings: all
  comments: ["<!-- ", " -->"]
  endings: [html, head, body, li, dt, dd, rt, rp, optgroup, option, colgroup, caption, thead, tbody, tfoot, tr, td, th]
  profile: false
  blanklines: false
  ignore:
    envs: []

具体每个参数的含义可以参考项目 README 介绍,经过几步即完成了插件的安装。该注意的是,如果 post 使用了 Jekyll 的linenos,则需要额外解决内嵌<pre>压缩错乱的 BUG

具体表现为, 原始状态使用{% highlight LANGUAGE linenos %}时,生成的代码格式为:

1
2
3
4
5
6
7
<figure class="highlight">
  <pre>
    <code class="..." data-lang="...">
    Code snippet
    </code>
  </pre>
</figure>

当压缩HTML时,会把最外层的<pre></pre>删除,但是不完整,残留/>

1
2
3
4
5
6
<figure class="highlight">
  />
    <code class="..." data-lang="...">
    Code snippet
    </code>
</figure>

虽然这会造成样式大错乱,但是项目维护者不打算修改这个 bug,认为这样会增加扫描层数,大大增加项目 build 的时间。上述 issues #71 中,已经有用户提供了解决方法: 在_includes/中新建文件fixlinenos.html,添加内容:

1
2
3
4
{% if _code contains '<pre class="lineno">' %}
  {% assign _code = _code | replace: "<pre><code", "<code" %}
  {% assign _code = _code | replace: "</code></pre>", "</code>" %}
{% endif %}

在每篇 post 源码中,调用{% highlight LANGUAGE linenos %}的地方作出修改。

原始代码:

1
2
3
{% highlight AnyLanguage linenos %}
  Some code
{% endhighlight %}

应修改为:

1
2
3
4
5
6
{% capture _code %}
{% highlight AnyLanguage linenos %}
  Some code
{% endhighlight %}
{% endcapture %}
{% include fixlinenos.html %}{{ _code }}

这样压缩就不会出现问题了,可是要注意的是,<pre>fixlinenos.html清除后,代码段的overflow: auto也会消失,需要为figure.highlight添加样式overflow: auto对超出父容器宽度的代码作水平滑动。

JSON 压缩

这个是个意外发现,和上一步 HTML 压缩 一样,文件头部加 YAML 声明调用压缩的layout即可:

1
2
3
---
layout: compress
---

服务器 GZIP 压缩

传输过程可以开启 Web 服务器的GZIP选项,如果博客是部署在 GitHub Pages 或 Coding Pages ,则默认开启了 GZIP 压缩传输。若是自己个人部署的 Web 服务器,如 Nginx,Apache 等,则需要跟进此点优化。

懒加载图片

图片是重要的内容资源,但是却不能忽略图片体积对网络带宽带来的负面影响。解决的策略就是对无需立即展示的图片,进行懒加载,也就是当网页滑动到图片所在位置时才进行加载。这个过程可以通过第三方库来完成,如 Lozad.js,轻量级,支持自定义加载过程细节。

结语

上述只是针对本站的优化,并没有覆盖常规网站的所有优化点,更加全面细致的性能建议,可参考 Google 开发者文档

参考资料

引用

KcpTun 加速 Shadowsocks

从 Google Analytics 获取 Pageviews

Comments powered by Disqus.