Large Scale Less Part 2 - Fun With Importing

by Matt Perdeck 13. April 2013 14:09

In LESS, importing files works differently from importing files in CSS. This can have some unintended consequences. This part 2 of an article series about introducing LESS in medium to large projects goes deeper into these importing issues.

Contents

Differences between LESS importing and CSS importing

On a large project with lots of styling to maintain, you'll probably split your styles into different files - for example a stylesheet per page. Some of your stylesheets will be sharing common styles. You might wind up with something like this:

Content directory
    Categories.less
    Main.less
    Common directory
        Library.css
        Shared.less
        Headers.less
        common.png

Just as CSS allows you to share stylesheets using the @import rule, so LESS has its own @import directive.

LESS allows you to import both LESS files and CSS files using the @import directive:

/* you can import both .less and .css files into .less files */
@import "Common/Library.css";
@import "Common/Shared.less";

Although both @imports look the same, they behave differently:

  • CSS's @import imports the CSS file at page load time. The browser has to load the imported stylesheet as a separate file over the Internet.
  • LESS's @import behavior depends on whether you import a CSS file or a LESS file:
    • If you import a CSS file, the compiler generates an @import rule in the generated CSS - so you get the same behavior as with CSS's @import. Because @import rules must come before any other statements other than @charset and @import, the compiler will move any @import rules to the top of the generated CSS file.
    • If you import a LESS file, the LESS compiler effectively inserts its LESS definitions into the importing file, and then compiles the importing file. So it generates one CSS file, containing styles from both the importing file and the files that were imported. As a result, the browser needs to load a single, but bigger, file over the Internet.

Before we look at this a bit further, a few things to keep in mind:

  • You can import the content of a CSS file into a LESS file at compile time, by changing the extension of the CSS file from .css to .less. This is because LESS is superset of CSS. If you compile such a renamed file with the LESS compiler, it will simply regenerate the original CSS.
  • Always use /, never \. When used to import LESS files, LESS's version of @import effectively loads files from the file system, instead of over the Internet. So it would make sense if you could use backslashes in that case. However, if you do, the lessc compiler won't rewrite relative URLs.

Import at compile time instead of page load time

As an example of importing at compile time, assume we have a file Common\Shared.less:

html, body, table {
    margin: 0;
    padding: 0;
}

And a file Categories.less:

@import "Common/Library.css";

@import "Common/Shared.less";

.categories {
    img { padding: 5px; }
}

This will be compiled to this CSS:

@import "Common/Library.css";

html, body, table {
    margin: 0;
    padding: 0;
}

.categories img { padding: 5px; }

As you see, Shared.less and Categories.less have been compiled into the one CSS file. But, the CSS file Library.css still needs to be loaded separately (unless you combine it in a later build step).

Relative URLs in imported LESS files

A tricky issue arises when you import LESS files from a different directory, and the imported LESS file has relative URLs - such as image URLs or URLs of imported CSS files.

s in the imported file.

Assume we have this file Common\Headers.less:

@import "Library.css";

h1 {
    background-image:url('common.png');
}

And this file Main.less:

@import "Common/Headers.less";

.main {
    img { padding: 5px; }
}

We saw that when Main.less is compiled into a CSS file, the contents of Common\Headers.less winds up in that CSS file as well, because it is imported into Main.less.

However, Headers.less sits in a sub directory Common and it contains the relative URLs Library.css and common.png. The issue is whether those relative URLs will be copied as-is in the generated CSS, like so:

@import "Library.css";

h1 {
    background-image:url('common.png');
}

.main {
    img { padding: 5px; }
}

Or whether the compiler assumes they refer to files that are actually in the Common directory and rewrites them accordingly:

@import "Common/Library.css";

h1 {
    background-image:url('Common/common.png');
}

.main {
    img { padding: 5px; }
}

The first option assumes that all relative URLs are always relative to the LESS file you're compiling, while the second option assumes they are relative to the LESS file where the URL is mentioned.

The second option intuitively makes sense, because that is how the CSS @import rule behaves. This also makes life much easier, because when you write a shared LESS file, you don't have to predict which file is going to import it.

Things never seem this easy though:

  • The lessc compiler (version 1.3.3) by default uses the first option - no rewriting of relative URLs. To get lessc to rewrite URLs, you have to use the --relative-urls option:
    lessc style.less style.css --relative-urls
  • Web Essentials 2.5.1 rewrites URLs in styles, but not those in @import statements.
  • The LESS language definition itself is silent about the treatment of relative URLs in imported LESS files, leaving the door wide open for different interpretations and confusion.

Whether you prefer the first option (no rewriting) or the second option (URLs are relative to the file mentioning them), you will want to make a decision up front, and make sure that your LESS compiler and build project support your decision.

Dealing with multiple imports of the same file

In a project with multiple shared files, you could easily have a situation where the same file is imported multiple times. For example:

Common\Shared.less:

html, body, table {
    margin: 0;
    padding: 0;
}

Common\Headers.less:

@import "Shared.less";

h1 {
    background-image:url('common.png');
}

Main.less:

@import "Common/Shared.less";
@import "Common/Headers.less";

.main {
    img { padding: 5px; }
}

Here, the file Shared.less is imported twice when Main.less is compiled - once by Main.less itself and once by Header.less when it is imported by Main.less.

This is not really an issue with the CSS version of @import, because when a file is imported twice, the second import will come from browser cache. However, in the LESS version, the doubly imported file will appear twice in the generated CSS, giving you a CSS file that is bigger than necessary.

How to solve this depends on the version of LESS you're using:

  • In version 1.3.3 or earlier, @import does not keep track of repeated imports of the same file. To prevent double imports, use the directive @import-once.
  • In version 1.4.0 or later, @import works the same as the @import-once directive in 1.3.3. @import-once has been removed.

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

Books

Book: ASP.NET Site Performance Secrets

ASP.NET Site Performance Secrets

By Matt Perdeck

Details and Purchase

About Matt Perdeck

Matt Perdeck PresentingMatt has written extensively on .Net and client side software development.

more >>