Render Sass stylesheets dynamically

If - for whatever reason - you have to render stylesheets dynamically, the following snippet might be of help. It emulates what "sprockets" would to when precompiling your assets, and give your stylesheets access to all the regular bells and whistles (like asset_path, proper @imports etc):

class DynamicStylesheetsController < ApplicationController

    def show
      logical_path = RELATIVE_PATH_TO_YOUR_TEMPLATE
      path = File.join(Rails.root, logical_path)
      template = Sass::Rails::SassTemplate.new(path)
      environment = YourApp::Application.assets
      context = environment.context_class.new(environment, logical_path, Pathname.new(path))
      output = template.render(context)
      render :text => output, :content_type => 'text/css'
    end
    
end

Injecting information

I needed to inject some color values into my stylesheets, and did not want to run everything through ERB first (which is also hard, because of @import statements etc). So I did basically this:

  1. Before the template.render, insert:

    context.singleton_class.send(:define_method, :theme_colors) do
      HASH_OF_COLOR_VALUES # e.g. { 'primary' => [100, 250, 10] }
    end
    
  2. In an initializer, add

    module Sass::Script::Functions
      def color(color_name)
        assert_type color_name, :String
        context = @options[:custom][:resolver].context
        Sass::Script::Color.new(context.theme_colors[color_name.to_s])
      end
      declare :color, :args => [:string]
    end
    
  3. In your sass template, use something like

    $PRIMARY_COLOR = color(primary)
    

Performance

Sass is not especially fast (and it usually does not have to be). For my medium-sized stylesheet, rendering took about 0.5 seconds. It's probably still a good idea to

  • use some form of action caching or page caching
  • make sure you set proper expiration and cache headers

Caveat

This solution is probably somewhat brittle, since the methods used are not exactly part of a public API. If you know of a cleaner way to do this, please drop me a line.

Tobias Kraze over 8 years ago
This website uses short-lived cookies to improve usability.
Accept or learn more