Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Andrew Mercer
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Andrew Mercer

Executing JavaScript In The LESS CSS Precompiler

By
Published in , Comments (9)

The other day, when I upgraded my LiveReload app, some of my LESS mixins started to break. During the debugging process, I discovered that some of the mixins were executing JavaScript code directly in the LESS CSS markup. I had no idea that this was even possible in LESS CSS; and so, as with all things JavaScript, I had to start experimenting. I have no concrete ideas on what I would use this for; but, here's what I found. Please note that this is all conjecture as there is zero documentation (that I could find) on the use of JavaScript in LESS CSS.

JavaScript can only be evaluated as part of an assignment. By this I mean that you can't simply execute JavaScript in LESS CSS. Instead, you have to execute the JavaScript and then assign the result to a LESS variable or a CSS property. Example:

@foo: `javascript` ;

To denote JavaScript, you have to wrap your code in backticks. When you do this, LESS will evaluate the expression and return the result as a String. It does so, "as is;" meaning, it leaves all your quotes in-place. If you want to remove the surrounding quotes, you can prepend the tilde operator:

@foo: ~`"surrounding quotes will be removed"`

That said, here's what I came up with. I defined an "API" object that holds my user-defined functions (UDFs). I then inject this API object into the global container which allows the "api" instance to be referenced in all the other JavaScript code blocks:

// Use the backtick character to run JavaScript directly in LESS CSS. We are using a
// Function here because LESS calls (my theory) .toString() on the function and stores
// the return value. It seems that only Functions returns their "source code" when
// .toString() is called, which allows us to reuse the JavaScript in other JavaScript
// code block instances.
@api: `function(){

	// Set up the API that we want to expose in other JavaScript contexts.
	var api = {

		// This is just me testing some JavaScript stuff.
		getContent: function() {

			return( "This is yo' content!" );

		},


		// When executing a JavaScript expression, you can only execute one expression
		// at a time (ie, no semi-colons). Unless, you are in a function. The point of
		// the run() function is to allow the calling context a way to enter a function
		// context and get access to the API at the same time. The API is injected as the
		// only argument to the given callback.
		// --
		// NOTE: The callback MUST RETURN A VALUE so that LESS can get the value of it.
		run: function( callback ) {

			return( callback( api ) );

		}

	};


	// Return the public API. Since this JavaScript expression is return the parent
	// Function, it will have to invoked in a different JavaScript context to actually
	// get access to the API.
	return( api );

}` ;


// I assign the API the global namespace, "api". This could have been done in the
// previous JavaScript code block; but, I kind of liked the idea of breaking it out into
// its own rsponsability.
// --
// NOTE: I am using a self-invoking function here to help ensure that "this" points to
// the global context and not to the context of the evaluation (if its different).
@apiGlobalInjector: `(function() {

	// Inject the API and store it in the global object.
	this.api = (@{api})();

	// The JavaScript HAS TO RETURN something so LESS CSS can assigne the variable value.
	return( "Injected in global" );

})()` ;


// ---------------------------------------------------------- //
// ---------------------------------------------------------- //


h1 {
	content: `api.getContent()` ;
}

h2 {
	content: `api.run(function( api ) {
		return( api.getContent().toUpperCase() );
	})` ;
}

When you execute a JavaScript block, LESS CSS doesn't seem to like semi-colons; unless you are in side of a function. As such, I created a .run() method, which was just a way to create a function that self-injects the API. This way, you could run multiple statements and return a value.

Anyway, when you run the compile the above file, LESS CSS produces the following output:

h1 {
	content: "This is yo' content!";
}
h2 {
	content: "THIS IS YO' CONTENT!";
}

Like I said before, I am not exactly sure how I would use this yet; but, it definitely tickles the imagination. If nothing else, it's inspired me to learn more about LESS CSS as a framework.

Want to use code from this post? Check out the license.

Reader Comments

15,841 Comments

@Academo,

Thanks for the tip. I've not heard of LessHat before, but it looks interesting. It looks like they inline a ton of JavaScript. Very fun. I'll definitely be taking a closer look at this!

1 Comments

Hi Ben,

I went for a simple small example to test whether it really works or not, but it failed. The following is a small snippet I tried:

@test: `function( api ) { return( "test".toUpperCase() ); })`;

On executing my Less stylesheet is giving error.

Is this works for you?

3 Comments

This is great, and fits a use case we have a specific need for... We want to define our standard color palette as a series of LESS variables, but we also want to use that palette to define the palette of our highchart implementations, without having to manually keep the CSS and JS versions in parity with each other... With something like this, I may be able to create some generic JS object map to be included as a module for highcharts purposes, and included as a JS-evaluated color map for LESS purposes... Since this only works for value assignments, we'd still have to make manual inclusions anytime a new piece of the color palette was added, but otherwise, any adjustments being made to the existing color palette entries should automatically be reflected in both the JS and CSS the next time the assets are built and deployed :)

15,841 Comments

@Richard,

It's funny you mention that - what you're describing sounds a little bit like a use-case that popped into my head. I tried to find some sort of file-read function in the Less CSS, but I couldn't. Meaning, I didn't see any way to read files from within the JavaScript context.

Then, I thought, maybe I can define a data structure in the JavaScript to then be used elsewhere in the Less. But I haven't played around with it too much. Right now, I'm just trying to learn all the angles in the feature set. It's exciting stuff. I can't believe I've been using Less CSS for like 2 years and didn't know half of what it did.

That said, for me at least, like 90% of the value comes from simply being able to nest rulesets :D

1 Comments

wow! nice.
"I am not exactly sure how I would use this yet..."
OK. With a single html as a template and in-place tags, how about using dataset variables as the selector elements-and-values for insertion into the existing tags for on the fly device compatibility!?

1 Comments

I was just thinking about this to retrieve a systemjs config map of a jspm install path which contains a version number (github:username/reponame@~1.2.1).
This api setup will do nicely to get me started :)

I'll likely add something like:

@import `api.getModulePath('moduleName')` + '/mixins/_buttons';

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel