Dynamic CSS in Rails Asset Pipeline, Compile on Fly

Dynamic CSS in Rails asset pipeline, compile on fly

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!

How do I create dynamic CSS in Rails?

Currently there is a lot of options to generate dynamic css in rails.

You can use less css - is an extension to CSS with extra features.

Gem Less css for rails provides integration for Rails projects using the Less stylesheet language in the asset pipeline.

If you are using twitter bootstrap you may check this out less rails bootstrap.

Also you can use one more CSS extension language Sass for generating CSS. Here is a Saas rails gem.

Check out Dynamic CSS in Rails and Render Rails assets to string blog posts and article about Asset Pipeline

Related SO questions:

  • Best way to handle dynamic css in a rails app
  • Dynamic CSS in Rails asset pipeline, compile on fly
  • Rails: change CSS property dynamically?

What is the convention in Rails (with asset pipeline) for internationalization inside CSS?

You don't need to generate a new CSS file for each locale - that borders on madness. Why does your CSS care about the text content of your website?

I think your best bet would be to use a data-attribute to grab the value...

<div class='bs-docs-example' data-before-content='<%= t.css_example %>'>
<!-- html here -->
</div>

And then in your css:

.bs-docs-example:after {
content: attr(data-before-content);
}

You could probably find a way to extract this into a partial (or helper), so that your erb file ends up like this:

<%= docs_example do %>
<!-- html here -->
<% end %>

And a helper method:

def docs_example
content_tag(:div, class: "bs-docs-example", "data-before-content" => t.css_example) do
yield
end
end

Dynamic CSS using Sprockets

After a long day I was able to solve my problem thanks to John Feminella and his post on google. The challenging part for me was figuring out how to create a new Sprockets::Context. Luckily John's solution doesn't require a Context.

Updated gist here

Attempt #1

This code does not allow importing css files from the asset pipeline.

@import "foundation"; works because foundation is loaded as a compass module

@import "custom_css"; results in an error message saying the file could not be found

def call(template)
erb = ActionView::Template.registered_template_handler(:erb).call(template)

%{
options = Compass.configuration.to_sass_engine_options.merge(
:syntax => :scss,
:custom => {:resolver => ::Sass::Rails::Resolver.new(CompassRails.context)},
)
Sass::Engine.new((begin;#{erb};end), options).render
}
end

Attempt #2

This code fails to embed base64 urls using asset-data-url

  def call(template)
erb = ActionView::Template.registered_template_handler(:erb).call(template)

%{
compiler = Compass::Compiler.new *Compass.configuration.to_compiler_arguments
options = compiler.options.merge({
:syntax => :scss,
:custom => {:resolver => ::Sass::Rails::Resolver.new(CompassRails.context)},
})
Sass::Engine.new((begin;#{erb};end), options).render
}
end

Attempt 3

Turns out you can use empty values while creating the context. Code below works in development but throws an error in production.

ActionView::Template::Error (can't modify immutable index)

It appears the error occurs in Sprockets::Index which is used instead of Sprockets::Environment in production. Switching to Sprockets::Environment doesn't solve the problem either.

  def call(template)
erb = ActionView::Template.registered_template_handler(:erb).call(template)

%{
context = CompassRails.context.new(::Rails.application.assets, '', Pathname.new(''))
resolver = ::Sass::Rails::Resolver.new(context)

compiler = Compass::Compiler.new *Compass.configuration.to_compiler_arguments
options = compiler.options.merge({
:syntax => :scss,
:custom => {:resolver => resolver}
})

Sass::Engine.new((begin;#{erb};end), options).render
}
end

Maintain RTL version of stylesheets with rails asset pipeline

Unfortunately, none of the other answers really suggested the solution I was looking for, so after digging into matters, I was able to come up with a simple gem that could flip the stylesheet based on the filename.

So by including that gem and serving up a version of the stylesheet with '-flipped' appended to the name, I am now able to serve an automatically flipped version both in dev and production.

Find the gem on rubygems.org: stylesheet_flipper

Find a usage description on gihub: monibuds/stylesheet_flipper

Live assets compilation only for one file in Rails

Option 1 - put sample.css.scss.erb in a path different from config.assets.manifest (default="public/assets") and add it with javascript_include_tag

Option 2 - Remove it from the config.assets.precompile

config.assets.precompile -= %w( sample.css.scss.erb )

Make sure you clean then precompile to test it.

I did not test either option, please let us know if any works for you.

Rails Asset Pipeline with conditional CSS files

i have a project with similar requirements and i use the techniques shown in the linked answer:

# app/views/layouts/application.html.haml
= stylesheet_link_tag "application", "labels/#{Whitelabel[:label_id]}"

# config/application.rb
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
config.assets.precompile += %w( active_admin.js active_admin.css labels/* )

this includes an additional stylesheet, that is not included in the application.rb

have a look at the full source: https://github.com/phoet/on_ruby/



Related Topics



Leave a reply



Submit