Understanding Middleman – the static site generator for faster prototyping

| 16 replies | Short URL: http://benfra.in/1zb

Who this is aimed at?

To get something from this, you’re probably a front-end focused coder (HTML/CSS) with absolutely zero Ruby knowledge and only cursory knowledge of the command line (I’m using OS X so sorry Windows users for the bits that make no sense). It will help a lot if you are already familiar with Sass and Compass.

What is the problem Middleman solves?

Do you create lots of mockups (HTML/CSS templates) for sites/apps? If so, perhaps at present you create flat HTML/CSS/JS pages (if a corporate site, imagine ‘home page’, ‘about -us’, contact-us’, ‘product1’, ‘product 2’ and on and on).

This practice is fine until after creating 10–15 pages something common to all pages needs to change (perhaps the navigation for instance). Sure you can ‘find and replace’ across the files but surely there is a better way? Some way to separate things common to all pages so you can update markup in one place and all pages automagically update?

There are a few ways to solve this problem, PHP or similar can do ‘includes’ (allowing you to have a header.php, footer.php for example and then include them on the current page). However, then you need to run a local server (MAMP/WAMP) and there is no simple way to get the generated content of everything you need to deliver (the HTML/CSS/JS if you need to deliver as flat files).

And that isn’t going to cover things when it comes to compiling any pre-processor languages (maybe you like writing markup in Markdown or HAML and write you styles with Sass). As it turns out, there is a better way. It’s using a ‘static site generator’.

Static Site Generators

Static site generators are becoming more and more popular. Two principal reasons:

  • For bloggers they present an opportunity to negate databases altogether and upload good ol’ HTML, CSS and JavaScript. If the site being made doesn’t require dynamic data this makes it both faster and more secure.

  • If you prototype web applications or build the front-end of web properties (be that sites or apps) static site generators can make life a whole lot easier (addressing the issues mentioned above).

Middleman is one such ‘static site generator’.

Now, the cool kids tend to be using Jekyll, another static site generator. However, I opted to look at Middleman instead for a few reasons.

Firstly, it seemed a more mature project, having a number of ‘extensions&#8216 that were useful. Secondly, it supported referencing dynamic YAML data (don’t worry if you have never heard of YAML before, we will come to that shortly). Maybe Jekyll does that too but I didn’t find any info on that (easily).

Finally it had built-in support for Markdown, Sass & Compass: tools I love using.

You don’t have to use command line static site generators anymore

These days, there are a growing number of tools that aim to make static site generation easier. At present, Middleman ‘runs’ from the command line. This shouldn’t put you off as even for a command line novice or virgin, it’s easy to do. However, if your stomach turned just reading ‘command line’, you should look at CodeKit by Incident57. It recently got the Kit language extension added, that allows partials and variables in your pre-compiled HTML – effectively covering much of what Middleman offers with GUI niceties. Hammer for Mac offers similar functionality although I haven’t used it (plus there are tools like mixture.io on the way – so lots of choice coming down the line).

Do you need Middleman?

If all you need is file includes and variables in your HTML, take a look at great GUI products like CodeKit and Hammer. They’ll do 90% of what Middleman can do and easier. However, that last 10% is where Middleman shines – auto bookmark images/favicons, bringing in data from JSON and YAML files, creating loops in your HTML and a whole lot more.

If you’re still reading this you’re interested in Middleman so let’s crack on and get it installed…

Installing Middleman

On my ‘home’ system, I installed ‘Middleman’ with no issues. From the Terminal I was able to simply run:

gem install middleman

You may need to run:

sudo gem install middleman

depending upon your system permissions.

However, on my ‘work’ system, things didn’t go smoothly. I know next to nothing about Ruby (only what I’ve learnt using Sass and Compass extensively) so when things weren’t working out I had to ask Mr Reynolds.

Long story short, if after install, when you run middleman --help from the command line you get an error, try running the following:

sudo gem update --system

After that completes, try running middleman --help again and hopefully it should run with no issues and you are now ready to get cracking with Middleman. If not, head over to the Middleman project on GitHub and search the issues there.

Starting a project

Out of the gate you can create a Middleman project from the command line. You just move to the folder you want to create the site (for example cd ~/Sites) and run:

middleman init my_new_project

Where my_new_project is the name of project you want to create.

Middleman will do it’s thing and create a project for you. Now before we get into using Middleman proper, let’s look at one of the big advantages of using it to create projects; custom templates.

Custom templates

Using the templates feature of Middleman you can easily create a project based on any number of templates. Perhaps there are dependencies you always use on certain types of projects (for example, jQuery and Modernizr). They can be included in the template you create so you don’t have to go and hunt for them (Middleman also has sprockets support for asset support but I’m going to omit talking about that in this post).

To create a new project using a different template you’ll need to run the middleman init command with the optional template flag. Here’s an example:

middleman init my_new_boilerplate_project --template=html5

As before, amend my_new_boilerplate_project to the name you want for the project.

Middleman has a default template for new projects and also includes the HTML5 one we referenced above (as you have guessed it’s based on the HTML5 boilerplate). Better still, you can easily make your own custom one. I used the included HTML5 template as a start point and amended from there. Here’s how…

In /Library/Ruby/Gems/1.8/gems/middleman-core-3.0.7/lib/middleman-core is a folder called templates. Easiest way to get started with a new custom template is to duplicate an existing project folder and then amend it as desired. So, copy ‘HTML5’ (the default template we referenced before) and amend the following folders/files:


layouts/layout.erb
index.html.erb

Also add any Sass files you will want by default into the CSS folder (yes, the CSS folder, I’ll explain why shortly). For example, I have my own mini library styles including _placeholders.scss and _mixins.scss etc that I carry from project to project and wanted them in every project I started with the template. So inside the CSS folder I added:


_partials/
styles.css.scss

Note, similar to the Sass convention, in Middleman, files prefixed with an underscore will be ignored by Middleman (although they are used/compiled in the build stage, more of which shortly). So add all the Sass partials in the partials folder with the normal .scss file extension.

However, note that the main styles style sheet (or whatever you call your main style sheet) has an extension like this styles.css.scss – this allows Middleman to preview/compile the files and reference them correctly but you’ll still end up with styles.css (or whatever you call your style sheet) when the project is built.

With those files added/amended, you also need to create a *.rb file for the new template here (path may vary depending upon Middleman version):

/Library/Ruby/Gems/1.8/gems/middleman-core-3.0.7/lib/middleman-core/templates/FILENAME.rb

Where FILENAME is the name of the new template you have made.

This file is similar to the config.rb file of Compass projects so if you have worked and looked at those before it should make sense.

You may wish to amend the default values passed to Compass too. For example, if you want to add web fonts using @font-face, you might want to specify a default folder for those. You can do this by amending the relevant *.rb file in the root of the templates folder. For example:

class_option "fonts_dir",
    :default => "css/fonts",
    :desc    => 'The path to the font files'

Now you can run your init command with the template flag and you should have a project generated to your exact requirements. That may seem like a lot of work but you’ll likely only need do this process once.

Open the project folder in your text editor of choice and we’re ready to get developing.

Working with Middleman – the development cycle

With a project generated you’ll be spending most of your time in what Middleman refers to as the development cycle. Here, Middleman provides a server that creates a magical place where Sass and other languages ‘just work’ – no need to compile with another tool. No, really! From the command line just run:

bundle exec middleman server

Now open your browser at http://localhost:4567/ and hey presto.

In terms of day-to-day editing, you’ll be doing all your work from the source folder. You edit a file, it refreshes in the browser, edit another file, view the refresh – on and on until done.

Inside the source folder

Inside source you’ll see the usual suspects – folders for HTML, CSS for example but also one for ‘layouts’. This is where the default template (called layout.erb) for a project is stored.

This layout.erb file contains everything in a page that isn’t unique. For example, it might have your header, footer and main navigation in it as these won’t change from page to page.

Conceptually, this may be the opposite of what you are used to. For example, in PHP, you tend to include the header and footer in other pages. With Middleman, this is the reverse. Where the unique part of the page should be, the layout simple references the yield, like so:

<%= yield %>

The yield

The yield is the unique part of each page (the content that differentiates ‘contact-us’ from ‘about-us’ pages for example). Put another way, the yield is the ‘meat and potatoes’ of each separate page.

Partials

Another great thing that Middleman allows is markup partials. If you are using Sass you’ll be familiar with the convention of partials. Essentially the same technique can be used for chunks of content in Middleman.

It’s a way to compartmentalise ‘blobs’ of code that are used across not just different pages but also templates. Therefore, it’s possible to section off logical sections of code into a partial and then include that in each layout. That way, you only ever need to edit code in a single place, not in numerous files.

Here’s an example, let’s pull the <head> section from a document:

<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"/>
    <meta content="yes" name="apple-mobile-web-app-capable" />
    <meta name="apple-itunes-app" content="app-id=XXXXXX"/>
    <title>< %= data.page.title || "Site Title Here" %></title>
    <meta name="description" content="Your amazing prototype"/>
    <link type="image/ico" href="favicon.ico" rel="icon" />
    <link rel="apple-touch-icon" href="apple-touch-icon.png" />
    <link rel="apple-touch-icon" sizes="57x57" href="apple-touch-icon-57x57-precomposed.png" />
    <link rel="apple-touch-icon" sizes="72x72" href="apple-touch-icon-72x72-precomposed.png" />
    <link rel="apple-touch-icon" sizes="114x114" href="apple-touch-icon-114x114-precomposed.png" />
    <link rel="apple-touch-icon" sizes="144x144" href="apple-touch-icon-144x144-precomposed.png" />
    < %= stylesheet_link_tag "styles" %>

    <script src="js/modernizr.2.6.2.min.js"></script>
</head>

We can go ahead and save that in the source/layout/partials folder (you can put it wherever you like, that’s just how I do it) as _head.erb – as in Sass, the underscore before the name is what tells Middleman this file is a partial.

Now, we can include this partial in all the layouts like this:

<%= partial "/layouts/partials/head" %>

Notice the convention here, opening angle bracket, percentage sign and equals, then the file within double quotation marks. You don’t need to put the underscore in there when referencing the file and there is no ‘closing’ equals sign before the closing angle bracket.

With that in place, if we ever have to amend the head, we only need do it in one file, not multiples.

Loops in the HTML

When working with content, Middleman lets you harness the power of Ruby to create loops in the HTML to cut down on the amount of inane repetition. If, like me, you are a Ruby newbie, here are a couple of examples:

< % 1.upto(7) do |num| %>
    <li class="section_<%= num %>"><a href="link_<%= num %>.html">Link to Section - < %= num %></a></li>
< % end %>

This will produce 7 iterations of that HTML, incrementing the value of the placeholder <% num %> each time.

Or how about generating a bunch of HTML based on the contents of an array:

< % for num in [1,7,9] %>
    <li class="icon_<%= num %>"><a href="<%= num %>.html">Link to section < %= num %></a></li>
< % end %>

That will produce three iterations where the num section is replaced with 1,7 and 9 in turn.

Referencing dynamic data

Middleman can work with YAML (an acronym for YAML Ain’t Markup Language). YAML is described as a ‘human friendly data serialisation standard’. From our point of view it lets us store data in a file and then pull that data out and stick it in our prototypes.

Let’s consider an example. Suppose we add a folder alongside source called data. In there we add a file called food.yml. The contents looks like this:

1: Sausages
2: Bacon
3: Pies
4: Pizza
5: Egg

And on and on the file goes with different foods listed at different numbers. With that we use the info in our earlier loops. For example:

< % 2.upto(5) do |num| %>
    <li class="icon_<%= num %>"><a href="<%= num %>.html">Food: < %= data.food[num] %></a></li>
< % end %>

The point of note is how the YAML is pulled in. First the source folder (data) then the file name (food) and the the reference [num] is the reference to the iteration number of our loop. Altogether it looks like this: < %= data.food[num] %>

What if you want to just grab a single record?

< %= data.food[84] %>

That will get you the record at number 84. What if the text that comes through is too long and you want to mimic server side truncation? Glad you asked…

Truncating text

Middleman packages Padrino helpers. These are handy Ruby utilities for doing stuff. So, if we need to truncate text we can do this:

< % 1.upto(7) do |num| %>
    <li section_<%= num %>"><a href="link_<%= num %>.html">< %= truncate(data.food[num], :length => 8) %></a></li>
< % end %>

Where ‘8’ is the amount of characters you want to truncate the data to.

Useful tips and tricks

In the course of building a few projects with Middleman I have come across some great features that may benefit others.

A ‘current’ state for each page

If you want a ‘current’ state for the relevant navigation item (for example, if you’re on the home page, you want ‘home’ highlighted in the nav) this tends to be achieved with flat HTML files by adding the page to the body class. For example:

<body class="index">

or:

<body class="contact-us">

And then using (S)CSS to create the relevant style:

.index .nav-link-index {}
.contact-us .nav-link-index {}

With Middleman you don’t need to manually add this class to the body tag. It can add it for you. Here’s how…

Adding the current page name as a class of the body

You can add a class name of each page to the body by adding this into the config.rb file [source]. Note, if you will want this on every project, you can add it into /Library/Ruby/Gems/1.8/gems/middleman-core-3.0.7/lib/middleman-core/templates/shared. Open your config.rb and add this in (note the code plugin I’m using adds a space between the angle brackets – remove this or it will throw an error):

###
# Helpers
###

def page_classes
  path = request.path_info.dup
  path < < settings.index_file if path.match(%r{/$})
  path = path.gsub(%r{^/}, '')

  classes = []
  parts = path.split('.')[0].split('/')
  parts.each_with_index { |path, i| classes << parts.first(i+1).join('_') }

  classes.join(' ')
end

Now, each page you create will have its name automatically added into the body class. Awesome.

Linking to style sheets

You can use some shortcuts for linking things up. For example, this is how you can reference stylesheets:

< %= stylesheet_link_tag "styles" %>

Where “styles” is the name of the scss or css file in the defined CSS folder.

Linking to pages/images

Because Middleman knows where your images and pages live, you can make easy links to them. Here’s an image link:

< %= image_tag 'book.jpg', :alt => 'Sass and Compass for designers book image' %>

Here’s an example of linking to a page in a project. Note there are multiple classes here and a data-icon attribute too:

< % link_to('/downloads.html', { :class => 'main-link color-five', :"data-icon" => 'd' }) do %>Source Code< % end %>

Favicon maker

Middleman can automatically make a number of favicons for you, each time the site is built from a single ‘base’ image. First you will need to install the Middleman Favicon plugin.

Now, add an image to the root of your ‘source’ folder called ‘favicon_base.png’ (dimensions should be 144 x 144px). Then, in the config.rb file, amend the following section:

configure :build do
  ...
  activate :favicon_maker
  ...
end

You will also need to amend the Gemfile (more on this in a moment). Ensure the following is added:

gem "middleman-favicon-maker"

Now, on build you will get all the various iOS and favicon sites generated into your ‘build’ folder (the original ‘favicon_base.png’ isn’t copied over).

Problems?

Note: if running bundle exec middleman build runs errors after enabling this, consider uninstalling and reinstalling ImageMagick:

brew uninstall imagemagick
brew link --overwrite jpeg (if it says 'You must `brew link jpeg' before imagemagick can be installed')
brew install imagemagick

The Gemfile

The Gemfile controls which bits and pieces get loaded into a middleman project. Any gems that you need for a project to build should be added. Here’s an example:

source :rubygems

gem "middleman", "~>3.0.7"
gem "sass"
gem "compass"
gem "susy"
gem "middleman-favicon-maker"
gem "middleman-smusher"

The source here is rubygems but it could also be Github or similar for any or all of the gems. For example, if you wanted to change the source you could do this:

gem "middleman-favicon-maker", :git => "git://github.com/follmann/middleman-favicon-maker.git"

Once you’ve added a new gem, you’ll want to bring that into you project. Do that by running this command from the Terminal/iTerm:

sudo bundle install

LiveReload

Front-end dev just isn’t worth doing these days without some form of LiveReload functionality. Middleman has your back here too. To add LiveReload functionality, add this to your gem file:

sudo gem install middleman-livereload

Now, back in the aforementioned config.rb file add this:

activate :livereload

Restart the Middleman server (bundle exec middleman server) and pump those fists in delight!

Let’s build this thing

The Middleman config file controls how the project is ‘compiled’ when you run:

bundle exec middleman build

With that command Middleman goes into action, producing your production ready code. It is squished together, optimised and spat out into the build folder, ready for your viewing pleasure (it takes only a few seconds for the most part).

There are a few options controlling how things are built and squished together, let’s look at a few…

Have Sass debug info on build but not production

I take it you want to see debug info for Sass in Chrome Dev Tools? Sure you do:

# Change Compass configuration
config :development do
  compass_config do |config|
    config.sass_options = {:debug_info =&gt; true}
  end
end

Here is an example of a recent config.rb file I had (a # symbol comments the line out giving you an idea what’s available and how you may wish to switch different things off):

###
# Compass
###

# Susy grids in Compass
# First: gem install susy --pre
require 'susy'

# Change Compass configuration
config :development do
  compass_config do |config|
    config.sass_options = {:debug_info => true}
  end
end

# Yeah Baby!
activate :livereload

###
# Page options, layouts, aliases and proxies
###

# Per-page layout changes:
#
# With no layout
# page "/path/to/file.html", :layout => false
#
# With alternative layout
# page "/path/to/file.html", :layout => :otherlayout
#
# A path which all have the same layout
# with_layout :admin do
#   page "/admin/*"
# end

# Proxy (fake) files
# page "/this-page-has-no-template.html", :proxy => "/template-file.html" do
#   @which_fake_page = "Rendering a fake page with a variable"
# end

###
# Helpers
###

# Automatic image dimensions on image_tag helper
# activate :automatic_image_sizes

# Methods defined in the helpers block are available in templates
# helpers do
#   def some_helper
#     "Helping"
#   end
# end

def page_classes
  path = request.path_info.dup
  path < < settings.index_file if path.match(%r{/$})
  path = path.gsub(%r{^/}, '')

  classes = []
  parts = path.split('.')[0].split('/')
  parts.each_with_index { |path, i| classes << parts.first(i+1).join('_') }

  classes.join(' ')
end

set :css_dir, 'css'

set :sass_dir, 'css'

set :js_dir, 'js'

set :images_dir, 'img'

set :fonts_dir, 'css/fonts'

# Build-specific configuration
configure :build do

  activate :minify_css

  # Minify Javascript on build
  activate :minify_javascript

  # Enable cache buster
  # activate :cache_buster

  # Use relative URLs
  activate :relative_assets

  # Or use a different image path
  # set :http_path, "/"

  # Compress PNGs after build
  # First: gem install middleman-smusher
  #require "middleman-smusher"
  #activate :smusher

  # Enable Favicon Maker
  require "favicon_maker"
  activate :favicon_maker
end

Compress JS and CSS on build

You can see in that prior config.rb file how CSS and JS is being minified on build:

activate :minify_javascript

And:

activate :minify_css

Respectively.

There’s all sorts of extra stuff you can use such as cache busting and the like.

Tip: You’ll notice there I’ve also commented out the image smusher until I’m ready to go to production proper as it adds significantly to the time it takes the build to process.

Sitemap XML

If you’re creating a static site there is a good chance you’ll want a sitemap.xml file to enable search bots to better index your site. You can facilitate this, thanks to this Gist by Luke Bowerman. First, create a new file (in your source folder) called sitemap.xml.erb. Inside, you can add this content (note WordPress adds a space between the opening angle bracket and % – you don’t need that space):

< % pages = sitemap.resources.find_all{|p| p.source_file.match(/\.html/) } %>
< ?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
< % pages.each do |p| %>
  <url>
    <loc>http://youdomain.com/< %=p.destination_path.gsub('/index.html','')%></loc>
    <priority>0.7</priority>
  </url>
< % end %>
</urlset>

As you would imagine, change ‘yourdomain’ for your site url. You’ll also want to stop that page getting the normal layout applied. You can do this by adding the following line in your config.rb page "/sitemap.xml", :layout => false. Then, when you build your site, you’ll get a sitemap.xml in the root with all your html files referenced in the sitemap.

Pretty URLs

Perhaps you want to have links like this: http://sassandcompass.com/chapter1/ instead of this: http://sassandcompass.com/chapter1.html (the default). This can be facilitated easily with Middleman. Just add this:

activate :directory_indexes

Into your config.rb file. Now, not only will the preview server continue to work (although if you have hard coded any links, you’ll want to update them to use the ‘dynamic’ link syntaxes listed above) but you’ll get all files in their own folders when built, ready for upload. The pages will now be available like this: http://sassandcompass.com/chapter1/

So much more (summary)

Despite this rather lengthy post, I’ve really only scratched the surface of Middleman and what it can do for you. I’ve not even talked about:

  • Blogging
  • Localization

Perhaps I’ll cover them in a future post. For now, if you have any need to produce static sites I’d encourage you to take a look at Middleman. Despite a moderate learning curve it rewards you with an incredibly flexible system to rapidly build prototypes, create blogs and more.

About The Author

16 Responses to “Understanding Middleman – the static site generator for faster prototyping”

  1. Mark

    Hi Ben,

    Do you have any idea about using JSON instead of YAML for local data in Middleman?

    Completely new to Ruby and may be misunderstanding the reason it uses YAML for local data. I’m using trying out Middleman so my site is lightweight and I can really control the code.

    Thanks.

  2. Ben Frain

    Sorry Mark, at this point I’ve only used it with YAML. However, can’t imagine why it wouldn’t or couldn’t use JSON as for data import purposes they essentially do the same. Apologies but at this point I can’t really add anything useful on that subject.

  3. Michael

    Thanks for explaining Middleman in a way I can understand, really useful!

  4. Mark

    Great read. I recently tried Jekyll, but Middleman seems the more flexible for what I want, this post helped massively with understanding and getting set up, especially with no Ruby knowledge!

    Have you any more posts planned on Middleman in the near future?

    Cheers!

  5. JayJay

    Do you know how to use HAML or do you have any pointers to use it?

  6. JayJay

    Excuse my lack of experience; but could you explain how you finally compile the file for development? Do you change the options in build for production (like re-enabling image optimisations) and then build it again?

  7. Gour

    I wonder if you, maybe, considered/tried Nanoc?

  8. Faust Gertz

    I kept getting messages such as “warning: conflicting chdir during another chdir block” during the build. Running `middleman –help` gave me
    “Ruby 1.8 requires the ‘ruby18_source_location’ gem be added to your Gemfile” and I couldn’t get ‘ruby18_source_location’ to install. I didn’t find anything helpful on the Middleman GitHub repository.

    I finally got it to work by uninstalling all the Middleman gems, installing rbenv (sans Homebrew), installing ruby-build, using it to install Ruby 1.9.3-p327, and running `sudo gem update –system` for good measure before reinstalling Middleman. Note: I had to run `rbenv rehash` to “generate the shims” before I could run Middleman.

    I have zero Ruby knowledge and my command line chops aren’t very strong either. So the install is as far as I got this evening. Hopefully, the rbenv tip will save someone some time if they run into the similar problems.

  9. viktor

    Hi Ben,
    Interesting article. One quick question.
    Can you split config.rb up in more files so you can for example have allt the login code in login.rb and then include the login.rb in config.rb file?

  10. Ben Frain

    Hi Viktor,

    I’d suspect not but I don’t actually know. Probably a question you’ll have more luck getting a definitive answer for on the GitHub project page.

    Sorry I can’t help more.

  11. Tom Gillard

    Hey Ben,
    This is by far the most helpful and in depth look at Middleman. A lot of useful stuff and better than the docs. Just wanted to let you know I’ve setup my blog on Middleman and have a post on dynamic pages you or your readers mightfind interesting – http://bit.ly/KmJSWd

    Thanks again,
    Tom

  12. Lori

    I hope you will write more on Middleman. Just used if for the first time and liked it a lot. Your tutorial was a great help.

    Thanks.

  13. Robin Solanki

    Great Article, I come from a rails background but when it comes to quickly setting up websites I go for wordpress because of the repetitive tasks in plain HTML, suggested by my brother I must say middleman has made be love HTML again.

×

Comments are closed.

501Days
501 days since this post was last revised. Specific details are likely out of date.
3926 words
MENU