Jekyll
jekyll用了这么久了,皮肤都换了三套了,是时候系统看一下jekyll的架构了。
- Jekyll:GitHub Pages:如何使用jekyll搭建网站;
- Jekyll:minima结构:minima网站架构;
- Jekyll:minima主题自定义:各种自定义元素,以minima为例;
- docsy-jekyll:collection定义、default layout;
- jekyll-theme-chirpy:collection、utterance;
使用jekyll创建一个站点非常容易。
jekyll命令的更多用法可参考Command Line Usage。
下面来了解一下jekyll的整体架构。
page
page是jekyll最基本的内容,其实就和在apache下访问一个普通的静态网站一样。不同的是,.md
(可以包括front matter)也会被jekyll渲染为html文件:
1
2
3
4
.
├── about.md # => http://example.com/about.html
├── index.html # => http://example.com/
└── contact.html # => http://example.com/contact.html
如果有文件夹,访问路径要带上文件夹,和静态网站一样:
1
2
3
4
5
6
.
├── about.md # => http://example.com/about.html
├── documentation # folder containing pages
│ └── doc1.md # => http://example.com/documentation/doc1.html
├── design # folder containing pages
│ └── draft.md # => http://example.com/design/draft.html
使用
permalinks
可以在jekyll渲染时改变文件的路径。
post
jekyll天然对博客提供支持,所以提供了post layout,只要把博客内容放在_posts
文件夹下即可。同样,可以是markdown,也可以是html。当然,我们一般用markdown,渲染的事情交给jekyll。
post文件要求名称符合下面的格式:
1
YEAR-MONTH-DAY-title.MARKUP
比如:
1
2
2011-12-31-new-years-eve-is-awesome.md
2012-09-12-how-to-write-a-blog.md
post文件必须带有front matter,一般主要是用来指定一个layout,比如:
1
2
3
4
5
6
7
8
9
10
---
layout: post
title: "Welcome to Jekyll!"
---
# Welcome
**Hello world**, this is my first Jekyll blog post.
I hope you like it!
在_posts
文件夹下,默认的layout就是post。也可以在config里手动配置默认layout。
链接
可以使用post_url
做文档之间的站内引用,这样就不必考虑文档的permalink发生变动导致超链接失效。
图片、pdf等内容的超链接直接用markdown语法即可,绝对路径的root代表本工程的文件夹,而非整个linux系统的root。相对路径的起点也是jekyll root:
1
2
3
4
... which is shown in the screenshot below:
![My helpful screenshot](/assets/screenshot.jpg)
... you can [get the PDF](/assets/mydoc.pdf) directly.
目录 - site.posts
目录就是所有博客的集合。所以可以用下面的模板创建一个目录页面:
1
2
3
4
5
6
7
8
9
<ul>
{% for post in site.posts %}
<li>
<a href="{{ post.url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
tag和category - site.tags
/site.categories
支持博客自然支持tag和category,他们可以定义在front matter里。
同样可以用类似的方法创建一个tag集合的页面:
1
2
3
4
5
6
7
8
9
10
{% for tag in site.tags %}
<h3>{{ tag[0] }}</h3>
<ul>
{% for post in tag[1] %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
{% endfor %}
除了在front matter里定义,_posts
文件夹前的文件夹默认也会被jekyll作为category:
1
movies/horror/_posts/2019-05-21-bride-of-chucky.markdown
这个post就会拥有movies和horror两个category。
和tag不同的是,category会影响post的路径!默认的permalink是permalink: /:categories/:year/:month/:day/:title:output_ext
,包含category。如果上述例子里的front matter里又定义了categories: classic hollywood
,则这篇文章会有四个category,最终渲染后的html路径为:
movies/horror/classic/hollywood/2019/05/21/bride-of-chucky.html
excerpt
jekyll的post也支持博客里常见的摘要,默认就是post文章的第一段。不过也可以使用excerpt_separator
自定义分隔符,可以放在front matter里,也可以放在config里:
1
2
3
4
5
6
7
8
9
---
excerpt_separator: <!--more-->
---
Excerpt with multiple paragraphs
Here's another paragraph in the excerpt.
<!--more-->
Out-of-excerpt
因此可以用下面的代码渲染出带摘要的目录:
1
2
3
4
5
6
7
8
9
10
<ul>
{% for post in site.posts %}
<li>
<a href="{{ post.url }}">{{ post.title }}</a>
{{ post.excerpt }}
</li>
{% endfor %}
</ul>
draft
没写完的post可以暂存为草稿,放到_drafts
下。文件不需要带date,jekyll也不会把它渲染出来:
1
2
3
4
.
├── _drafts
│ └── a-draft-post.md
...
想预览草稿的话,可以使用带--drafts
参数的jekyll serve
或jekyll build
。
文件的修改时间默认就是它的date。
Front Matter
所有带前言(YAML格式)的文件都会被jekyll处理。
前言里可以放预定义/自定义的变量,然后在正文中使用liquid标签访问:
1
2
3
4
5
6
7
8
9
---
layout: post
title: Blogging Like a Hacker
name: puppylpg
---
<h1>{{ page.name | downcase }}</h1>
所有定义在页头里的变量,在Liquid中都可以使用page
变量访问,比如上面定义的name的访问方式就是{{ page.name }}
。
jekyll的page或post有一些预定义好的变量,可以直接在前言里用:
- layout:使用哪个layout渲染本文件。layout模板文件都放在
_layouts
下; - permalink:自定义本文的url;
- published:设为false后就不会发表,不会生成为一个html页面;
post还有一些独有的预定义变量:
- date:
YYYY-MM-DD HH:MM:SS +/-TTTT
,hours, minutes, seconds, and timezone offset are optional - categories
- tags
defaults
如果有些前言里的变量经常出现,可以使用defaults配置在config里,有需要的时候再override即可。
比如,为本项目下所有的posts类型的文件设置默认的变量layout: "default"
:
1
2
3
4
5
6
7
8
defaults:
-
scope:
path: "" # an empty string here means all files in the project
type: "posts" # previously `post` in Jekyll 2.2.
values:
layout: "default"
author: "puppylpg"
collection
除了jekyll默认创建的pages
/posts
/drafts
三个集合,我们也可以自己创建集合。
自定义一个books集合,自定义permalink,并设置默认的layout为page:
1
2
3
4
5
6
7
8
9
10
11
12
13
collections:
# custom collection type
books:
output: true
permalink: /:collection/:path
defaults:
- scope:
path: _books
type: books
values:
layout: page
comments: true
必须设置output: true
,jekyll才会渲染这个集合里的文件。
从3.7.0起,也可以设置一个collections_dir: my_collections
,然后它下面的每一个文件夹都是collection,比如my_collections/_books
,my_collections/_recipes
,直接就创建出了books和recipes两个collection。与此同时,_posts
等预定义的collection所在的文件,也要放到这个文件夹下面。
默认情况下,posts的遍历顺序是date变量的倒序,其他collection是date的升序(如果没有,就是按他们的path排序)。也可以使用sort_by
自定义排序条件。
比如自定义一个tabs集合,按照front matter里定义的order排序:
1
2
3
4
collections:
tabs:
output: true
sort_by: order
然后给_tabs
下的文件都制定order,比如:
1
2
3
4
5
6
7
8
---
# the default layout is 'page'
icon: fas fa-info-circle
order: 4
---
> 读书破万卷,下笔无有神,但一样快乐!
{: .prompt-tip }
最后做一个页面,把tabs都渲染出来,site.tabs
直接就是按照order升序排列了。
可以自定义一个sidebar,在里面遍历所有的site.tabs
:
1
2
3
4
5
6
7
8
9
10
11
12
13
{% for tab in site.tabs %}
<li class="nav-item{% if tab.url == page.url %}{{ " active" }}{% endif %}">
<a href="{{ tab.url | relative_url }}" class="nav-link">
<i class="fa-fw {{ tab.icon }}"></i>
{% capture tab_name %}{{ tab.url | split: '/' }}{% endcapture %}
<span>{{ site.data.locales[include.lang].tabs.[tab_name] | default: tab.title | upcase }}</span>
</a>
</li>
<!-- .nav-item -->
{% endfor %}
collection variable
collection所有的属性都可以在这里获得,posts是jekyll硬编码的collection,所以遍历collection的时候一定会有posts。
collection的docs
属性返回的是每一篇文档数组,文档有这些属性,比如title
/content
/url
等,可以方便地用于渲染文档的内容。
做个collection目录页
使用刚学的collection知识练练手——
既然定义了多个collection,现在想为每个collection做个目录页。也就是在books页展示books colleciton的所有条目;在life页展示life collection的所有条目。最重要的一步就是获取该页面对应的collection的所有目录。怎么确定该页面对应的collection?有多种办法,比如在该页面的markdown文件的Front Matter里设置一个变量,标记这个页面所属的collection。或者既然_tabs
下的这些文件的文件名本身就对应一个collection,那用文件名标记collection也是不错的办法。
1
2
3
4
5
6
7
{% for collection in site.collections %}
{% assign basename = page.name | split: '.' | first %}
{% if basename == collection.label %}
{% assign sorted = collection.docs | sort: 'date' | reverse %}
{% for collect_doc in sorted %}
- 首先使用
site.collections
获取所有collection; - 然后获取该页面对应的文件名,我们的文件名和collection名称是对应的;
- 接着使用
collection.label
获取当前遍历到的collection的名字,看是否和文件名对应; - 如果对应,那这就是我们要渲染的collection,遍历它的
collection.docs
即可。注意默认是按照date正序,需要改成倒序。
_data
除了jekyll内置的变量、front matter里定义的变量,还可以用_data
来自定义变量。所有放在_data
下面的yaml/json/csv/tsv文件都可以作为变量文件。
比如:
1
2
3
4
5
6
7
8
- name: Eric Mill
github: konklone
- name: Parker Moore
github: parkr
- name: Liu Fengyun
github: liufengyun
就可以通过site.data.members
访问每一个人了:
1
2
3
4
5
6
7
8
9
10
11
<ul>
{% for member in site.data.members %}
<li>
<a href="https://github.com/{{ member.github }}">
{{ member.name }}
</a>
</li>
{% endfor %}
</ul>
在上述sidebar渲染tabs collection的例子中,_data
结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ tree _data
_data
├── locales
│ ├── ar.yml
│ ├── bg-BG.yml
│ ├── cs-CZ.yml
│ ├── de-DE.yml
│ ├── el-GR.yml
│ ├── en.yml
│ ├── es-ES.yml
│ ├── fi-FI.yml
│ ├── fr-FR.yml
│ ├── hu-HU.yml
│ ├── id-ID.yml
│ ├── it-IT.yml
│ ├── ko-KR.yml
│ ├── my-MM.yml
│ ├── pt-BR.yml
│ ├── ru-RU.yml
│ ├── sl-SI.yml
│ ├── sv-SE.yml
│ ├── th.yml
│ ├── tr-TR.yml
│ ├── uk-UA.yml
│ ├── vi-VN.yml
│ ├── zh-CN.yml
│ └── zh-TW.yml
└── origin
├── basic.yml
└── cors.yml
zh-CN
内容示例如下:
1
2
3
4
5
6
7
8
# The tabs of sidebar
tabs:
# format: <filename_without_extension>: <value>
home: 首页
categories: 分类
tags: 标签
archives: 归档
about: 关于
然后就可以使用变量site.data.locales[include.lang].tabs.[tab_name]
获取相应include.lang
对应的locale里tab_name的语言表示了:
1
2
3
4
5
6
7
8
9
10
11
12
13
{% for tab in site.tabs %}
<li class="nav-item{% if tab.url == page.url %}{{ " active" }}{% endif %}">
<a href="{{ tab.url | relative_url }}" class="nav-link">
<i class="fa-fw {{ tab.icon }}"></i>
{% capture tab_name %}{{ tab.url | split: '/' }}{% endcapture %}
<span>{{ site.data.locales[include.lang].tabs.[tab_name] | default: tab.title | upcase }}</span>
</a>
</li>
<!-- .nav-item -->
{% endfor %}
其中:
site.data.locales
来自_data
;include.lang
来自include sidebar.html
时传的参数lang
,就像这样: ``` $ find . -type f -exec grep “include sidebar” {} + ./_layouts/default.html:
1
```
Sass
不懂。
静态文件
静态文件就是没有front matter的文件,可以通过site.static_files
获取。包含这么多属性。
静态文件虽然不能加front matter,但是可以在config的defaults里设置:
1
2
3
4
5
defaults:
- scope:
path: "assets/img"
values:
image: true
然后就可以使用filter把他们过滤出来:
1
2
3
4
5
6
{% assign image_files = site.static_files | where: "image", true %}
{% for myimage in image_files %}
{{ myimage.path }}
{% endfor %}
目录结构
介绍了上面那么多概念,jekyll的目录结构基本都出来了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.
├── _config.yml
├── _data
│ └── members.yml
├── _drafts
│ ├── begin-with-the-crazy-ideas.md
│ └── on-simplicity-in-technology.md
├── _includes
│ ├── footer.html
│ └── header.html
├── _layouts
│ ├── default.html
│ └── post.html
├── _posts
│ ├── 2007-10-29-why-every-programmer-should-play-nethack.md
│ └── 2009-04-26-barcamp-boston-4-roundup.md
├── _sass
│ ├── _base.scss
│ └── _layout.scss
├── _site
├── .jekyll-cache
│ └── Jekyll
│ └── Cache
│ └── [...]
├── .jekyll-metadata
└── index.html # can also be an 'index.md' with valid front matter
每一部分的详细作用可以参考这里。
以.
开头的文件会被jekyll忽略,想加入到构建后的_site
里,需要在config里显式配置include
。同理,想排除的文件需要显式配置exclude
,比如readme、changelog等。
Liquid
Jekyll使用Liquid模板语言引擎来处理模板。
Liquid有三个主要部分:
- Object:object包含liquid要输出到网页上的内容,当被双括号包围时,就会输出该变量的值。比如
{{ page.title }}
; - Tag:
{% if page.show_sidebar %}
,大括号加百分号,用于逻辑控制,html里嵌入代码,相当于JSP; - Filter:竖线,对内容进行处理,相当于lambda里的map函数。用在两个地方:
- 输出,用双括号:
{{ "hi" | capitalize }}
; - 赋值,用括号百分号:
{% assign sorted = collection.docs | sort: 'date' | reverse %}
;
- 输出,用双括号:
这里的tag指的是liquid的tag,即控制流、循环、模板、变量相关的tag,不是jekyll用于支持博客的tag。jekyll对liquid的tag做了扩充,引入了一些内置tag,比如include
。post_url
也是jekyll内置的一个tag(所以它要被大括号和百分号包围)。
在代码高亮上,jekyll内置了highlight
tag(基于Rouge),还支持某些行高亮:
1
2
3
4
5
6
7
{% highlight ruby linenos mark_lines="1 2" %}
def foo
puts 'foo'
end
{% endhighlight %}
实际使用的时候,这些代码不能再被markdown的三撇号包裹,且高亮特定行并没有出现。效果如下:
1
2
3
def foo
puts 'foo'
end
所以不如用markdown的高亮。
jekyll对liquid的filter也做了扩充,引入了一些和路径、日期处理等相关的filter,可以参考这里。
variable
jekyll的variable分为好几类,每一类可用的属性不同:
- site
- page
- layout
- theme
- content
- paginator
也包括之前介绍的collection和doc。
laytout
模板文件,放在_layouts
下。
layout可以堆叠,jekyll称之为继承.
permalink
permalink决定了最终页面的路径。
对post来说,permalink默认是:
1
permalink: /:categories/:year/:month/:day/:title:output_ext
permalink里可用的placeholder在这里,也可以用用一些jekyll预定义好的格式,但是front matter里不支持这种写法,pretty
就是普通的pretty。
但是对post和draft以外的page和collection来说,没有时间和category,所以就等价于/:title:output_ext
。他们可用的placeholder和posts的是不一样的。
permalink在jekyll里的逻辑如下:
- 给某一个page指定一个permalink,那么只要访问这个url,就能到达这个page;
- 然后在我们做的页面上,渲染一个带有
href
的元素,使之可以抵达该页面。对于post来说,就是创建个目录页面,可以遍历site.posts
,然后使用(假设变量是post)post.url
获取其值,作为href
的地址:<a href="{{ post.url | relative_url }}">{{ post.title }}</a>
。
theme
对于各种theme,如果是以gem的形式安装的,我们可以在自己的工程下用同名文件覆盖theme本身的文件。
Jekyll will look first to your site’s content before looking to the theme’s defaults for any requested file in the following folders: /assets
、/_data
、/_layouts
、/_includes
、/_sass
。
感想
后面可以考虑搞个ci,自动部署到自己的小服务器上。
本文由作者按照 CC BY 4.0 进行授权