12

I'm running into an upsetting issue when trying to share variables and mixins across Sass stylesheets.

If I use @import to include a global stylesheet – one which includes global colors, mixins, etc. – it gets included again when Rails combines all stylesheets referenced in the manifest file.

Alternatively, if my manifest file does not include the global stylesheet, but multiple files in the manifest import it, the global stylesheet will still be included more than once. GAH.

How can you get around this? Does Sass have secret inclusion guards? Am I doing something terribly wrong?

ClosureCowboy
  • 20,825
  • 13
  • 57
  • 71

4 Answers4

7

I do not understand why this is an issue for your specific question. Variables and mixin declarations should not lead to any selectors or declaration blocks in your generated css file. However, when you use mixins for multiple selectors, the corresponding declarations are included for every such selector. This is how SASS handles it.

So as long as it's only variables and mixins, it should not matter if they are included multiple times in the manifest file since this does not have an effect on the compiled file. On a site-node, I believe it to be good style that SASS forces you to explicitly declare each file's dependencies.

If, however, you also have selectors and declarations in the base file that you want to inherit from in separate files, then indeed these will be included multiple times even in the compiled file. It would still be interesting to know how to prevent this by configuration. However, if you treat each of your sass files to be one encapsulated set of rules without cross-file inheritance, then you should be able to prevent your issue by convention.

For example, when using bootstrap-sass, just the mixins and variable definitions can be brought in scope with;

@import "bootstrap/variables";
@import "bootstrap/mixins";
tombh
  • 377
  • 4
  • 10
emrass
  • 6,253
  • 3
  • 35
  • 57
  • 2
    I agree with having the line in each file so that you can see what that particular files dependancies are. But there should be a simple wau in SASS (and LESS) that allows you to include a file only once (like PHP require_once) in a manifest file. Further, it would be nice if there were a way to specify switch that spits the manifest imports into their own individual css files, in case you run into the IE 4095 selector limitation, like my team is right now (although we know we need to clean out CSS). – b01 Aug 15 '12 at 15:15
4

You can get around this by doing this:

/* 
 * application.css.scss
 *= require_self
 * ----> Note the missing = require_tree 
*/  

@import "global.css.scss"; // Defines global mixins etc
@import "users.css.scss"; // Uses mixins defined in _global.css.scss

Then don't import global.css.scssinside users.css.scss or any other dependent files.

If you haven't already, check out Ryan Bates screencast on SASS and sprockets in rails 3.1, this is what he does to solve this issue.

Yarin
  • 173,523
  • 149
  • 402
  • 512
David
  • 7,310
  • 6
  • 41
  • 63
  • Note that this means the dependent css files won't get rebuilt when you make changes [**See comments**](http://railscasts.com/episodes/268-sass-basics?view=comments#comment_152242) – Yarin Dec 21 '13 at 17:59
1

For now, until SASS 4 is released, I prefer modifiying the import lines to something like:

@if not-imported("font") { @import "font"; }

You will need a function named not-imported then, of course, looking like this:

$imported-once-files: () !default;

@function not-imported($name) {
  $module_index: index($imported-once-files, $name);
  @if (($module_index == null) or ($module_index == false)) {
    $imported-once-files: append($imported-once-files, $name);
    @return true;
  }
  @return false;
}

This works fine and maintains the interoperability with other tools (like StyleDocco). I wrote about it, here

Paul Wellner Bou
  • 532
  • 5
  • 16
0

It is currently not possible to use SASS to include files dynamically. @import cannot be used within control directives (e.g. @if) or mixins, using a variable in an import directive is erroneous syntax, and there is no directive for ending file execution early (which effectively would allow conditional imports). However, you can solve your issue by changing how you structure your style rules.

NOTE

There is a SASS plugin that will modify @import to only include files once. However,

  1. Your code will have added dependence on the environment
  2. We would all do better to understand how style rules are intended to be structured, which would avoid any duplicate rules.
  3. @import may soon be deprecated. The SASS team is planning "large-scale redesign of importing", so re-import protection might be part of future versions of SASS, as it is popular requested feature.

Solution

If you have styles that are conditionally included from multiple files, they are not 'global' styles. These styles should be encapsulated in mixins, in 'module' or 'library' files. The main idea is that importing one such file will not output any css. This way, you can import these files redundantly so you can use the mixins wherever you need them.

If you need to do this with variables, then:

  1. Make sure you define variables before including mixins, otherwise they are only available in the mixin scope. A good way to do this is to define all default variables in a vars.sass file, and import it along with imported modules/libraries so the variables are available globally.
  2. Define variables using !default in the mixin, so it can be overridden even before the mixin is called (like in a vars.sass file).

If you have top-level styles that you want to ensure are defined without having rigid inclusion rules, you can use this mixin:

$was-defined: () !default;
@mixin define-once($name) {
    @if not index($was-defined, $name) {
        $was-defined: append($was-defined, $name);
        @content;
    }
}

// Example:
@include define-once('body-typography') {
    body {
        font-size: 100%;
        line-height: 2em;
        color: #444;
        font-family: monospace;
    }
}

For more good practices on structuring your code like this, check out this article.

Community
  • 1
  • 1
Stephen M. Harris
  • 7,163
  • 3
  • 38
  • 44