Creating a Jekyll plugin. Part 1
This is the first part of the article. The next part can be found here.
Introduction
Actually the main purpose of this post is to provide a detailed step-by-step guide for building a Jekyll plugin from scratch. So the use scope of the plugin by itself is not so important.
Even though Jekyll does have some documentation about plugins (The general overview and installation guide and Some basic knowledge about plugin types and internals) the existing documentation lacks on details a bit.
And even more in: the official documentation there is a phrase like:
For tips on creating a gem … look through the source code of an existing plugin such as jekyll-feed`
I agree with the fact that this plugin is a good way to discover the best practices and a common structural patterns of a Jekyll plugin in general, and it is worth to looking into it. But the thing is that there’re really not enought details and the basic how-to-start building a plugin tutorial is actually missing.
Anyway all of these materials are worth reading to get to get a general idea of what’s going on. And it won’t take you more than 20-40 minutes approximately.
What we’re going to build
Keeping an eye on your visitors is extremely useful. It allows you to analyze which subjects are interesting for your audience, how comfortable is the navigation and even more.
Actually there are tons of different plugins, trackers and web applications which can be used for these purposes.
We’re going to use two of the world’s most popular digital analytics systems: Google Analytics and Yandex Metrica.
We will also only deal with page views
counters to not make our examples overloaded.
Obtaining codes (cheat-sheet)
That’s just a basic cheat-sheet. You can find a more detailed how-to guides here:
Getting tracking code for Google Analytics
Admin
Property
Tracking Info
Tracking Code
Will result to where the value XX-XXXXXXXXX-X
will be your tracking code for Google:
Getting tracking code for Yandex Metrica
Add tag
Go to the tag
Settings
Code snippet (in the bottom of the page)
Will result to where the value XXXXXXXX
will be your tracking code for Yandex:
First steps
Creating a test project
Let’s start with a small step at first.
First of all, you’ll need to have an existing Jekill site installed. Let’s create a test project for it:
$ jekyll new test_project
Running bundle install in /home/somename/test_project...
Bundler: Fetching gem metadata from https://rubygems.org/..........
Bundler: Fetching gem metadata from https://rubygems.org/.
Bundler: Resolving dependencies...
...
New jekyll site installed in /home/vanya/Projects/zinovyev/tutorials/test_project.
Now let’s enter the new project directory and list all the files there:
$ cd test_project
$ ls
$ 404.html about.markdown _config.yml Gemfile Gemfile.lock index.markdown _posts
Start your jekyll app with this command:
jekyll serve --watch --verbose --port 15000
Your application should be listening on the 127.0.0.1:15000 now.
If you want to know more about all the flags that I’ve used just type jekyll serve --help
and you’ll get the
description for all of them.
Start to build the plugin
In your site source root, make a _plugins
directory. Now you can place all your plugins here and they
will be available in your application right away.
Let’s create a file _plugins/test_project.rb
with the following content:
What we’ve done so far:
- We’ve created a hook plugin which will register itself on the
post_render
event for the pages of typesdocuments
andpages
; - The
#injectable?
method checks if the current page is suitable for injection of the code; - The
#inject_scripts
->#prepare_scripts_for
->#load_scripts
chain prepends the</head>
tag with the content of the simpleHello World!
script which we’ve just hardcoded for now;
As you refresh the page you should see the Hello World!
alerting window.
Moving the scripts to a separate file
Now let’s get rid of the hardcoded script inside of our plugin module. Let’s create another directory called
_includes
and the file _includes/metrics.html
with the following content:
<script>
alert('Hello World from file!')
</script>
And we’ll change the plugin class to be able to read from include:
# Main metrics plugin class
class JekyllMetrics
attr_accessor :page
def initialize(page)
@page = page
yield(@page, self) if block_given? && injectable?
end
def injectable?
['Jekyll::Document', 'Jekyll::Page'].include?(page.class.name) || page.write? &&
(page.output_ext == '.html' || page.permalink&.end_with?('/'))
end
def inject_scripts
page.output.gsub!(%r{<\/head>}, load_scripts)
end
def prepare_scripts_for(closing_tag)
[load_scripts, "</#{closing_tag}>"].compact.join("\n")
end
def load_scripts
File.read(metrics_template)
end
def metrics_template
root_path.join('_includes/metrics.html')
end
def root_path
Pathname.new(site.source)
end
def config
site.config
end
def site
page.site
end
end
# Register the hook
Jekyll::Hooks.register [:documents, :pages], :post_render do |page|
JekyllMetrics.new(page) { |_page, metrics| metrics.inject_scripts }
end
What we’ve changed here is the #load_scripts
method which will read the file from the _includes/metrics.html
template for now.
Try to reload the page at 127.0.0.1:15000 to check that everything is working fine.
You should see the new alerting window: Hello World from file!
Adding some configuration options
Now we’ll add some configuration options for our script.
Open the _config.yml
file in the root of your project and add the following content there:
test_project:
template: _includes/metrics.html
google_analytics_id: 11-111111111-1
yandex_metrica_id: 22222222
Update the content of _includes/metrics.html
:
<script>
alert('Your Yandex Metrica ID: {{ yandex_metrica_id }}')
</script>
<script>
alert('Your Google Analytics ID: {{ google_analytics_id }}')
</script>
And we will modify our plugin code again to make it work with the config:
# Main metrics plugin class
class JekyllMetrics
CONFIG_NAME = 'test_project'.freeze
DEFAULT_CONFIG = {
'template' => '_includes/metrics.html',
'google_analytics_id' => 'XX-XXXXXXXXX-X',
'yandex_metrica_id' => 'XXXXXXXX'
}.freeze
attr_accessor :page
def initialize(page)
@page = page
yield(@page, self) if block_given? && injectable?
end
def injectable?
['Jekyll::Document', 'Jekyll::Page'].include?(page.class.name) || page.write? &&
(page.output_ext == '.html' || page.permalink&.end_with?('/'))
end
def inject_scripts
page.output.gsub!(%r{<\/head>}, load_scripts)
end
def prepare_scripts_for(closing_tag)
[load_scripts, "</#{closing_tag}>"].compact.join("\n")
end
def load_scripts
render_template(File.read(metrics_template))
end
def render_template(file)
Liquid::Template.parse(file).render(plugin_config)
end
def metrics_template
root_path.join(plugin_config['template'])
end
def root_path
Pathname.new(site.source)
end
def plugin_config
@plugin_config ||= DEFAULT_CONFIG.merge(config[CONFIG_NAME].to_h)
end
def config
site.config
end
def site
page.site
end
end
# Register the hook
Jekyll::Hooks.register [:documents, :pages], :post_render do |page|
JekyllMetrics.new(page) { |_page, metrics| metrics.inject_scripts }
end
What we’ve added now:
- The
#plugin_config
method reads the specific plugin configuration that we’ve entered to the_config.yml
file; - The
#metrics_template
will now use the file name from the configuration with the possibility to fallback to the default path; - The
#render_template
method will preprocess the Liquid template and populate it with variables from our plugin configuration;
The following two alerts should appear:
Your Yandex Metrica ID: 22222222
Your Google Analytics ID: 11-111111111-1
If everything went well, let’s now add the real trackers code to our template _includes/metrics.html
:
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id="></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '');
</script>
<!-- End Global site tag (gtag.js) - Google Analytics -->
<!-- Yandex.Metrika counter -->
<script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
Now as you reload the page and open the source code of it, you will see the boths counters included with the proper ID substitutions from the configuration file.
And that’s it for now!
Continue reading on the next part of the article to find out how to convert the plugin to a separate gem.
The source code of the plugin
The full source code of the plugin may be found on github. It may slightly differ from the one described in the article. Please follow commits to find out what changed.