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)
endUsed 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
How to Run a Simple File on Heroku
Rvm VS Native Installation of Ruby
Dealing with Large CSV Files (20G) in Ruby
How to Get Generators Call Other Generators in Rails 3
How to Pass an Argument When Calling a View File
Rails Two-Legged Oauth Provider
How to Integrate Hoptoad with Delayedjob and Daemonspawn
Override the Protect_From_Forgery Strategy in a Controller
How to Disable a Form Submit Button "A Là Ruby on Rails Way"
Attempting to Install Libv8, "Failed to Build Gem Native Extension"
How to Get Words Frequency in Efficient Way with Ruby
How to Configure Config.Yml So That I Can Install Devkit
How to Write a Rails Mixin That Spans Across Model, Controller, and View