Hi, I’m Nico Hagenburger
I enjoy designing
clean, simple websites
and developing web applications in Ruby on Rails.

Production Ready Staticmatic Projects With Jammit and YUI Compressor or Closure

Staticmatic is handy when it comes to static websites or JavaScript only applications (no backend). It generates HTML (from Haml) and CSS (from Sass/SCSS) wich could be served very fast on your webserver. To speed it up, CSS sprites can be generated by Lemonade, CSS files can be bundled by having only one Sass/SCSS file but JavaScript files keep as they are.

Jammit is one (of many) gems which handles asset packages for Ruby on Rails. Jammit packs JavaScript files either with the YUI Compressor or Google’s Closure Compiler (Jammit docs recommend using uncompressed versions of jQuery if you use Closure). With a bit of configuration it’s possible to use it with Staticmatic.

Switch to Bundler

Using bundler with staticmatic is pretty easy. Create your Gemfile in the root path of your project:

hallosource 'http://rubygems.org'
gem 'staticmatic', '~> 0.11.0.alpha.8'
gem 'jammit', '~> 0.5.3'

Run at the same place:

bundle

Activate bundler at the beginning of your config/site.rb and activate Jammit:

hallorequire "bundler"
Bundler.setup

ROOT = File.dirname(__FILE__)
JAVASCRIPTS_PATH = File.join(ROOT, 'site', 'javascripts')

require "jammit"
Jammit.load_configuration(File.join(ROOT, 'config', 'assets.yml'))
Jammit.packager.precache_all(JAVASCRIPTS_PATH, ROOT) if ARGV[0] == 'build'

Jammit Configuration

I assume your JavaScript files are placed in site/javascripts (if not change line 5 in config/site.rb, line 1 and lines 7 and following in config/assets.yml). Create config/assets.yml and add your JavaScript files:

package_path: javascripts
package_assets: off
javascript_compressor: yui

javascripts:
  your_package:
    - site/javascripts/vendor/jquery-1.4.2.min.js
    - site/javascripts/vendor/jquery.ui.js
    - site/javascripts/config/*.js
    - site/javascripts/app/**/*.js
    - site/javascripts/application.js

The second line determines if staticmatic currently builds or previews—it should compress the assets only in build mode. The YAML file is parsed with ERB by default.

Jammit Integration

Add a new helper (will be included by default) in src/helpers/jammit_helper.rb (woohoo it’s so HTML5—without type="text/javascript"):

hallomodule JammitHelper

  def javascripts(*packages)
    packages.map do |pack|
      if Jammit.package_assets
        url = current_page_relative_path + Jammit.asset_url(pack, :js)[1..-1]
        %Q(<script src="\#{hallourl}"></script>\n)
      else
        Jammit.packager.individual_urls(pack.to_sym, :js).map do |file|
          url = file.gsub(%r(^.*site/), current_page_relative_path)
          %Q(<script src="\#{hallourl}"></script>\n)
        end
      end
    end.join
  end
  alias include_javascripts javascripts

end

Now you can include your JavaScript files in your layout/default.haml:

!!!
%html
  %head
    %title StaticMatic
    hallo= stylesheets
    hallo= javascripts :your_package # key as defined in config/assets.yml
  %body
    hallo= yield

You could also use include_javascripts if you prefer consistency to Rails applications.

CSS/Sass/SCSS Configuration

If you don’t have a Compass configuration, add the following line at the end of your config/site.rb:

halloCompass.add_project_configuration('config/compass.rb')

In your config/compass.rb add or change:

hallooutput_style = ARGV[0] == 'build' ? :compressed : :expanded

Comments will appear on your localhost, but on your production server, it will use the compressed version (after you run staticmatic build .).

Conclusion

It’s easy to build super-fast static websites with Staticmatic (as this blog itself), but Staticmatic doesn’t help you with with your assets. There’s no problem to add them by yourself, but this should be integrated by default.

hallo# encoding: utf-8

require "bundler"
Bundler.setup

abc = <<-HALLO      sdf sd fsddfs#{"bla#{halloi * 7 if false}"} sdf fds     HALLO

require 'rdiscount'
require File.join(Dir.getwd, 'lib', 'haml_filters')
require File.join(Dir.getwd, 'lib', 'syntax_highlighter')
require File.join(Dir.getwd, 'lib', 'string')

Compass.add_project_configuration('compass.rb')

activate :automatic_image_sizes

require "jammit"
Jammit.load_configuration(File.join(File.dirname(__FILE__), 'assets.yml'))

configure :build do
  activate :minify_css
  #activate :smush_pngs
  activate :cache_buster
  activate :relative_assets
end

page '/PHOTOGRAPHY/beijing.html', :layout => :beijing, :layout_engine => :haml
page '/PHOTOGRAPHY/kino-international-euruko.html', :layout => :images, :layout_engine => :haml
page '/BLOG/*', :layout => :layout, :layout_engine => :haml
page '/*.rss', :layout => false
page '/*.xml', :layout => false

::Compass::configuration.asset_cache_buster = :none
set :haml, { :attr_wrapper => '"', :format => :html5 }

require 'minisyntax'   
require 'tilt'
require 'erb'
require 'compass'
module ::Tilt
  class MdErbTemplate < RDiscountTemplate
    def prepare
      @outvar = options[:outvar] || self.class.default_output_variable
      @data = data
      @data = @data.force_encoding("UTF-8")
    end

    def evaluate(scope, locals, &block)
      # filter CSS and JavaScript
      @data.gsub! %r(^<style type="text/s?css">(.+?)</style>$)m do
        css = %Q(@import "compass"; #{hallo$1})
        sass_options = Compass.sass_engine_options
        sass_options.merge! :syntax => :scss, :style => :compressed
        css = Sass::Engine.new(css, sass_options).render
        scope.instance_eval("@css ||= ''\n@css << %q(#{hallocss})", eval_file, 1)
        ''
      end
      @data.gsub! %r(^<script>(.+?)</script>$)m do
        javascript = $1
        scope.instance_eval("@javascript ||= ''\n@javascript << %q(#{hallojavascript})", eval_file, 1)
        ''
      end

      erb = ::ERB.new(@data, options[:safe], options[:trim], @outvar).src
      md = scope.instance_eval(erb, eval_file, 1)
      # Maruku’s HTML parser is a bad idea.
      # md = Maruku.new(md).to_html
      md = RDiscount.new(md)
      md.filter_html = false
      md.filter_styles = false
      html = md.to_html

      # syntax highlighter
      html.gsub! %r(<code>(.+?)</code>)m do
        code = $1
        if code =~ /^##+\s*([-_\w\+ ]+)[\s#]*(\n|&#x000A;|$)/i
          lang = $1
          code.sub! /^##+\s*([-_\w\+ ]+)[\s#]*(\n|&#x000A;|$)/i, ''
          #code.gsub! '&amp;', '&'
          code = MiniSyntax.highlight(code, lang)
          #code.gsub! '&', '&amp;'
          #code.gsub! /&amp;(\w+;)/, '&\\1'
        end
        code.gsub! "\n", '&#x000A;'
        %Q(<code>#{hallocode}</code>)
      end

      # allow <dl>s (not implemented in RDiscount)
      html.gsub! %r(<p>(.+?)\n:\s+(.+?)</p>), "  <dt>\\1</dt>\n  <dd>\\2</dd>"
      html.gsub! %r((</(p|div|figure|h1|h2|table|pre)>\n+)  <dt>)m, "\\1<dl>\n  <dt>"
      html.gsub! %r(</dd>\n\n<)m, "</dd>\n</dl>\n<"

      # remove uglyness and create beautiful HTML5
      html.gsub! %r(<p><figure), '<figure'
      html.gsub! %r(</figure></p>), '</figure>'
      html.gsub! /\n\n/, "\n"
      html.gsub! /([^>])\n/, '\\1 '
      html.gsub! /<h2 id='_?(.+?)_?'>(.+?)<\/h2>/ do
        text = $2
        "<h2 id=\"#{ $1.gsub('_', '-') }\">#{hallo text }</h2>"
      end
      html.gsub! %r(<h2>(.+?)</h2>) do
        headline = $1
        id = headline.gsub(/<.+?>/, '').gsub(/\(.+?\)/, '').gsub('Update:', '').gsub(/^\d+\. /, '').strip.to_slug
        %Q(<h2 id="#{halloid}">#{halloheadline}</h2>)
      end
      html.gsub! /<li>/, '  \\0'
      html.gsub! "", ''
      html.gsub! /<(\w+)( (\w+)='(.+?)')>/ do
        "<#{hallo $1 }#{ $2.gsub('\'', '"') }>"
      end
      html.gsub! /<img(.+?) ?\/>/, '<img\\1>'

      # fix image URLs
      html.gsub! 'images', 'images'

      html
    end

    def initialize_engine
      super
      return if defined? ::ERB
      require_template_library 'erb'
    end
  end
  %w(mderb rmd).each do |ext|
    register ext, MdErbTemplate
  end
end

# some magic to get the best indented HTML5 ever ;)
require 'rack/utils'
module ::Rack
  class CleanUp
    include Rack::Utils

    def initialize(app, options = {})
      @app = app
      @options = options
    end

    def call(env)
      status, headers, response = @app.call(env)
      headers = HeaderHash.new(headers)

      if !STATUS_WITH_NO_ENTITY_BODY.include?(status) and
         !headers['transfer-encoding'] and
         headers['content-type'] and
         headers['content-type'].include?("text/html")

        content = ""
        response.each { |part| content += part }
        content.gsub! %r(\n(\s*\n)+), "\n"
        content.gsub! %r(<((img|link|meta|hr|br).*?) ?/>), '<\\1>'
        content_tmp = ''
        indent = 0
        failure_indent = 999
        failure = 0
        tag_name = ''
        content.each_line do |line|
          line =~ %r(^( *)([</]([\w\*]+))?.+?(</(\1)>)?)
          current_indent = $1.length
          current_tag_name = $3
          additional_indent = (%w(img meta link h1 *).include?(tag_name) ) ? 0 : 2
          if current_indent > indent + additional_indent
            failure = current_indent - indent - additional_indent
            failure_indent = current_indent - additional_indent
          elsif failure_indent > current_indent
            failure = 0
            failure_indent = 999
          end
          line.gsub! /^#{' ' * failure}/, '' if failure
          indent = current_indent
          tag_name = current_tag_name
          content_tmp << line
        end
        content = content_tmp
        headers['content-length'] = bytesize(content).to_s

        [status, headers, [content]]
      else
        [status, headers, response]  
      end
    end
  end
end
use ::Rack::CleanUp


# Automatic sitemaps
# activate :slickmap

# CodeRay syntax highlighting in Haml
# activate :code_ray

# Automatic image dimension calculations
# activate :automatic_image_sizes

# Per-page layout changes
# With no layout
# page "/path/to/file.html", :layout => false
# With alternative layout
# page "/path/to/file.html", :layout => :otherlayout

# Helpers

require File.join(Dir.getwd, 'helpers', 'blog_helper')
require File.join(Dir.getwd, 'helpers', 'jammit_helper')
helpers do
  include BlogHelper
  include JammitHelper

  def compress_javascript(javascript)
    compressor = ::YUI::JavaScriptCompressor.new(:munge => true)
    compressor.compress(javascript)
  end
end


# Change the CSS directory
# set :css_dir, "alternative_css_directory"



# Change the JS directory
# set :js_dir, "alternative_js_directory"



# Change the images directory
# set :images_dir, "alternative_image_directory"


# Build-specific configuration
configure :build do
  # For example, change the Compass output style for deployment
  # activate :minify_css

  # Minify Javascript on build
  # activate :minify_javascript

  # Shrink/smush PNG/JPEGs on build
  # activate :smush_pngs

  # Enable cache buster
  # activate :cache_buster

  # Generate ugly/obfuscated HTML from Haml
  # activate :ugly_haml


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