zenweb
Stupid fast static website generation
Table of Contents:
zenweb 3 is a complete rewrite inspired by Jekyll. Jekyll was designed for github pages and does a great job of it. Unfortunately these design decisions don’t apply to the rest of us. Jekyll is painfully slow because it always builds everything and it only allows liquid for templating which unnecessarily restricts our ability to implement features.
zenweb 3 overcomes these issues. It uses rake to manage dependencies and only builds what is needed. It allows for any markup language to be used at any level so you’re not stuck with liquid or anything else you might not like. Extra markup languages are provided via a simple rubygems plugin system.
Features
- Renderers
- Incredibly fast static file generation.
- Uses rake to manage dependencies, so it only builds what you’ve changed.
- Multiple renderers per file can be easily applied via multiple
file extensions. (eg,
index.html.md.erbwill callrender_erband thenrender_mdto produceindex.html) - Templates can be applied via each page’s
layoutconfiguration. - Templates can have templates, allowing for cleanly refactored layouts.
- Plugins
- Rubygem-based plugin system allows for infinite flexibility.
- Ships with erb, markdown, and less plugins.
- Many more to come.
- Configuration
- Directory wide configuration provided via
_config.ymlfiles. - Every file can have a YAML header overriding configuration from parent directories.
- Hierarchical configuration system means you have complete control on every file and every directory.
- Directory wide configuration provided via
Getting Started
Creating a New Site
- gem install –pre zenweb
- gem fetch –pre zenweb-template
- gem unpack zenweb-template-1.0.0.b1.gem
- mv zenweb-template-1.0.0.b1/website mynewwebsite
- rm -r zenweb-template-1.0.0.b1
- cd mynewwebsite
- rake
- rake run
- open http://localhost:8000/
You can now modify the Rakefile and set HTML_DIR to point to the base directory of your website. If the server is remote, prepend the hostname with a colon:
1 |
HTML_DIR="example.com:public_html |
Once this is set, rake push will use rsync to publish your website
quickly and efficiently. See the tasks below for more info of what you
can do via rake.
Using zenweb
zenweb is typically driven entirely through rake tasks. Out of the box it provides the following tasks:
run- Run a webserver and build on the fly.
publish- Publish your website: clean, generate, push
generate- Generate the website.
push- Push changes to the site.
clean- Remove junk files.
realclean- Clean and also remove the generated .site directory.
debug- Debug the generation of a file.
Authoring
Creating a new page is as simple as creating a new file. Really. That’s all there is to it. You can provide an optional YAML header but the rest of the file is just content. File extensions are used to determine how to render the file and stop rendering when they get to a file extension they don’t know.
For example, say you have the file pages/index.html.md.erb and it
looks like this:
1 2 3 4 5 6 7 |
---
title: My Pages
...
This is my website. There are many like it but this one is mine.
{{ sitemap }}
|
Broken down:
- Lines 1-3
- The YAML header and can define anything you want. Here we only define
the title of the page. Other configuration values like
layoutcan come from inherited_config.ymlfiles. - Line 5
- One simple line of markdown content.
- Line 7
- Ruby code that creates a sitemap for all pages below this one.
When it comes time to generate this file, render_erb will be called
first because it is the last extension on the filename. Next,
render_md will be called. Since there is no render_html, rendering
stops at this level and the resulting filename will be .site/pages/index.html.
When it comes time to generate this file index.html.md.erb will call
render_erb resulting in index.html.md. index.html.md will call
render_md resulting in index.html. There is no render_html, so
rendering stops at this level. The resulting filename will be
.site/pages/index.html.
When it comes time to generate this file:
index.html.md.erbwill callrender_erbresulting inindex.html.md.index.html.mdwill callrender_mdresulting inindex.html.- There is no
render_html, so rendering stops. - The resulting filename will be
.site/pages/index.html.
| input filename | renderer | output filename |
|---|---|---|
| pages/index.html.md.erb | render_erb |
pages/index.html.md |
| pages/index.html.md | render_md |
pages/index.html |
| pages/index.html | N/A | .site/pages/index.html |
But it doesn’t stop there… If layout is defined, then
_layouts/<layout-name>* is looked up. The layout is rendered and the
contents from your page is injected into the body of the layout. If a
layout defines a layout, then the process repeats. This allows you to
organize your site as site-layout(section-layout(page-contents)) or
however you please.
File Organization
File organization in zenweb is pretty easy and flexible. There is
only 1 file required to get started: the Rakefile. It needs to contain:
1 |
Rake.application.rake_require "zenweb/tasks" |
and nearly everything else is taken care of.
A simple website might contain the following:
Rakefile- Mentioned above.
.site/...- All generated files are written to
.site. _config.yml- The top level configuration (yaml) file.
_includes/...- Snippets to be used by
includestatements are stored here. _layouts/...- Layout files are stored here.
index.html.md.erb- The homepage of your site.
blog/_config.yml- Subdirectories can have their own config files and can override values higher up.
blog/index.html.md.erbblog/2012-01-01-post1.html.md.erbblog/2012-01-02-post2.html.md.erbblog/2012-01-03-post3.html.md.erbblog/...- A typical blog directory will contain an index file and a bunch of dated posts. These files all render using ERB and markdown.
normal_page1.html.mdsubdir/normal_page2.html.mdsubdir/normal_page3.html.md- Regular web pages and subdirectories. zenweb isn’t biased towards blogs.
atom.xml.erb- Generates an Atom feed file for feed reader applications. Uses ERB to generate.
sitemap.xml.erb- A
sitemap.xmlfile to help search-engines index your content. robots.txt- A simple static robots.txt file
Comparing to Jekyll
zenweb is quite a bit lighter than jekyll. This can be seen as both a good thing and a bad thing. In some ways, zenweb does less than jekyll. In some ways this gives zenweb more flexibility and certainly more speed.
To objectify this comparison, we can look at the number of dependencies:
| gem | deps |
|---|---|
| Jekyll | 27 |
| zenweb | 8 |
Features
Things that zenweb borrowed from jekyll:
- _files and _dirs are not generated, only used to organize shared stuff.
Things zenweb doesn’t do out of the box (yet?):
- zenweb has no notion of “virtual” pages (yet), so it currently
doesn’t do:
- Pagination
- Tags / Categories
- Archive directories
- zenweb doesn’t do “permalink”.
- It does have a
date_fmtconfiguration variable that is used when creating files with YYYY-MM-DD in their filenames.
- It does have a
- zenweb will provide migrations for other systems in the future, but they won’t ship with zenweb itself. Since you only use it once(ish), why include them?
Things zenweb has that jekyll doesn’t:
- Proper dependency management. Updating a blog post should update the
sitemap, index.html, and anything else that could reference the blog
post. zenweb uses rake to ensure this happens correctly.
- Custom dependencies can be easily declared via the
extra_depstask.
- Custom dependencies can be easily declared via the
- All configuration data is available at all stages of rendering. This is mostly a limitation caused (by design) by liquid.
- Speed. I’m not entirely sure why, but jekyll is a dog.
- Flexibility.
- Liquid holds me back too much. Give me the full power of ruby or whatever else I want for my layout files.
- zenweb is not oriented towards blogs and really doesn’t care what you’re creating or how it is structured (eg, there is no _posts directory).
Performance
- Jekyll + octopress w/ 747 pages.
- zenweb w/ 1061 pages.
| Build Type | Jekyll | zenweb |
|---|---|---|
| Full Build | 146.2s | 26.6s |
| Build 1 page | 148.2s | 4.9s |
| Build 0 pages | 147.3s | 2.3s |
Updating a single page on my website (~300 pages) and rsyncing all
changes to the server takes me a mere 3.3 seconds with a single rake
sync invocation on a semi-slow café network.
Get The Code
If you just want to use zenweb, you can install it via RubyGems:gem install zenweb
If you want to hack on zenweb, clone it from GitHub:
git clone git://github.com/seattlerb/zenweb