Precompiling SCSS: variable is undefined (Rails 4) - ruby-on-rails

The scenario
In /products.css.scss:
#import 'partials/colors';
#import 'partials/boxes';
#wrapper {}
In /partials/_colors.css.scss:
$light-gray: #ccc;
In /partials/_boxes.css.scss:
#box-light-gray {
background-color: $light-gray;
width: 50px;
height: 50px;
}
The problem
It happens on rake assets:precompile, in production environment:
Sass::SyntaxError: Undefined variable: "$light-gray".
(in /partials/_boxes.css.scss:2)
My thoughts
The file /partials/_boxes.css.scss doesn't have the variable $light-gray by itself – and here I have to agree with rake assets:precompile. The point is: how do I do to make rake recognize the injection of $light-gray into /partials/_boxes.css.scss?
I think rake is not matching the points of the puzzle because it doesn't know how SCSS works. I feel I'm missing something related to SCSS in couple with rake.

This is a tricky problem with the current version of Sprockets and sass-rails. You would think that the *= require lines in your application.css file would load the variables in order so that they would be available to all scss files, but it does not. Here is what the asset pipeline Rails guide says about it:
If you want to use multiple Sass files, you should generally use the
Sass #import rule instead of these Sprockets directives. Using
Sprockets directives all Sass files exist within their own scope,
making variables or mixins only available within the document they
were defined in. You can do file globbing as well using #import "",
and #import "*/*" to add the whole tree equivalent to how
require_tree works. Check the sass-rails documentation for more info
and important caveats.
In other words, you have a two choices:
#import your variables in each file as you need them
Use #import in your application.css.scss file instead of *= require
If you go with the first option, just drop in #import 'partials/colors'; to the top of _boxes.css.scss.
With the second option, you just need to #import your stylesheets in your application.css.scss once (in the proper order), then your variables in mixins will be available to all stylesheets. You're still using the asset pipeline here so precompilation will still work fine, but you're letting sass-rails work its sass magic. Your application.css.scss would look something like this:
#import 'partials/colors';
#import 'partials/*';
#import '*';
Be warned though, there is currently a bug with sass-rails. If you have .erb stylesheets, sass-rails won't be able to import them by wildcard if they're in a seperate folder.

Without modifying your scss, I think your best option is to put #import 'partials/colors'; in your _boxes.css.scss file also. The biggest drawback with #import is that it includes an additional http request, however since you are precompiling your assests I'm not entirely sure that is still an issue.
Potential refactoring option:
_colors.css.scss
$light-gray: #ccc;
_box-sizes.css.scss
.small-box{
width: 50px;
height: 50px;
}
products.css.scss
#import 'partials/colors';
#import 'partials/box-sizes';
.small-light-grey-box{
#extend .small-box
background-color: $light-gray;
}
Again, this is just an example. There are countless ways you could refactor your scss and html to get the desired outcome.

Related

How do I organize SCSS in rails?

I just bootstrapped a new rails project and I was trying to reference from an open sourced rails project on how they architect their app.
Link to the open sourced project
I noticed they have multiple layouts e.g. admin, application, home ..etc. And each may load in different stylesheets via stylesheet_link_tag.
For example in focus_home.html.erb:
<!-- Load styles -->
<%= stylesheet_link_tag 'focus_home' %>
<%= stylesheet_link_tag 'app/components/modal' %>
And in their app/assets/stylesheets directory, they have focus_home.scss
I try to follow their architecture where I have multiple css files and I call different stylesheets with different layout.
I created my home.scss to be used by home.html.slim
when I started my rails server and try to load the home page, the following error occurs
Asset filtered out and will not be served: add
`Rails.application.config.assets.precompile += %w( home.css )` to
`config/initializers/assets.rb` and restart your server
Basically it asked me to tell rails to precompile home.scss. However, when I browse through the open source project's code base. It doesn't seem to have this line of code. The precompilation just seem to happen like magic.
So I am wondering what I am missing ?
==============
Edit: Further explain the case
In their project they DO NOT have an application.css file like normal rails project.
/* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. *
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * *= require_tree . *= require_self */
Instead, in their application.scss, it goes like
#charset "utf-8";
#import "vars/base";
#import "constants";
// Libraries
#import 'bootstrap';
#import 'app/mixins/*';
#import 'basscss';
#import '_deprecated/basspluss';
#import '_deprecated/utility';
#import '_deprecated/nocss_vars';
#import '_deprecated/nocss';
#import '_deprecated/nocss_mq';
#import "app/main";
#import "base/*";
#import "utilities/*";
#import "app/components/*";
#import "components/*";
#import "app/slop";
#import 'libs/owl.carousel';
#import 'libs/owl.transitions';
#import 'libs/owl.theme';
#import 'c3/c3'
So I wonder how they actually do their precompilation??
You don't need to use Sprockets comments in your application.scss and must only use #import CSS rules.
From rails-sass documentation:
Sprockets provides some directives that are placed inside of comments
called require, require_tree, and require_self. DO NOT USE THEM IN
YOUR SASS/SCSS FILES. They are very primitive and do not work well
with Sass files. Instead, use Sass's native #import directive which
sass-rails has customized to integrate with the conventions of your
Rails projects.
Then, if you have any other .scss that needs to be precompiled, you will have to explicitly add them using the Rails.Application.config.assets.precompile directive, then the sprockets railtie will do the rest of the job!
To answer your original question, the reason why the open source project do not need to specify assets to precompile is because they are using config.assets.compile = true in the config/environment/production.rb file. This is obviously a very bad practice and I don't recommend you to switch this directive to true in a production environment... you will end up with a slow code making a lot of requests and there a slow page load.

How should I go about managing sass code when using bootstrap-sass?

The documentation says:
Import Bootstrap styles in app/assets/stylesheets/application.scss:
// "bootstrap-sprockets" must be imported before "bootstrap" and "bootstrap/variables"
#import "bootstrap-sprockets";
#import "bootstrap";
bootstrap-sprockets must be imported before bootstrap for the icon fonts to work.
Make sure the file has .scss extension (or .sass for Sass syntax). If you have just generated a new Rails app, it may come with a .css file instead. If this file exists, it will be served instead of Sass, so rename it:
$ mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss
Then, remove all the //= require and //= require_tree statements from the file. Instead, use #import to import Sass files.
Do not use //= require in Sass or your other stylesheets will not be able to access the Bootstrap mixins or variables.
But if I do as they say, my other stylesheets won't be included automatically, as they did before that. Should I include every stylesheet explicitly in layout? AFAIU, that would also make me have separate stylesheets in production environment, instead of one as it would have been without bootstrap-sass.
There are several things here. First, by default each stylesheet is served in separate http request in development, and in one request in production (they are precompiled into one file). If we follow the docs, we'll end up having one request in development as well, which would negate performance benefit of compiling only the file that has changed. If we don't, we might end up having bootstrap several times in our stylesheets. In case we need some variables or mixins in several stylesheets.
So I suggest having it this way:
application.sass (do note that I put require_self before require_tree .):
/*
*= require_self
*= require_tree .
*/
// import bootstrap once
#import "bootstrap-sprockets"
#import "bootstrap"
.d1
text-align: center
welcome.sass:
// in other files import only some particular partials
#import "bootstrap/variables"
#import "bootstrap/mixins"
.d2
text-align: right
But if I do as they say, my other stylesheets won't be included automatically, as they did before that.
Yes, you a right.
1) You shouldn't remove your require directives from application.scss. They don't want to use require directives because in this case you don't have ability to use SASS variables and mixins inside of included files.
2) Just rename application.css to application.scss. They want it because in this case you will have ability to use #import directives inside application.scss file. This is mean that you will have ability to use SASS variables and mixins inside of included files.
Should I include every stylesheet explicitly from layout?
No, just leave them in application.scss
AFAIU, that would also make me have separate stylesheets in production environment, instead of one as it would have been without bootstrap-sass.
No, you will use one application.scss in different environments.

Rails 3.2 _partial.scss.erb variable scoping: full compilation before #import?

Smarter minds than mine:
We have several SCSS partials which define mixins, variables, and other useful goodies. We now have need of using some embedded ruby in one of these partials, like so:
_partial.scss.erb
$text-color: #555555;
<%= "" %>
app.scss
#import "partial"
.text {color: $text-color;}
If the file is named _partial.scss it works fine (without any ruby, of course). When named .erb I get an error that the variable $text-color is not defined.
I'm using the sass_rails_patch gem. I can't say for sure, but I believe the _partial.scss.erb is being compiled all the way into _partial.css and then being imported into app.scss. This may be why $text-color is getting stripped out.
Any advice on how to get this flow working the way I expect? I would expect:
Evaluate ruby in .erb files, generate valid .scss.
Run #import statements on .scss
Do all the other SCSS compilation stuff, generate .css.
You most likely want to use Ruby script in your SCSS files in order to build asset paths (and that MUST be the only reason). If this is the reason, you can use SCSS functions to build them up :
Instead of:
background-image: url(<%= asset_path('general/side_shadow.png') %>);
Use this CSS/SCSS function instead:
background-image: url(asset-path("general/side_shadow.png", image));
Here's the doc for SCSS helpers : http://rubydoc.info/github/petebrowne/sprockets-sass/master/Sprockets/Sass/Functions
Hope this helps

How can I get Compass to automatically import all stylesheets in app/assets/stylesheets?

I'm trying to add Compass to my Rails 3.2 app, using compass-rails. How can I get it to automatically import all of the stylesheets in app/assets/stylesheets? At the moment I have to manually do #import 'filename'; in application.css.scss for each one.
Put all your scss files (except application.css.scss) in a different folder:
/application.css.scss
/all/hello.css.scss
/all/hi.css.scss
application.css.scss file like below will work.
#import "compass";
#import "all/*";
For bundling stylesheets, use the asset pipeline
If you're using the asset pipeline, this should happen automagically with:
/*
* In application.css
*= require_tree .
*/
Docs: http://guides.rubyonrails.org/asset_pipeline.html#manifest-files-and-directives
The important caveat is "Using Sprockets directives all Sass files exist within their own scope, making variables or mixins only available within the document they were defined in."
For mixins & vars, have your imports in one place, then import once
If you're heavy on the functions, try having a file like app/assets/stylesheets/base.css.scss that contains #import directives (wildcard or not) for all your mixin and var files. Then you only need to #import "base" once for every stylesheet and can still bundle your css using sprockets directives.

Use Rails 3.1 Asset Paths in a SCSS Partial

I have the following setup:
app/assets/stylesheets/application.css.scss
/*
*= require_self
*= require fancybox
*/
/* COLORS.. */
/* MIXINS... */
/* FONT STACKS... */
/* IMPORTS */
#import "reset";
#import "supergrid";
#import "rails";
#import "app";
#import "forms";
#import "layout";
In my various partials I'm having a real problem with the asset paths. When inside application.css.scss or anything loaded by the manifest, I can use:
.example { background-image: image-path("background.png"); }
However, when I'm using a partial, such as my _layout.css.scss partial, when I try the same thing the background-image property is simply omitted from the compiled file. It seems the SCSS asset helpers are not available inside partials?
Has anyone gotten this to work, am I missing something obvious? Or is it simply impossible to use asset helpers in partials? If so this is a major, MAJOR problem, as my entire app structure depends on SCSS variables and mixins which are shared among the partials.
I know that variables and mixins are not shared across the sprockets manifest, so if partials cannot access the asset helpers then I'm looking at having to concatenate everything into a single scss file, which pretty much defeats the purpose of both Sass and Sprockets.
Any help would be very much appreciated!
Strange, it should work.. Just few ideas:
Are you using up-to-date gems sass-rails and sprockets?
Try to rename .css.scss to .scss
Try to replace image-path('background.png') with asset-url('background.png', image) or image-url('background.png')
Try to remove require_self from application.css.scss
Try to remove all directives from application.css.scss and import those files with #import

Resources