Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with:

Executing JavaScript In The LESS CSS Precompiler

By Ben Nadel on

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.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader 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!

Reply to this Comment

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?

Reply to this Comment

@Maninder,

It looks like you have an extra ")" at the end. Try removing the last parens right after the last "}".

Reply to this Comment

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 :)

Reply to this Comment

@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

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.