Jekyll有一个带有钩子的插件系统,允许您创建特定于您的网站的自定义生成内容。您可以为您的网站运行自定义代码,而无需修改 Jekyll 源代码本身。
注意:
您可以在 _config.yml
中的 whitelist
“键” 中添加特定的插件,以允许它们在安全模式下运行。
Jekyll内置支持使用插件来扩展核心功能。
主要是,任何扩展名为 .rb
的文件都将在一个 build session 期间自动加载,该文件位于站点 source
的根目录的 _plugins
目录中。
此行为可以配置如下:
_plugins
目录。safe
模式下运行时,_plugins
目录中的插件(或其等效目录)将不会加载。要使用打包为 gems 的插件,必须在名为 plugins
的顶级"键"下的配置文件中列出所需的 gems 。
此外,如果您是在 safe
模式下构建的,那么 gem 需要列在名为 whitelist
的顶级"键"下。例如:
plugins:
- jekyll-gist
- jekyll-coffeescript
- jekyll-seo-tag
- some-other-jekyll-plugin
# Enable safe mode
safe: true
# Whitelist plugins under safe mode.
# Note that `some-other-jekyll-plugin` is not listed here. Therefore,
# it will not be loaded under safe mode.
whitelist:
- jekyll-gist
- jekyll-coffeescript
- jekyll-seo-tag
在没有 Gemfile 的情况下,必须手动确保在调用 Jekyll 之前已经安装了列出的插件。例如,上面列表中的 gems 的最新版本可以通过运行以下指令安装到系统范围的位置:
gem install jekyll-gist jekyll-coffeescript jekyll-remote-theme some-other-jekyll-plugin
通过将 Gemfile(通常位于站点 source 的根目录)与名为 bundler
的 Rubygem 结合使用,可以大大简化各种 gem 依赖项的维护。然而,Gemfile 应该列出网站的所有主要依赖项,包括 Jekyll 本身,而不仅仅是网站的 gem-based 插件,因为 Bundler 将已安装的 gems 的范围缩小到仅仅是"运行时依赖项"(通过评估 Gemfile 来解决)。例如:
source "https://rubygems.org"
# Use the latest version.
gem "jekyll"
# The theme of current site, locked to a certain version.
gem "minima", "2.4.1"
# Plugins of this site loaded during a build with proper
# site configuration.
gem "jekyll-gist"
gem "jekyll-coffeescript"
gem "jekyll-seo-tag", "~> 1.5"
gem "some-other-jekyll-plugin"
# A dependency of a custom-plugin inside `_plugins` directory.
gem "nokogiri", "~> 1.11"
Gemfile 中列出的 gems 可以通过简单地运行 bundle install
来共同安装。
:jekyll_plugins
Gemfile groupJekyll 对 Gemfile 中作为 :jekyll_plugins
"组"的一部分而列出的 gems 进行特殊处理。该组下的任何 gem 都是在任何 Jekyll 进程的一开始加载的,而不考虑 --safe
CLI flag 或配置文件中的条目。
虽然此途径允许使用额外的子命令和选项来增强 Jekyll 的 CLI ,或者避免在配置文件中列出 gems ,但缺点是需要注意"组"中包含哪些 gems 。例如:
source "https://rubygems.org"
# Use the latest version.
gem "jekyll"
# The theme of current site, locked to a certain version.
gem "minima", "2.4.1"
# Plugins of this site loaded only if configured correctly.
gem "jekyll-gist"
gem "jekyll-coffeescript"
# Gems loaded irrespective of site configuration.
group :jekyll_plugins do
gem "jekyll-cli-plus"
gem "jekyll-seo-tag", "~> 1.5"
gem "some-other-jekyll-plugin"
end
GitHub Pages 由 Jekyll 提供支持。所有 GitHub Pages 网站都是使用 --safe
选项生成的,以出于安全原因禁用插件(一些白名单插件除外)。不幸的是,这意味着如果你通过 GitHub Pages 进行部署,你的插件将无法工作。
你仍然可以使用 GitHub Pages 发布你的网站,但你需要在本地构建网站,并将生成的文件推送到你的GitHub存储库,而不是Jekyll 源文件。
如果您愿意,您可以在同一个站点同时使用上述任何插件渠道。其中一个的使用并不限制其他的使用。
插件允许您扩展 Jekyll 的行为以满足您的需求。Jekyll 中有六种类型的插件。
Generators 在您的网站上创建内容。例如:
Converters 将标记语言更改为另一种格式。例如:
Commands 使用子命令扩展 jekyll
可执行文件。例如:
Tags 创建自定义 Liquid tags. 例如:
Filters create custom Liquid filters. For example:
Hooks 提供细粒度的控制来扩展构建过程。例如:
编写插件时需要注意两个标志:
Flag | Description |
---|---|
| 一个布尔标志,用于通知 Jekyll 此插件是否可以在不允许执行任意代码的环境中安全执行。GitHub Pages使用它来确定哪些核心插件可以使用,哪些运行不安全。如果您的插件不允许执行任意代码,请将其设置为 |
| 此标志决定插件的加载顺序。 有效的值为: |
要使用上面的一个示例插件作为示例,以下是您应该如何指定这两个标志:
module Jekyll
class UpcaseConverter < Converter
safe true
priority :low
...
end
end
这些指南帮助您了解创建插件的细节。我们还推荐了一些最佳实践来帮助构建您的插件。
我们建议您的插件使用 gem 。这将帮助您管理依赖关系,与站点源代码保持分离,并允许您在多个项目之间共享功能。有关创建 gem 的小贴士,请参阅 Ruby gems guide 或查看现有插件(如 jekyll-feed )的源代码。
当您需要Jekyll根据您自己的规则创建其他内容时,您可以创建一个生成器。
生成器是 Jekyll::Generator
的一个子类,它定义了一个 generate
方法,该方法接收 Jekyll::Site
的一个实例。generate
的返回值被忽略。
在 Jekyll 对现有内容进行盘点后,在网站生成之前,生成器会运行。有 front matter 的页面被存储为 Jekyll::Page
的实例,可通过 site.pages
获取。静态文件成为 Jekyll::StaticFile
的实例,并可通过 site.static_files
获取。有关详细信息,请参阅the Variables documentation page 和 Jekyll::Site
。
在以下示例中,生成器将注入"在构建时为模板变量计算"的值。名为 reading.html
的模板有两个未定义变量 ongoing
和 done
,它们将在生成器运行时被定义或分配值:
module Reading
class Generator < Jekyll::Generator
def generate(site)
book_data = site.data['books']
ongoing = book_data.select { |book| book['status'] == 'ongoing' }
done = book_data.select { |book| book['status'] == 'finished' }
# get template
reading = site.pages.find { |page| page.name == 'reading.html'}
# inject data into template
reading.data['ongoing'] = ongoing
reading.data['done'] = done
end
end
end
以下示例是一个更复杂的生成器,用于生成新页面。
在本例中,生成器的目的是为 site
中注册的每个 category 创建一个页面。页面是在运行时创建的,因此它们的内容、front matter 和其他属性需要由插件本身设计。
index.html
。type
将是有益的。module SamplePlugin
class CategoryPageGenerator < Jekyll::Generator
safe true
def generate(site)
site.categories.each do |category, posts|
site.pages << CategoryPage.new(site, category, posts)
end
end
end
# Subclass of `Jekyll::Page` with custom method definitions.
class CategoryPage < Jekyll::Page
def initialize(site, category, posts)
@site = site # the current site instance.
@base = site.source # path to the source directory.
@dir = category # the directory the page will reside in.
# All pages have the same filename, so define attributes straight away.
@basename = 'index' # filename without the extension.
@ext = '.html' # the extension.
@name = 'index.html' # basically @basename + @ext.
# Initialize data hash with a key pointing to all posts under current category.
# This allows accessing the list in a template via `page.linked_docs`.
@data = {
'linked_docs' => posts
}
# Look up front matter defaults scoped to type `categories`, if given key
# doesn't exist in the `data` hash.
data.default_proc = proc do |_, key|
site.frontmatter_defaults.find(relative_path, :categories, key)
end
end
# Placeholders that are used in constructing page URL.
def url_placeholders
{
:path => @dir,
:category => @dir,
:basename => basename,
:output_ext => output_ext,
}
end
end
end
生成的页面现在可以被设置为使用 destination 目录中特定路径的"特定布局或输出",所有这些都是通过"使用 front matter defaults" 的配置文件实现的。例如:
# _config.yml
defaults:
- scope:
type: categories # select all category pages
values:
layout: category_page
permalink: categories/:category/
Generators 只需要实施一种方法
Method | Description |
---|---|
| Generates content as a side-effect. |
如果生成器包含在单一文件中,则可以根据需要对其进行命名,但扩展名应为 .rb
。如果您的生成器被拆分到多个文件中,则应该将其打包为Rubygem,以便在 https://rubygems.org/
发布。在这种情况下,gem 的名称取决于该站点名称的可用性,因为不存在两个 gems 可以具有相同的名称。
默认情况下,Jekyll 在 _plugins
目录中查找生成器。但是,您可以通过将所需的名称分配给配置文件中的"键" plugins_dir
来更改默认目录。
如果您有一种新的标记语言要用于您的网站,您可以通过实现自己的转换器来包含它。Markdown 和 Textile 标记语言都是使用这种方法实现的。
Jekyll 只转换顶部有 YAML 头的文件,即使是使用插件添加的转换器。
下面是一个转换器,它将接收所有以 .upcase
结尾的文章,并使用 UpcaseConverter
进行处理:
module Jekyll
class UpcaseConverter < Converter
safe true
priority :low
def matches(ext)
ext =~ /^\.upcase$/i
end
def output_ext(ext)
".html"
end
def convert(content)
content.upcase
end
end
end
转换器应至少实现3种方法:
Method | Description |
---|---|
| 给定的扩展是否与此转换器的可接受扩展列表匹配?采用一个参数:文件的扩展名(包括那个点)。如果匹配,则必须返回 |
| 要指定给输出文件的扩展名(包括点)。通常这将是 |
| 进行"内容转换"的逻辑。采用一个参数:文件的原始内容(没有 front matter)。必须返回一个字符串。 |
在我们的示例中,UpcaseConverter#matches
会检查我们的文件扩展名是否为 .upcase
,如果是,则会使用转换器进行渲染。它会调用 UpcaseConverter#convert
来处理内容。在我们的简单转换器中,我们只是对整个内容字符串进行大写。最后,当它保存页面时,它将使用 .html
扩展名进行保存。
从 2.5.0 版本开始,Jekyll 可以通过插件进行扩展,这些插件为 jekyll
可执行文件提供子命令。这可以通过将相关插件包含在名为 :jekyll_plugins
的 Gemfile
组中来实现:
group :jekyll_plugins do
gem "my_fancy_jekyll_plugin"
end
每个 Command
必须是 Jekyll::Command
类的子类,并且必须包含一个类方法:init_with_program
。例如:
class MyNewCommand < Jekyll::Command
class << self
def init_with_program(prog)
prog.command(:new) do |c|
c.syntax "new [options]"
c.description 'Create a new Jekyll site.'
c.option 'dest', '-d DEST', 'Where the site should go.'
c.action do |args, options|
Jekyll::Site.new_site_at(options['dest'])
end
end
end
end
end
命令应实现此"单类"方法:
Method | Description |
---|---|
init_with_program | 这个方法接受一个参数,Mercenary::Program 实例,它就是 Jekyll 程序本身。在程序上,可以使用上述语法来创建命令。有关更多详细信息,请访问 GitHub.com 上的 Mercenary 仓库。 |
如果你想在你的网站中包含自定义的 liquid tags ,你可以通过连接 tagging 系统来实现。Jekyll 添加的内置示例包括highlight
和 include
tags 。以下是一个自定义 liquid tag 的示例,该 tag 将输出页面被渲染的时间:
module Jekyll
class RenderTimeTag < Liquid::Tag
def initialize(tag_name, text, tokens)
super
@text = text
end
def render(context)
"#{@text} #{Time.now}"
end
end
end
Liquid::Template.register_tag('render_time', Jekyll::RenderTimeTag)
liquid tags 至少必须实现:
Method | Description |
---|---|
| Outputs the content of the tag. |
您还必须向 Liquid 模板引擎注册自定义 tag ,如下所示:
Liquid::Template.register_tag('render_time', Jekyll::RenderTimeTag)
在上面的例子中,我们可以在其中一个页面的任何位置放置以下 tag :
<p>{% render_time page rendered at: %}</p>
我们会在页面上看到这样的内容:
<p>page rendered at: Tue June 22 23:38:47 –0500 2010</p>
上面看到的 render_time
tag 也可以通过继承 Liquid::Block
类重写为 tag 块。看看下面的例子:
module Jekyll
class RenderTimeTagBlock < Liquid::Block
def render(context)
text = super
"<p>#{text} #{Time.now}</p>"
end
end
end
Liquid::Template.register_tag('render_time', Jekyll::RenderTimeTagBlock)
我们现在可以在任何地方使用 tag 块:
{% render_time %}
page rendered at:
{% endrender_time %}
我们仍然可以在页面上获得与上面相同的输出:
<p>page rendered at: Tue June 22 23:38:47 –0500 2010</p>
注意:
在上面的示例中,tag 块和 tag 都使用名称 render_time
注册,但不建议在同一项目中使用相同名称注册 tag 和 tag 块,因为这可能会导致冲突。
Filters 是一系列的模块,该模块用于将其方法导出为 liquid 。所有方法都必须采用至少一个"表示过滤器输入"的参数。返回值将是过滤器的输出。
module Jekyll
module AssetFilter
def asset_url(input)
"http://www.example.com/#{input}?#{Time.now.to_i}"
end
end
end
Liquid::Template.register_filter(Jekyll::AssetFilter)
有关创建自定义 Liquid Filters 的更多详细信息,请访问 Liquid docs 。
Jekyll 允许您通过 @context.registers[:site]
中 Liquid 的 @context.registers
功能访问 site
对象。例如,您可以使用@context.registers[:site].config
访问全局配置文件 _config.yml
。
使用钩子,插件可以对构建过程的各个方面进行细粒度的控制。如果你的插件定义了任何钩子,Jekyll 会在预定义的点调用它们。
Hooks 被注册到所有者和事件名称。要注册一个,请调用 Jekyll::Hooks.register
,并传递钩子所有者、事件名称和代码,以便在触发钩子时调用。例如,如果你想在每次 Jekyll 渲染页面时执行一些自定义功能,你可以注册一个钩子,如下所示:
Jekyll::Hooks.register :pages, :post_render do |page|
# code to call after Jekyll renders a page
end
注意:下面提到的 :post_convert
事件是 v4.2.0 中引入的一个功能。
开箱即用,Jekyll为所有者预先定义了 hook 点::site
, :pages
, :documents
和 :clean
。此外,为 :documents
定义的 hook 点只能通过调用集合类型来用于单个集合。即::posts
用于 collection _posts
中的文档和 :movies
用于 collection _movies
中的文档。在所有情况下,Jekyll 都会使用 owner 对象作为第一个回调参数来调用钩子。
每个注册的 hook 所有者都支持以下事件 - :post_init
, :pre_render
, :post_convert
, :post_render
, :post_write
- 但是,:site
所有者被设置为响应"特殊的事件名称"。有关详细信息,请参阅下一节。
所有的 :pre_render
钩子和 :site, :post_render
钩子也将提供一个 payload
hash 作为第二个参数。在 :pre_render
事件的情况下,payload 使您能够完全控制渲染期间可用的变量,而在 :site, :post_render
事件中,payload 包含渲染所有站点后的最终值(对 sitemaps, feeds 等有用)。
可用钩子的完整列表:
Owner | Event | Triggered at |
---|---|---|
Encompasses the entire site |
| Just after the site initializes. Good for modifying the configuration of the site. Triggered once per build / serve session |
| Just after the site resets during regeneration | |
| After all source files have been read and loaded from disk | |
| Just before rendering the whole site | |
| After rendering the whole site, but before writing any files | |
| After writing all of the rendered files to disk | |
Allows fine-grained control over all pages in the site |
| Whenever a page is initialized |
| Just before rendering a page | |
| After converting the page content, but before rendering the page layout | |
| After rendering a page, but before writing it to disk | |
| After writing a page to disk | |
Allows fine-grained control over all documents in the site including posts and documents in user-defined collections |
| Whenever any document is initialized |
| Just before rendering a document | |
| After converting the document content, but before rendering the document layout | |
| After rendering a document, but before writing it to disk | |
| After writing a document to disk | |
Allows fine-grained control over all posts in the site without affecting documents in user-defined collections |
| Whenever a post is initialized |
| Just before rendering a post | |
| After converting the post content, but before rendering the postlayout | |
| After rendering a post, but before writing it to disk | |
| After writing a post to disk | |
Fine-grained control on the list of obsolete files determined to be deleted during the site's cleanup phase. |
| During the cleanup of a site's destination before it is built |
您还可以为插件引入的 Jekyll 对象注册并触发钩子。所需要的就是将 trigger
调用放在一个合适的 owner
名称下,放在自定义类中所需的位置上,并通过插件注册 owner
。
为了进行说明,请考虑以下插件,该插件为初始化的每个自定义 Excerpt
对象实现自定义功能:
module Foobar
class HookedExcerpt < Jekyll::Excerpt
def initialize(doc)
super
trigger_hooks(:post_init)
end
def output
@output ||= trigger_hooks(:post_render, renderer.run)
end
def renderer
@renderer ||= Jekyll::Renderer.new(
doc.site, self, site.site_payload
)
end
def trigger_hooks(hook_name, *args)
Jekyll::Hooks.trigger :excerpts, hook_name, self, *args
end
end
end
Jekyll::Hooks.register :excerpts, :post_init do |excerpt|
Jekyll.logger.debug "Initialized:",
"Hooked Excerpt for #{excerpt.doc.inspect}"
end
Jekyll::Hooks.register :excerpts, :post_render do |excerpt, output|
return output unless excerpt.doc.type == :posts
Foobar.transform(output)
end