Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic CSS in Rails asset pipeline, compile on fly

I'm building a site in Rails 3.2. Its been 3 years since I've touched Rails or Ruby, so I'm rusty on both, plus the last time I used rails is was Rails 2.3. Needless to say, please excuse any "simple" questions below.

Here are the specs

  • Multi Tennant CMS/Store Site
    • http://company1.mywebsite.com
    • http://company2.mywebsite.com
    • etc.
  • Each "Store" (aka sub-domain) can have its own look, feel, etc. through CSS customizations
    • The customizations can be performed in a UI within the app allowing the user to change basic variables of Bootstrap (i.e. @textColor, @bodyBackground, etc.)
  • I'm using the less-rails-bootstrap gem to the Twitter Bootstrap look/feel, etc.

Here are the challenges

  1. I need to be able to dynamically output the variables for the CSS into a file that gets mixed in to Bootstrap so the variables are picked up to create the final CSS
  2. When a user changes a variable for the CSS, the existing style is basically invalidated. I need the full CSS recompiled and written back out to disk, memory stream, or some other location where I can get my hands on it (remember this is using less)
  3. I need different CSS to spit out per sub-domain. Any suggestions on how to approach this?

Further complicating the matter...

...given that I essentially will have to find some way to compile the CSS on the fly, that means I have to include GEMS I typically would not in a production environment. Performance will be very important. Is there a way to isolate this? Once the CSS has been invalidated and regenerated, I could take the content and either write it out to disk or store is in some memcached/redis/etc. instance for performance.

Any comments, even if just to point me in a general direction would be appreciated.

Thanks!

like image 480
Ryan Griffith Avatar asked Dec 13 '12 13:12

Ryan Griffith


1 Answers

Here is the solution I finally landed on:

  • I ended up switching over to bootstrap-sass instead https://github.com/thomas-mcdonald/bootstrap-sass
  • Made the following changes to my application.rb file to ensure that the :asset group is always included despite the environment:

    if defined?(Bundler)
        # If you precompile assets before deploying to production, use this line
        # Bundler.require(*Rails.groups(:assets => %w(development test)))
        # If you want your assets lazily compiled in production, use this line
        Bundler.require(:default, :assets, Rails.env)
    end
    
  • Used the concepts provided by Manuel Meure (Thank you Manuel!) of Kraut Computing found at http://www.krautcomputing.com/blog/2012/03/27/how-to-compile-custom-sass-stylesheets-dynamically-during-runtime/ .

    • I made some adjustments to suit my own needs, but the core concepts illustrated by Manuel were the foundation for my compilation process.
  • In my model (lets call it "Site"), I have a snippet of code that looks like this:

    # .../app/models/site.rb
    ...
    
    BASE_STYLE = "
      @import \"compass/css3\";
    
      <ADDITIONAL_STYLES>
    
      @import \"bootstrap\";
      @import \"bootstrap-responsive\";
    ".freeze
    
    # Provides the SASS/CSS content that would 
    # be included into your base SASS content before compilation
    def sass_content
      "
      $bodyBackground: #{self.body_background};
      $textColor: #{self.text_color};
      " + self.css # Any additional CSS/SASS you would want to add
    end
    
    def compile_css(test_only = false, force_recompile = false)
    
      # SassCompiler is a modification of the information made available at the Kraut Computing link
      compiler = SassCompiler.new("#{self.id}/site.css", {:syntax => :scss, :output_dir => Rails.root.join('app', 'assets', 'sites')})
    
      # Bail if we're already compiled and we're not forcing recompile
      return if compiler.compiled? && !force_recompile && !test_only
    
      # The block here yields the content that will be rendered
      compiler.compile(test_only) {
        # take our base styles, slap in there some vars that we make available to be customized by the user
        # and then finally add in our css/scss that the user updated... concat those and use it as
        # our raw sass to compile
        BASE_STYLE.gsub(/<ADDITIONAL_STYLES>/, self.sass_content)
      }
    end
    

I hope this helps. I know its a deviation from the original post, but its deviated because this seemed to be the most attainable solution to the problem.

If I haven't answered a specific question you have, feel free to comment so I can expand where possible.

Thanks!

like image 196
Ryan Griffith Avatar answered Sep 28 '22 19:09

Ryan Griffith