Minifying js and css in nanoc

I found some solutions to minify and concatenate scripts and stylesheets in nanoc, but none of them had the flavor i really wanted, so I baked one up myself.

This solution will allow you to download unminified (development) versions of your javascript libraries and css frameworks, which allows for easier debugging, and minifying them on-demand as you compile your site. It’s leightweight and easi to implement.

For this example i have my css files and js files under /assets/js/ and /assets/css/ respectfully. You can easily change the code to reflect another structure.

We’ll be using rainpress and uglifier to minify our scripts and stylesheets respectfully, so we’ll have to install the gem to make that happen.

The easiest way to deal with gems in nanoc in my opinion is to use a Gemfile.

In your application root directory, create a Gemfile if you haven’t already

touch Gemfile

Now open this file with your favorite editor and add the gems rainpress and uglifier. Whe using a Gemfile you also have to include nanoc itself.

gem "rainpress"
gem "uglifier"
gem "nanoc"

Install the gems

bundle install

We’ll make two helpers that will concatenate the compiled content of all your scripts and stylesheets. The advantage here is that you can mix css, scss, less … whatever! It doesn’t matter, because the items will pass through compilation before they are concatenated.

Put this code in lib/helpers.rb. Nanoc includes all code in the lib directory, so this will be available in your app.

def all_js(files)
  js_arr = []
  for file in files
    item = @items.find{|i| i.identifier == "/assets/js/#{file}/"}
    puts "File #{file} doesn't exist!" unless item
    js_arr << item.compiled_content
  end
  js_arr.join("\n")
end

def all_css(files)
  css_arr = []
  for file in files
    item = @items.find{|i| i.identifier == "/assets/css/#{file}/"}
    puts "File #{file} doesn't exist!" unless item
    css_arr << item.compiled_content
  end
  css_arr.join("\n")
end

These functions take an array of identifiers and turn the compiled content of the files with these identifiers into one long string. Later we will run this content through the minification filters.

In your config file (config.yaml) in the root, add a few directives. The debug directive turns concatenation and minification on or off. This is helpful if you have to track down typo’s or bugs in the scripts or in the stylesheets. The scripts and stylesheets directive contains the filenames (without extensions) of your scripts and stylesheets.

debug: false  # if true, don't concatenate scripts

scripts:      # your script filenames without extension
  - jquery
  - main

stylesheets:  # your stylesheets files without extension
  - screen
  - print
  - ie

Make two files /assets/css/all.css and /assets/js/all.js

touch /assets/css/all.css && touch /assets/js/all.js

In all.css put this code:

<%= all_css @config[:scripts] %>

In all.js put this code:

<%= all_js @config[:stylesheets] %>

In your default template (usually default.html), put this code in the head (without the dots please!!):

...
<head>
    ...
    <% if @config[:debug] %>
    <% for file in @config[:stylesheets] %>
    <link rel="stylesheet" type="text/css" href="/assets/css/<%= File.basename(file, ".*") %>.css">
    <% end %>
    <% else %>
    <link rel="stylesheet" type="text/css" href="/assets/css/all.css">
    <% end %>
    <% if @config[:debug] %>
    <% for file in @config[:scripts] %>
    <script src="/assets/js/<%= file %>"></script>
    <% end %>
    <% else %>
    <script src="/assets/js/all.js"></script>
    <% end %>
    ...
</head>
...

The above code makes all your normal scripts appear on seperate link and script statement if debug is enabled in the config.yaml file. Otherwise everything is concatenated ( and uglified / minified ) into one big file: all.js or all.css and that one is used. This kinda mimics the rails assets pipeline on a very low level.

Compile rules (in Rules):

compile '/assets/css/*' do
  filter :erb
  filter :rainpress if File.basename(item[:content_filename], ".*") == 'all'
end

compile '/assets/js/*' do
  filter :erb
  filter :uglify_js if File.basename(item[:content_filename], ".*") == 'all'
end

This runs the all.css and all.js files through the rainpress and uglify_js filters which comes with nanoc.

Route rules (in Rules):

route '/assets/css/*' do
  # if not debug mode, only let all.css through
  unless @config[:debug] 
    item.identifier.chop + '.css' if File.basename(item[:content_filename], ".*") == 'all' 
  else
    item.identifier.chop + '.css'
  end
end

route '/assets/js/*' do
  # if not debug mode, only let all.js through
  unless @config[:debug]
    item.identifier.chop + '.js' if File.basename(item[:content_filename], ".*") == 'all'
  else
    item.identifier.chop + '.js'
  end
end

You should now have minification up and running. The real strength of this comes to life when you mix in files with sass or coffescript or something like that. Be aware that you should make changes to reflect this in the Rules file. I will let you fiddle with that yourself. Good luck and have fun with nanoc. It rocks!!!

Comments

comments powered by Disqus