Sass Pathways

Ward Penney

Sass’s killer feature was truly the @import statement. When a Sass file is imported, all the variables and mixins defined previously are now available in any subsequent imported file. So, it is very good practice to define a single top-level stylesheet that you link to from the <HEAD> of your page layout. In a Rails implementation, this would likely be the application.scss. This root file is then compiled into a CSS file of the same name. This file and its subsequent imported partials I refer to as a pathway.

Although most sites do not require it, you may have more than one pathway. There are several reasons for this, but they are often one of these:

  • An “admin” view with a different design from the main site
  • A live style guide with all of its extra CSS used to present the guide itself
  • A new design in progress, with a corresponding separate layout
  • A new pathway with refactored Sass and corresponding layout

When Sass compiles a pathway, it runs line-by-line from the top to the bottom in linear fashion, delving recursively into any additional Sass partials referenced with an @import statement. As Sass compiles the pathway, it loads any variables, mixins, functions, placeholders and declarations into the global namespace for that pathway. For example, if you define a variable at the top of a pathway, it is available for use later in the pathway, including in other files imported after the variable’s declaration.

Partials

Pathways @import partials from the codebase, therefore it is not uncommon for two pathways to share a common partial. For example, if you needed the same color palette variables in a new design you were working on, you might @import a _colors.scss partial in more than one pathway.

When naming Sass files, partials that are imported should begin with a leading underscore. This is mostly a convention to let developers know this is not a root Sass pathway file, and it is intended to be imported.

The important thing here is to understand that for a given Sass load path, the namespace for declarations is global and the @import statement propagates those declarations down into the partials.

Seperate rendering and non-rendering Sass

In a previous post Separate Rendering Sass From Non-Rendering Sass, I covered how and why to separate these types of Sass.

As I mentioned in that article, variables, functions, and mixins should be separated from Sass declarations that result in CSS being generated. Separating the two gives you:

  • flexibility among Sass pathways,
  • clarity among imports without redundancy,
  • speed for the developer who may now assume access to all functions, variables, and mixins in a pathway

A simple file structure

Let’s start with a simple setup that only separates rendering and non-rendering Sass.

+-- application.scss
+-- base/
|   +-- _colors.scss
|   +-- _grid-settings.scss
|   +-- _icon-variables.scss
|   +-- _variables.scss
+-- patterns/
|   +-- _cards.scss
|   +-- _posts.scss
|   +-- _layouts.scss
|   +-- _lists.scss
|   +-- ...

And the root Sass pathway file:

application.scss

@import "base/colors";
@import "base/grid-settings";
@import "base/icon-variables";
@import "base/variables";

@import "patterns/cards";
@import "patterns/posts";
@import "patterns/layouts";
@import "patterns/lists";

The base folder holds all the non-rendering Sass. patterns folder holds all the Sass declarations that will result in CSS. Whenever a new pattern is made, the developer can safely assume they have access to any variable, mixin, or function that exists.

Easily create a new pathway

Suppose you made an additional Sass pathway:

new_lander.scss

@import "base/colors";
@import "base/grid-settings";
@import "base/icon-variables";
@import "base/variables";

@import "base/hero";
@import "base/signup-box";

Now your alternate layout and pathway has access to all of the variables, functions, and mixins from the other pathway.

Arguments against

Not all patterns need all variables, functions, and mixins. Isn’t this exposing too much?

True, not all patterns will use everything, but there is an unbalanced trade-off in favor of always keeping variables, mixins, and functions in the global scope.

Benefits:

  • Easy creation of additional pathways.
  • Developer never has to search to see if what they need is already imported, which becomes difficult with @import statements in partials.
  • Chances for naming conflict among variables, functions, and mixins are lowered because conflicts are addressed as they are built.
  • No accidental duplicate @import of rendered CSS being sent down to the browser.
  • Very explicit root pathway file: you know what’s in there and what isn’t without having to go dig for it.
  • Negligible performance impact (if you have compile-time performance problems, try libsass or remove use of @extend from deep nesting).