Sass Reference
Article

Import

By Hugo Giraudel

CSS has been providing the import feature (through the @import directive) for ages now, unfortunately it is not of great help. Indeed, as busted by Steve Souders a couple of years ago, using @import prevents some browsers (if not all) to load stylesheets in parallel, which is a huge performance drawback.

That’s too bad because being able to split styles across several files is a blessing when building clean and modular stylesheets. So it seemed that the import feature was born to die, until Sass came up a few years back and extended it so it can be used effectively when setting up organized stylesheets. When using the @import directive in stylesheets, Sass will take the whole content of the imported file and purely replace the import statement with it.

This way, we can seamlessly use as many files as we want in our development environment, only to have a single file in production. Actually most Sass-based architectures use this pattern: many files organized in folders for components, vendors, utilities and so on, all imported in a single stylesheet that gets compiled to CSS.

@import 'vendors/normalize';

@import 'utilities/variables';
@import 'utilities/mixins';

@import 'components/images';
@import 'components/blockquotes';

@import 'partials/header';
@import 'partials/footer';

Differences with CSS imports

Aside from the performance concerns just mentioned, there are several differences between the original CSS imports and the ones provided by Sass.

For starters, CSS imports must come at the very top of a stylesheet (only the @charset directive might come before) while Sass imports can appear pretty much anywhere in a stylesheet.

.foo { content: 'bar'; }

/* Invalid CSS import */
@import 'foo.css';

/* Valid Sass import */
@import 'foo.scss';

On top of that, Sass will assume a regular CSS import in some circumstances and will leave the line untouched without trying to fetch and include the requested file at all. There are 4 cases for this:

  • When the file path is passed through the url() function;

    // Compiled to a regular CSS import because of `url()`
    @import url('foo.css');
    
    // Sass import (replaced with file content)
    @import 'foo.scss';
  • When the import statement includes one or several media types (used to restrict the file fetching to some media types only); of course Sass does not know about media types since it gets compiled long before ever getting to the browser;

    // Compiled to a regular CSS import because of `print`
    @import url('foo.css') print;
  • When the given path is absolute;

    // Compiled to a regular CSS import because of `http://`
    @import 'http://rainbow.unico.rn/foo.css';
  • When the file extension is .css; note that Sass does not require the extension to be present in the file name; if it is, it will honor it; if it is not, it will assume a Sass/SCSS file.

    // Compiled to a regular CSS import because of `.css`
    @import 'foo.css';
    
    // Both Sass imports are identical
    @import 'foo';
    @import 'foo.scss';

Let’s add to this comment that leading underscores in case of Sass partials are not required either for files to be correctly imported. Sass will try both with and without the underscore.

// Will try to get `foo.scss` or `_foo.scss`
@import 'foo';

Last, Sass imports can be chained, not that it presents any specific benefits except for reading concerns.

@import
    'foo',
    'bar',
    'baz';

Import once

Sass does not provide a way to restrict a file to a single import. While that may not seem like a big deal in most cases, when working in incredibly large Sass code bases, the risk of a file getting imported twice is getting bigger and bigger.

That being said, with nothing but Sass tools, it is perfectly possible to build a little system that prevents any file from being imported several times.

/// List storing names of imported modules
/// @type List
$imported-modules: () !default;

/// Module export mixin
/// This mixin helps making sure a module is imported once and only once.
/// @access public
/// @param {String} $name - Name of exported module
/// @require $imported-modules
@mixin exports($name) {
    @if not index($imported-modules, $name) {
        $imported-modules: append($imported-modules, $name) !global;
        @content;
    } @else {
        @warn 'Module `#{$name}` has already been imported.';
    }
}

Then, we only have to wrap the content of our files in the exports mixin, like so:

// _reset.scss

@include exports('reset') {
    // Content of file
}

Let’s try to import our _reset.scss file twice in our main stylesheet to see what’s going on:

// main.scss

@import 'reset';
@import 'reset';

Thanks to our little mixin, the content of _reset.scss will be included once and only once, and a message will be printed in the console:

Module reset has already been imported.

As you can see, it does not involve too much code to achieve the desired effect so that is something you could consider if you’re afraid of multiple imports of a file.

File globbing

Glob is a name borrowed from Unix-like environments, defining a set of filenames with wildcard characters. It gets quite useful when wanting to import all files from a folder, or all files from all folders from a folder, or all files from a specific extension, and so on.

Most languages providing a file system management have a way to manipulate globs, but not Sass. There is a good reason for this: CSS is an order-sensitive language, and glob-based imports make the ordering of content unclear and likely fragile.

Now, if you really want to go down the rabbit hole, there is a Ruby Sass gem called Sass Globbing that allows import globs.

// Possible with Sass globbing
@import 'library/mixins/*';

Note that I don’t recommend this, since we cannot predict in what order files will be included, there could be terrible side effects with the end result.

Engine compatibility

The @import feature is fully compatible across all Sass engines and there is no known bug to this day about its implementation.