Published 2022-03-27.
Last modified 2022-04-24.
Time to read: 7 minutes.
jekyll
collection.
This article discusses the various types of Jekyll plugins, including filters, generators, tags, block tags and hooks. It is intended to provide background information.
The follow-on articles build upon the information presented here.
Plugin Types
Jekyll supports the following types of plugins. This article discusses and demonstrates templates for all of these types of plugins.
- Generators – Create additional content on your site. This type of plugin is often surprisingly short, however documentation is scant.
- Converters – Convert a page written in a markup language into another format, usually HTML. This is one of the least common types of plugins.
- Commands – Extend the jekyll executable with subcommands. This is one of the least common types of plugins. Documentation is insufficient to make a useful plugin. Very few working examples exist, and they are complex.
- Tags – Create custom Liquid tags.
Tags are perhaps the most common types of Jekyll plugins.
However, judging by the currently available tag plugins (other than those provided by GitHub),
parameter parsing can be a challenge for programmers.
Read on, I will address this next.
2 types of tags exist:
- Inline tags – can accept parameters.
- Block tags – can accept parameters and a content body
- Filters – Liquid filters are methods that take one or more parameters and return a value. This is the easiest type of Jekyll plugin to write, and they are perhaps the second most common type of Jekyll plugin.
- Hooks – Fine-grained control to extend the build process. Documentation on these types of plugins is scant. These plugins are often quite short and easy to develop if you have access to good information. Good thing you found this article!
Debugging Plugins
The official Jekyll documentation does not provide much guidance about how to debug Jekyll plugins. Please read my article on Debugging Jekyll Plugins. In this article, and the follow-on article, I provide debugging scripts for the plugins shown. You can use those scripts on your Jekyll plugin projects.
Defining Several Plugins Together
This section discusses how to package more than one plugin in a Gem.
Normally, only one instance of any type of plugin is defined in a Jekyll plugin project. However, you can define as many as you wish. For example, a project could define various tags, tag blocks, generators and hooks.
All that is necessary is to require
the files defining each of your plugins,
then include
them into a module that is loaded by Jekyll.
The name of the module does not matter, so long as it is unique.
For example, if your Jekyll project has a Gemfile
that contains the following:
group :jekyll_plugins do gem 'jekyll_x' end
... then Jekyll will load your plugin called plugin_x
by
include
ing a file in the jekyll_x
plugin called lib/plugin_x.rb
.
My all_collections
plugin project
defines a Jekyll hook and a Jekyll tag, like this:
require_relative 'all_collections_hooks' require_relative 'all_collections_tag'
module JekyllAllCollections include AllCollectionsHooks include AllCollectionsTag end
Tag Parameter Parsing
Block tag and inline tag plugins often require parameter parsing. I have never seen good parameter parsing for Jekyll tag plugins. Most tag plugins either have no need to pass arguments, or the parameters are merely keywords (which are easy to parse), or Yet Another Awful Parser (YAAP) is built.
It is actually quite easy to perform proper parameter parsing once you know how. The GitHub project in the follow-on article uses the technique described here.
Here is an example of parameters that requires parsing:
{% my_tag_plugin param1=value1 param2='value 2' param3="value 3" %}
The name/value pairs for param1/value1
, param2/value 2
and param3/value 3
are parsed and provided to your plugin without having to do anything special.
Shellwords
Let’s assume that the parameters highlighted above are provided to the Jekyll tag plugin as a string called argument_string
.
First we tokenize name/value pairs by using Shellwords
:
argv = Shellwords.split(argument_string)
Shellwords
is part of the standard Ruby runtime library.
It manipulates strings according to the word parsing rules of the UNIX Bourne shell.
That just means that Shellwords
creates an array of tokens from the command line string.
Shellwords
recognizes quoted strings, using either single quotes or double quotes.
Below is a small example demonstration program, followed by its output.
The flavor of Ruby heredoc used, called a
squiggly heredoc,
strips leading whitespace between END_STRING
delimiters,
and does not require quotes to be escaped.
require "shellwords" string = <<~END_STRING a b=c d='e f' g="h i j" END_STRING puts Shellwords.split(string)
Each token is displayed on a separate line. Let's run the program and see the output.
$ ruby shellwords.rb a b=c d=e f g=h i j
KeyValueParser
For extracting key/value pairs from the tokens returned by Shellwords
,
we use KeyValueParser
,
an incredibly versatile yet easy to use Ruby gem.
KeyValueParser
accepts the array of tokens from Shellwords
and returns a hash[Symbol, String]
,
called params
in the following code.
require 'key_value_parser'
# argv was returned by Shellwords, above params = KeyValueParser.new.parse(argv)
# Example of obtaining the value of parameter param1 @param1 = params[:param1]
# The value of non-existant keys is nil, # which often displays as the empty string @param_x = params[:not_present]
The RSpec unit test
for jekyll_plugin_template
demonstrates how KeyValueParser
is used in conjunction with Shellwords
:
require 'key_value_parser' require 'shellwords'
params = "param0 param1=value1 param2='value2' param3=\"value3's tricky\"" argv = Shellwords.split params options = KeyValueParser.new.parse(argv)
expect(options[:param0]).to eq(true) expect(options[:param1]).to eq("value1") expect(options[:param2]).to eq("value2") expect(options[:param3]).to eq("value3's tricky") expect(options[:unknown]).to be_nil
Variables
Most of this section pertains to tag plugins, but this information can also be useful for most of the other types of plugins.
Jekyll variables can be defined in various places.
I believe that each of these places is what is meant by a scope
...
but because that term is never defined, I cannot be 100% sure.
- In a page content, via a liquid
assign
orcapture
statement. - In the front matter of a page
- In an include's parameters
- In a layout
-
Jekyll also exposes a few internal variables, such as
site
andpage
. They can be retrieved from therender
method usingliquid_context.registers[:site]
andliquid_context.registers[:page]
.
Additionally, name/value pairs are available from the YAML data in _config.yml
and data
.
Configuration data can be retrieved by site.config['my_config_variable']
.
When referencing a variable from an include
,
prepend the scope to the variable name.
For example: layout.compress
, include.param1
,
page.date
, or my_var
.
The Jekyll documentation does not provide any guidance about how a plugin would to evaluate a variable reference. Here is what I've learned:
-
Variables defined in the content of pages can be retrieved as hash values in from the
render
method's parameter, which is of typeLiquid::Context
. For example:
my_tag.rbdef render(liquid_context) my_var = liquid_context['my_var'] end
-
Variables defined in front matter can be retrieved from the
:page
context, which has typeJekyll::Drops::DocumentDrop
. For example:
my_tag.rbdef render(liquid_context) page = liquid_context.registers[:page] layout = page['layout'] end
-
Is this true?
Variables passed from an include parameter can be
fetch
ed from theliquid_context
within theregister
method like the following. Note thatfetch
provides for a default value, which isfalse
in this example:
my_tag.rbdef render(liquid_context) do_not_escape = liquid_context['include'] .fetch('do_not_escape', 'false') end
-
Variables defined in a layout template used by a page can be retrieved from within the
register
method like the following:
my_tag.rbdef render(liquid_context) env = liquid_context.environments.first layout_hash = env['layout'] layout_hash['compress'] end
Plugins may receive variables as arguments, if they are enclosed in double curly braces.
--- front_matter_variable: Provided in front matter --- {% assign page_variable = "page variable value" %} {% block_tag_template param1="provided as string" param2='{{page_variable}}' param3="{{front_matter_variable}}" param4='{{page.last_modified_at}}' param5='{{layout.compress}}' %} This is the block_tag_template content. It includes {{front_matter_variable}} variable data. {% endblock_tag_template %}
Bonus! You can examine all of the attributes of a page except content
and next
as follows.
(You probably want to skip the content
and next
attributes because they will fill up your screen.)
def render(liquid_context) puts liquid_context.registers[:page] .sort .reject { |k, _| ["content", "next"].include? k } .map { |k, v| "#{k}=#{v}" } .join("\n ") end
Here is sample output:
categories=["Jekyll"] collection=posts date=2022-03-28 00:00:00 -0400 description=Just a draft test article I wrote draft=true excerpt=<h2>Hello, World!</h2> ext=.html front_matter_variable=Provided in front matter id=/blog/2022/03/28/test2 last_modified_at=2022-04-12 layout=default output= path=_drafts/2022/2022-05-01-test2.html previous= relative_path=_drafts/2022/2022-05-01-test2.html slug=test2 tags=[] title=Test Draft Post url=/blog/2022/03/28/test2.html
Scope Control
Some types of plugins, such as tags and filters, automatically have a restricted scope; they only act on their arguments. Other types of plugins, such as converters, generators, and hooks, have a much larger scope; they either act on the entire website, or all webpages within a certain category, for example all blog pages. I will show you some ways of efficiently restricting the scope of hooks so only selected webpages are processed, or even just selected portions of designated webpages.
Jekyll process Steps
The types of hooks follow the major processing phases that Jekyll follows, as defined in
Jekyll/site.rb
in Site.process
:
def process return profiler.profile_process if config["profile"] reset read generate render cleanup write end
Jekyll tag plugins, and all Liquid constructs, are processed in the render
phase.
That means the content passed to :site :post_render
hooks contains the output of Jekyll tag plugins.
Generator Invocations
The generate
method invokes all generators.
It is called after read
, but before render
.
generate
looks like this:
# Run each of the Generators. # # Returns nothing. def generate generators.each do |generator| start = Time.now generator.generate(self) Jekyll.logger.debug "Generating:", "#{generator.class} finished in #{Time.now - start} seconds." end nil end
This means that generators inherit the global state right after the reset
and
read
hooks trigger, but before the render
,
cleanup
and write
hooks trigger.
This also means that any changes that generators make to global state are only visible in the render
,
cleanup
and write
hooks.
Tag Invocations
Tags get called by the call sequence initiatated by Site.render
,
by the invocation of document.renderer.run
in
Site.render_regenerated
,
just before the :site :post_render
triggers.
def render_regenerated(document, payload) return unless regenerator.regenerate?(document)
document.renderer.payload = payload document.output = document.renderer.run document.trigger_hooks(:post_render) end
Cleanup Invocations
No mention of the purpose of the :cleanup
hook is provided in the official documentation.
Looking at the Jekyll source code reveals that :cleanup
is where unnecessary files
are removed from the _site
under construction,
prior to writing out the site.
Site Payload
The site payload is defined in Jekyll/site.rb
in Site.site_payload
# The Hash payload containing site-wide data. # # Returns the Hash: { "site" => data } where data is a Hash with keys: # "time" - The Time as specified in the configuration or the # current time if none was specified. # "posts" - The Array of Posts, sorted chronologically by article date # and then title. # "pages" - The Array of all Pages. # "html_pages" - The Array of HTML Pages. # "categories" - The Hash of category values and Posts. # See Site#post_attr_hash for type info. # "tags" - The Hash of tag values and Posts. # See Site#post_attr_hash for type info. def site_payload Drops::UnifiedPayloadDrop.new self end alias_method :to_liquid, :site_payload
Hooks
This is the Jekyll method that is invoked by the 45 examples of hooks described next.
def self.register(owners, event, priority: DEFAULT_PRIORITY, &block) Array(owners).each do |owner| register_one(owner, event, priority_value(priority), &block) end end
-
There are 45 valid combination of
owners
andevent
. I will introduce those parameters, provide code examples for all of them, discuss when they should be used, and offer suggestions of how to best use them. Jekyll internally defines additional hooks, for example:site :post_convert
, but only the documented hooks are exposed to plugin developers. -
Notice that the
register
method accepts a block. We can use a RubyProc
to supply the block to consolidate code across multiple hooks. I provide examples of this in the Nugem: Custom Rails & Jekyll Plugins.
45 Plugin Hooks
Jekyll’s built-in hook ‘owners
’s are
:site
, :pages
, :documents
, :posts
,
and :clean
.
The core events are :post_init
, :pre_render
, :post_convert
,
:post_render
and :post_write
.
There are 45 valid combinations of owners
and event
parameters to
Jekyll::
.
Jekyll’s hook event types vary between owners.
The 5 core event types mentioned above pertain to 3 types of owner
s –
:pages
, :documents
, and :posts
.
This core set of hooks is embellished for owner
:site
,
and does not pertain to owner
:clean
.
The :site
owner has event types that are similar, but not identical to other event owners
.
Owner
:site
has 6 combinations, 3 of which are unique.
Owner
:clean
has only one combination.
We will see the details in a moment.
Following is the source file containing short working examples all 45 of the standard Jekyll hooks, and an explanation of the key features.
require 'jekyll_plugin_logger' require_relative 'jekyll_plugin_template/version' require_relative 'dumpers' module JekyllPluginHooksName PLUGIN_NAME = 'jekyll_plugin_hooks' end # The Jekyll processing steps are described in https://jekyllrb.com/tutorials/orderofinterpretation/ # # The Jekyll log level defaults to :info, which means all the Jekyll.logger statements below will not generate output. # You can control the log level when you start Jekyll. # To set the log level to :debug, write an entery into _config.yml, like this: # plugin_loggers: # JekyllPluginHooks: debug # # Jekyll::Hooks.register accepts an optional parameter: # :priority determines the load order for the hook plugins. # Valid values are: :lowest, :low, :normal, :high, and :highest. # Highest priority matches are applied first, lowest priority are applied last. # The default value is :normal # # Each hook, except the clean hook, can set a boolean flag, called `site.safe`, that informs Jekyll if this plugin may be safely executed in an environment # where arbitrary code execution is not allowed. This is used by GitHub Pages to determine which # core plugins may be used, and which are unsafe to run. If your plugin does not allow for arbitrary # code execution, set this to true. GitHub Pages still will not load your plugin, but if you submit it # for inclusion in core, it is best for this to be correct! # Default value is false. # The hooks for pages, posts and documents access safe via pages.site.safe, posts.site.safe and documents.site.safe, respectively. module JekyllPluginHooks ########## :site hooks # These hooks influence the entire site # Called just after the site resets during regeneration # This is the first hook called, so you might think that this is the best place to define loggers. # However, this hook will not be called unless safe mode is OFF, so define loggers in the :site :after_init hook instead Jekyll::Hooks.register(:site, :after_reset, priority: :normal) do |site| @log_site ||= PluginMetaLogger.instance.new_logger(:SiteHooks, PluginMetaLogger.instance.config) @log_site.info { 'Jekyll::Hooks.register(:site, :after_reset) invoked.' } Dumpers.dump_site(@log_site, 'Jekyll::Hooks.register(:site, :after_reset)', site) end # This hook is called just after the site initializes. # It is a good place to modify the configuration of the site. # This hook is triggered once per build / serve session. Jekyll::Hooks.register(:site, :after_init, priority: :normal) do |site| @log_clean = PluginMetaLogger.instance.new_logger(:CleanHook, PluginMetaLogger.instance.config) @log_docs = PluginMetaLogger.instance.new_logger(:DocumentHooks, PluginMetaLogger.instance.config) @log_pages = PluginMetaLogger.instance.new_logger(:PageHooks, PluginMetaLogger.instance.config) @log_posts = PluginMetaLogger.instance.new_logger(:PostHooks, PluginMetaLogger.instance.config) @log_site ||= PluginMetaLogger.instance.new_logger(:SiteHooks, PluginMetaLogger.instance.config) @log_site.info { "Loaded #{JekyllPluginHooksName::PLUGIN_NAME} v#{JekyllPluginTemplateVersion::VERSION} plugin." } @log_site.info { 'Jekyll::Hooks.register(:site, :after_init) invoked.' } Dumpers.dump_site(@log_site, 'Jekyll::Hooks.register(:site, :after_init)', site) end # Called after all source files have been read and loaded from disk. # This is a good hook for enriching posts; # for example, adding links to author pages or adding posts to author pages. Jekyll::Hooks.register(:site, :post_read, priority: :normal) do |site| @log_site.info { 'Jekyll::Hooks.register(:site, :post_read) invoked.' } Dumpers.dump_site(@log_site, 'Jekyll::Hooks.register(:site, :post_read)', site) end # Called before rendering the whole site # This is the first hook in the site generation sequence where site['env'] has a value. # Consequently, this is the first hook that defines mode (production, development or test), # because it is derived from site['env']['JEKYLL_ENV'] # @param payload [Hash] according to the docs, payload is a hash containing the variables available during rendering; the hash can be modified here. # However, the debugger shows payload has type Jekyll::UnifiedPayloadDrop Jekyll::Hooks.register(:site, :pre_render, priority: :normal) do |site, payload| @log_site.info { 'Jekyll::Hooks.register(:site, :pre_render) invoked.' } @log_site.debug { dump(':site, :pre_render payload', payload) } Dumpers.dump_site(@log_site, 'Jekyll::Hooks.register(:site, :pre_render)', site) Dumpers.dump_payload(@log_site, 'Jekyll::Hooks.register(:site, :pre_render)', payload) end # Called after rendering the whole site, but before writing any files. # Functionally, this hook is exactly the same as a Jekyll generator. # This hook is also similar to invoking the same method on the :post_render hooks for :documents and :pages: # Jekyll::Hooks.register(:documents, :post_render, &my_method) # Jekyll::Hooks.register(:pages, :post_render, &my_method) # ... with the difference that this hook will be called only once, for the entire site, so you will have to iterate over all of the # :documents and :pages, whereas the :pages and :documents hooks are called once for each page and document. # @param payload [Hash] contains final values of variables after rendering the entire site (useful for sitemaps, feeds, etc). Jekyll::Hooks.register(:site, :post_render, priority: :normal) do |site, payload| @log_site.info { 'Jekyll::Hooks.register(:site, :post_render) invoked.' } @log_site.debug { dump(':site, :post_render payload', payload) } Dumpers.dump_site(@log_site, 'Jekyll::Hooks.register(:site, :post_render)', site) Dumpers.dump_payload(@log_site, 'Jekyll::Hooks.register(:site, :post_render)', payload) end # Called after writing all of the rendered files to disk Jekyll::Hooks.register(:site, :post_write, priority: :normal) do |site| @log_site.info { 'Jekyll::Hooks.register(:site, :post_write) invoked.' } Dumpers.dump_site(@log_site, 'Jekyll::Hooks.register(:site, :post_write)', site) end ########## :pages hooks # Pages are web pages that do not belong to a collection, such as posts or drafts. # These hooks provide fine-grained control over all pages in the site. # Called whenever a page is initialized Jekyll::Hooks.register(:pages, :post_init, priority: :normal) do |page| @log_pages.info { 'Jekyll::Hooks.register(:pages, :post_init) invoked.' } Dumpers.dump_page(@log_pages, 'Jekyll::Hooks.register(:pages, :post_init)', page) end # Called just before rendering a page Jekyll::Hooks.register(:pages, :pre_render, priority: :normal) do |page, payload| @log_pages.info { 'Jekyll::Hooks.register(:pages, :pre_render) invoked.' } Dumpers.dump_page(@log_pages, 'Jekyll::Hooks.register(:pages, :pre_render)', page) Dumpers.dump_payload(@log_pages, ':pages, :pre_render payload', payload) end # Called after converting the page content, but before rendering the page layout Jekyll::Hooks.register(:pages, :post_convert, priority: :normal) do |page| @log_pages.info { 'Jekyll::Hooks.register(:pages, :post_convert) invoked.' } Dumpers.dump_page(@log_pages, 'Jekyll::Hooks.register(:pages, :post_convert)', page) end # Called after rendering a page, but before writing it to disk Jekyll::Hooks.register(:pages, :post_render, priority: :normal) do |page| page.site.safe = true @log_pages.info { 'Jekyll::Hooks.register(:pages, :post_render) invoked.' } Dumpers.dump_page(@log_pages, 'Jekyll::Hooks.register(:pages, :post_render)', page) end # Called after writing a page to disk Jekyll::Hooks.register(:pages, :post_write, priority: :normal) do |page| @log_pages.info { 'Jekyll::Hooks.register(:pages, :post_write) invoked.' } Dumpers.dump_page(@log_pages, 'Jekyll::Hooks.register(:pages, :post_write)', page) end ########## :documents hooks # Documents are web pages that belong to a collection, for example posts, drafts and custom collections. # These hooks provide fine-grained control over all documents in the site. # If you want to inspect or process all collections in the same way, use these hooks. # If you just want to process a custom collection, use these hooks and filter out the documents # that do not belong to that collection. # Called whenever any document is initialized. # Front matter data will not have been assigned yet to documents when this hook is invoked, for example: # categories, description, last_modified_at, tags, title, and slug; # other document attributes that are not yet ready when this hook is invoked include # excerpt and ext (file extension). # The collection attribute will be set properly for this hook. Jekyll::Hooks.register(:documents, :post_init, priority: :normal) do |document| @log_docs.info { 'Jekyll::Hooks.register(:documents, :post_init) invoked.' } Dumpers.dump_document(@log_docs, 'Jekyll::Hooks.register(:documents, :post_init)', document) 'stop' end # Called just before rendering a document. # Front matter data will have been assigned when this hook is invoked. # Liquid variables are still embedded in the content. # If the document contains markdown (or some other markup), # it will not have been converted to HTML (or whatever the target format is) yet. Jekyll::Hooks.register(:documents, :pre_render, priority: :normal) do |document, payload| @log_docs.info { 'Jekyll::Hooks.register(:documents, :pre_render) invoked.' } Dumpers.dump_document(@log_docs, 'Jekyll::Hooks.register(:documents, :pre_render)', document) Dumpers.dump_payload(@log_docs, ':documents, :pre_render payload', payload) end # Called after converting the document content to HTML (or whatever), # but before rendering the document using the layout. Jekyll::Hooks.register(:documents, :post_convert, priority: :normal) do |document| @log_docs.info { 'Jekyll::Hooks.register(:documents, :post_convert) invoked.' } Dumpers.dump_document(@log_docs, 'Jekyll::Hooks.register(:documents, :post_convert)', document) end # Called after rendering a document using the layout, but before writing it to disk. # This is your last chance to modify the content. Jekyll::Hooks.register(:documents, :post_render, priority: :normal) do |document| @log_docs.info { 'Jekyll::Hooks.register(:documents, :post_render) invoked.' } Dumpers.dump_document(@log_docs, 'Jekyll::Hooks.register(:documents, :post_render)', document) end # Called after writing a document to disk. # Useful for statistics regarding completed renderings. Jekyll::Hooks.register(:documents, :post_write, priority: :normal) do |document| @log_docs.info { 'Jekyll::Hooks.register(:documents, :post_write) invoked.' } Dumpers.dump_document(@log_docs, 'Jekyll::Hooks.register(:documents, :post_write)', document) end ########## :posts hooks # These hooks provide fine-grained control over all posts **and drafts** in the site without affecting # documents in user-defined collections # Called whenever any post is initialized Jekyll::Hooks.register(:posts, :post_init, priority: :normal) do |post| @log_posts.info { 'Jekyll::Hooks.register(:posts, :post_init) invoked.' } Dumpers.dump_document(@log_posts, 'Jekyll::Hooks.register(:posts, :post_init)', post) end # Called just before rendering a post Jekyll::Hooks.register(:posts, :pre_render, priority: :normal) do |post, payload| # post is a Jekyll::Document @log_posts.info { 'Jekyll::Hooks.register(:posts, :pre_render) invoked.' } Dumpers.dump_document(@log_posts, 'Jekyll::Hooks.register(:posts, :pre_render)', post) Dumpers.dump_payload(@log_posts, ':posts, :pre_render payload', payload) end # Called after converting the post content, but before rendering the post layout. # This hook can be used to make edits to rendered pages, # regardless of whether they were originally written in markdown or HTML. # # Changes must modify post.output, as shown in this example: # Jekyll::Hooks.register(:posts, :post_convert) do |post| # post.output.gsub!('programming PHP', 'banging rocks together') # end Jekyll::Hooks.register(:posts, :post_convert, priority: :normal) do |post| @log_posts.info { 'Jekyll::Hooks.register(:posts, :post_convert) invoked.' } Dumpers.dump_document(@log_posts, 'Jekyll::Hooks.register(:posts, :post_convert)', post) end # Called after rendering a post, but before writing it to disk. # Changing `post.conent` has no effect on visible output. Jekyll::Hooks.register(:posts, :post_render, priority: :normal) do |post| @log_posts.info { 'Jekyll::Hooks.register(:posts, :post_render) invoked.' } Dumpers.dump_document(@log_posts, 'Jekyll::Hooks.register(:posts, :post_render)', post) end # Called after writing a post to disk Jekyll::Hooks.register(:posts, :post_write, priority: :normal) do |post| @log_posts.info { 'Jekyll::Hooks.register(:posts, :post_write) invoked.' } Dumpers.dump_document(@log_posts, 'Jekyll::Hooks.register(:posts, :post_write)', post) end ########## :clean hooks # These hooks provide fine-grained control on the list of obsolete files determined # to be deleted during the site's cleanup phase. # Called during the cleanup of a site's destination, before the site is built Jekyll::Hooks.register(:clean, :on_obsolete, priority: :normal) do |files| # files has type Array[String] @log_clean.info { "Jekyll::Hooks.register(:clean, :on_obsolete) invoked for #{files}." } end end
Hook Call Order
The Jekyll documentation does not indicate the exact order that each of the 45 hooks gets called.
However, the log output from this template makes that clear.
Following is elided output; I removed duplicate log entries.
All loggers were set to level info
.
Output will vary, depending on the Jekyll site that is processed and the log levels you set.
:post_init
, in particular, gets called many times.
You should write your hooks so they are idempotent.
INFO Module: Jekyll::Hooks.register(:site, :after_reset) invoked. INFO Module: Jekyll::Hooks.register(:pages, :post_init) invoked. INFO Module: Jekyll::Hooks.register(:documents, :post_init) invoked. INFO Module: Jekyll::Hooks.register(:posts, :post_init) invoked. INFO Module: Jekyll::Hooks.register(:site, :post_read) invoked. INFO Module: Jekyll::Hooks.register(:pages, :post_init) invoked. INFO Module: Jekyll::Hooks.register(:site, :pre_render) invoked. INFO Module: Jekyll::Hooks.register(:pages, :pre_render) invoked. INFO Module: Jekyll::Hooks.register(:pages, :post_convert) invoked. INFO Module: Jekyll::Hooks.register(:pages, :post_render) invoked. INFO Module: Jekyll::Hooks.register(:site, :post_render) invoked. INFO Module: Jekyll::Hooks.register(:clean, :on_obsolete) invoked for []. INFO Module: Jekyll::Hooks.register(:pages, :post_write) invoked.