Select Page

Less CSS Won’t Import The Same File Twice When Globbing

Ben Nadel
Published: April 12, 2024

When organizing my Less CSS files in a multi-page web application (MPA), I often have a folder full of modules in which each .less file represents a unique component. For the most part, the order in which these modules are imported is irrelevant since they represent isolated definitions. The exception to this rule is the theming and design system modules. In order to work with the CSS cascade, it’s important that these design system modules be imported first such that other modules can override properties locally without having to worry about CSS selector specificity. Thankfully, Less CSS makes this easy with its automatic (default) deduplication of import paths.

For this exploration, I am using this package.json file:

{
	"scripts": {
		"build": "lessc --glob ./src/main.less ./dist/main.css"
	},
	"devDependencies": {
		"less": "4.2.0",
		"less-plugin-glob": "3.0.0"
	}
}

The less package is the Less CSS compiler; and the less-plugin-glob package allows me to use * and ** in my @import paths.

Now, consider my modules folder with the following .less files (I’m including the content of each file in a single snippet here in order to reduce the noise):

/* file: ./modules/a.less */
.a::before {
	content: "a" ;
}
/* file: ./modules/b.less */
.b::before {
	content: "b" ;
}
/* file: ./modules/c.less */
.c::before {
	content: "c" ;
}
/* file: ./modules/design-system.less */
.design-system::before {
	content: "Design System" ;
}

If my main .less file looked like this:

@import "./modules/*.less" ;

… then, compiling the CSS file will import each .less file into my main.css. The less files will be imported in lexicographic order (ie, alphabetically) by file name. Which results in the following CSS output:

.a {
  content: "a";
}
.b {
  content: "b";
}
.c {
  content: "c";
}
.design-system {
  content: "Design System";
}

As you can see, each file was imported in lexicographic order by file name. And, unfortunately, this puts our design system content at the very end, which is problematic from an overrides perspective. Consider this HTML:

<h1>
	Testing CSS Cascade
</h1>
<p class="design-system c"></p>

The intent here is use the base styles from .design-system and then override some of those styles with .c. However, due to the CSS cascade rules, rendering this HTML page results in the following output:

Rendering of CSS rules shows that they are applied in order of definition if specificity is the same.

As you can see, .c failed to override the .design-system because the .design-system was defined last in the CSS file (and has the same specificity).

To fix this, we can update the main .less file to specifically import high-priority Less CSS files first, before executing our globbing import:

// In order to work with the cascade, we need to include our design system definitions
// first. This way, all other modules can consume and then override properties of the
// design system, even if the selector specificities are the same (last one wins).
@import "./modules/design-system.less" ;
// NOTE: The design-system will NOT BE included twice.
@import "./modules/*.less" ;

As you can see, both @import statements reference the ./modules/ folder. But, we’re explicitly importing the design-system.less file first before globbing the rest of the ./modules/ folder. And, when we compile the CSS file this time, we get the following output:

.design-system::before {
  content: "Design System";
}
.a::before {
  content: "a";
}
.b::before {
  content: "b";
}
.c::before {
  content: "c";
}

This time, the design-system.less file content was placed at the top of the compiled CSS output due to our explicit @import. And, most importantly, it wasn’t included a second time as part of the *.less globbing. This is because the Less CSS compiler won’t import the same file twice (the default behavior).

Now, if we go to render the previous HTML page, we get the following output:

Rendering of CSS rules shows that they are applied in order of definition if specificity is the same.

This time, since the design-system.less file was included first, out c.less file definition is able to override the content property.

This is a rather helpful behavior when the execution / import order of a small number of .less files is important. It means that we can be explicit about importing a few files first and then just brute-force globbing the rest of the files without having to worry about duplication. This keeps life simple.

Check out the license.


https://bennadel.com/go/4630

Source: www.bennadel.com