Article summary
The combination of Middleman, LESS, and Bootstrap is perfect for web application prototyping. Kedron Rhodes wrote about using Middleman and Bootstrap for rapid prototyping last year, and recently Dustin Tinney wrote about how to use LESS mixins to reduce some of Bootstrap’s clutter.
Configuring Middleman to use LESS is simple. Just add gem 'less'
and gem 'therubyracer'
to your Gemfile, and you can start requiring .less
files in the asset pipeline (see Asset Pipeline in the Middleman docs).
The Problem
Everything seems to work great until you start using LESS‘s import capability to import other .less
files. If you make a change to a file that has been imported into another LESS stylesheet, Middleman does not detect the change. This means you can change the styles all day long but you won’t see your changes when you refresh the browser.
issue was opened on GitHub earlier this year about the problem but has since been closed without being fixed. I would like to eventually dig into it and try to fix the problem, but until I get a chance to do that, I came up with a workaround that lets me move forward right now.
The Solution
1. Require a Single LESS File
In order to take advantage of the full power of LESS, it makes sense to have a single LESS file for your application that uses “@import” statements to pull in all of your other LESS files. The CSS generated from that one LESS file can then be tacked on to any other CSS you want to include by requiring it in the asset pipeline. For example, here is an all.css
that includes some jquery-ui styles and the contents of an application.css.less
file:
/*
*= require jquery-ui/jquery-ui
*= require application
*/
And here is an example of an application.css.less
file:
@import 'lib/bootstrap-3.0.0/bootstrap.less';
@import 'my-variables';
@import 'layout';
@import 'pages/home';
Now keep in mind with this setup that, if a change is made to layout.less
, it will not be detected by Middleman. Only changes to a LESS file that are required directly through Sprockets get picked up.
2. Add a Little Misdirection
The key to this workaround is to “trick” Middleman into detecting changes by modifying the application.css.less
file every time one of the other LESS files is updated. Instead of importing the other files directly in application.css.less
, let’s add another file called site.less
that will have all of the import
statements.
So now we have an application.css.less
file:
@import 'site'
// 1
And site.less
:
@import 'lib/bootstrap-3.0.0/bootstrap.less';
@import 'my-variables';
@import 'layout';
@import 'pages/home';
Notice the commented out number at the bottom of the application.css.less
file? The purpose of that is to give us something that can be updated each time we want Middleman to recompile the LESS files. All you have to do is increment the number each time you make a change to a LESS file.
3. Automate It
Obviously having to manually update some file each time you make a change is not going to be a pleasant development cycle. This is where Guard comes in. The following Guardfile will configure it to watch for changes to any .less
stylesheets and automatically modify the application.css.less
file, causing Middleman to recompile the stylesheets on the next page load.
require 'guard/guard'
module ::Guard
class LessWatcher < ::Guard::Guard
def start
run_on_changes([])
end
def reload
start
end
def run_on_changes(paths)
dest_path = "source/stylesheets/application.css.less"
return if paths == [dest_path]
next_number = if File.exists?(dest_path)
contents = File.read(dest_path)
if contents =~ %r{// (\d+)}
$1.to_i + 1
end
end
next_number = next_number.to_i
template = File.read("source/stylesheets/application-template.css.less")
File.open(dest_path, 'w') do |file|
file.write(template.gsub(%r{// (\d+)}) { "// #{next_number}"})
end
end
end
end
guard :less_watcher do
watch(%r{^source/stylesheets/(.+)\.less$})
end
And here is the template file that is used by the Guardfile (source/stylesheets/application-template.css.less
):
// GENERATED FILE - DO NOT MODIFY
@import 'site';
// 0
4. Combine Processes with Foreman
At this point, if both Middleman and Guard are running, we can make a change to a .less
file, refresh the browser, and the updated styles will be used. Having to deal with two separate processes is a pain, but with Foreman this is no problem. Here’s a Procfile that will fire up Middleman and Guard when run:
guard: bundle exec guard start -i
middleman: bundle exec middleman server
The following command will run Foreman (assuming it is in your Gemfile):
bundle exec foreman start
5. Ignore the Generated File
You will want to add the generated source/stylesheets/application.css.less
file to your .gitignore
(or whatever the equivalent is for your source control system). The Guardfile will ensure that the generated file exists whenever it starts, so there’s no need for the file to be checked in. And ignoring the generated file will prevent endless conflicts when multiple developers are working in the codebase at the same time.
6. Building the Static Site
Middleman treats its source files differently when it builds the static site, as opposed to running a local development server. In order to keep it from trying to compile all of the LESS files it comes across in the source/stylesheets directory, you have to configure it to ignore them. You can do this by adding an ignore
line in config.rb
:
configure :build do
ignore "*.less"
end
The last thing you should consider doing is adding a Rake task to make sure the application.css.less
file exists before building the static site. In normal development, the file would be there as soon as you start Guard, but if you wanted to have a Continuous Integration server build the site automatically each time a change is committed, then Guard would not be involved.
task :copy_template do
sh "cp source/stylesheets/application-template.css.less source/stylesheets/application.css.less", verbose: false
end
desc "Build/compile the site"
task :build > :copy_template do
sh "bundle exec middleman build --clean"
end
Put It All Together
I’ve created a skeleton Middleman project (including Bootstrap 3) that shows how all of these pieces fit together. Check it out if you want an example of how Middleman, Guard, and Foreman can work together to make for a hassle-free prototyping environment with LESS.
I don’t suppose you ever managed to get a proper fix for this issue in Middleman?
Casey,
No, I never did. This workflow worked well enough that I was never forced to get to the bottom of the real issue.
This worked well for me, however a slight modification modification to the Guardfile was required to use the newest version.
Update:
class LessWatcher < ::Guard::Guard
to
class LessWatcher < ::Guard::Plugin