NOTE: This post is a more concise version of the much longer version on my blog
Jekyll, the static site generator, although it is the older technology, only consists of about 6800 lines while can achieve many things: Theme, Layout, Markdown, Plugins, Live-Reload, and more.
Ever wonder how does it works internally? I hope what's am about to write can give you some ideas.
When serving the blog ($ jekyll serve), the internal calls look like this:
In its heart, it's the Site class and its #new() and #process() method.
#new()
This loads plugins:
-
generatorsthe plugins that receive thesiteinformation and generates additional content such as atom,sitemap.xml -
convertersthe plugins that receive thesiteandcontents(pages/posts you wrote and what generated from thegenerators)
The loading mechanism is especially interesting. Any plugin must inherit from Jekyll::Generator or Jekyll::Converter, so we can load them by tracking their descendants:
def instantiate_subclasses(klass)
klass.descendants.select { |c| !safe || c.safe }.tap do |result|
result.sort!
result.map! { |c| c.new(config) }
end
end
The loading code:
self.converters = instantiate_subclasses(Jekyll::Converter)
self.generators = instantiate_subclasses(Jekyll::Generator)
#process()
The code itself is very clean:
def process
return profiler.profile_process if config["profile"]
reset
read
generate
render
cleanup
write
end
-
resetresetssitestate into empty states (for example,site.pages = []) -
readreads site and theme contents intosite.pages,site.posts,site.static_files -
generateuses loadedgeneratorsplugins to generate additional contents as mentioned -
renderrenders contents using Liquid template, converters (such asMarkdownconverter), and put into layouts (layout is Liquid template that renderscontentvariable) -
cleanupremoves obsolete files by comparing new set of generated files versus the current generated files -
writewrites outputs fromrenderinto the actual filesystem
Watching for File Changes
It uses listen gem underneath, any file changes will trigger the #new and #process steps again (unless incremental build is enabled) then notify the browser via LiveReload websocket communication.
The LiveReload.js on the pages got injected when serving files by manipulating <header> through custom WEBrick handler.
Code Worth Stealing
There are many good piece of code worth stealing:
- lib/jekyll/site.rb especially on the plugins loading
-
lib/jekyll/external.rb for how to handle when
requirefails - lib/jekyll/profiler.rb for how a simple profiler could be done for your command line
- lib/jekyll/cache.rb for how a simple cache on disk could be implemented
- lib/jekyll/commands/serve/servlet.rb for LiveReload injection for your site
- jekyll-watch if you need watching over files and reacting to changes
- History.markdown seems to be manually kept
Conclusion
The Jekyll code itself is very clean (though I would say it is more engineered than clean). I wrote more about it including caching, profiling, security, and more on my blog.
Hope you like this article. If you spot any mistakes or want to share feedback, please feel free to comment or DM me :)













