Categories


Archives


Recent Posts


Categories


Magento 2 and the Less CSS Preprocessor

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Last week we covered RequireJS, which is the foundational framework for Magento 2’s modern approach to javascript. Today we’re going to do the same for Magento 2’s use of cascading style sheets.

Web development, whether delivering marketing pages or software applications, has always struggled with the browser compatibility problem. On one hand, it’s a fantastic testament to the world wide web that completely different companies can all produce the different browsers engines that have, more or less, a shared set of functionality.

The devil, of course, is in that more or less.

Slightly different interpretations of standards, or lingering older versions of browsers, can wry havoc with developers trying to get consistent browser behavior. This is especially true for people who don’t have the time (or company culture) to perform full cross browser tests of every feature. The advent of the mobile web has made this problem even worse, as mobile device browsers are almost, but not quite entirely, compatible with their desktop counterparts.

Javascript, in some respects, has it easier than CSS. Thanks to some excellent early reverse engineering by Microsoft’s JScript engineers and the development of a reasonable standardization process, the core javascript language performs identically between various browser implementations. The APIs javascript calls into (i.e. the DOM) are another story, but since javascript is a programming language it’s relatively easy for programmers to create shim libraries (like jQuery) to carve out a consistent portion of the DOM to work with.

On the Cascading Style Sheets (CSS) side of things, and back in the quiet days of web development when IE stagnated at version 6 and Firefox/Safari were quietly catching up, “web standards” proponents attempted to carve out a set of CSS rules that worked across browsers, and a set of CSS techniques that relied on non-obvious-but-standard CSS behavior to achieve desired layout behaviors.

Then Microsoft woke up with IE 7/8/9/10/11/edge, Apple essentially created the first real mobile web with Safari for iPhone (while simultaneously pushing closed non-HTML based applications for accessing web data), Firefox started pushing its own standards agenda, Google started turning Chrome into a platform, and a weird “HTML5” movement started. The only WaSPs left in NYC were in The Hamptons. CSS developers were once again faced with death by a thousand browser cuts. While CSS is “code”, and an important valuable skill to master, CSS is not a programming language. The CSS system cannot change its own behavior via a turing complete programming language.

All this led to the rise of CSS pre-processors. A CSS pre-processor lets a front end developer write their styling rules in a custom style language, and then have a stand alone computer program (or “compiler”) turn that custom style language into CSS files that are compatible with multiple browsers.

With pre-processors, the maintainer of the pre-processor (or the pre-processor’s library) becomes the CSS expert, and the designer is freed from needing to remember which CSS rules are safe, which -prefix classes to add for each browser, which crazy CSS standards-bugs are used to implement common UI patterns, etc.

Understanding Less

Magento 2 uses a CSS pre-processor called Less. With Less, designers write their styles in a CSS like language. This is a sample of a Less stylesheet from the Less documentation.

#File: example.less
@base: #f938ab;

.box-shadow(@style, @c) when (iscolor(@c)) {
  -webkit-box-shadow: @style @c;
  box-shadow:         @style @c;
}
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
  .box-shadow(@style, rgba(0, 0, 0, @alpha));
}
.box {
  color: saturate(@base, 5%);
  border-color: lighten(@base, 30%);
  div { .box-shadow(0 0 5px, 30%) }
}

As you can see, this sort of looks like CSS, but seems to have things like functions and variables available. Designers will write Less based code, and then compile it with a program like lessc, which will convert it to CSS.

$ lessc example.less 
.box {
  color: #fe33ac;
  border-color: #fdcdea;
}
.box div {
  -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}

As you can see above, variables have been expanded, functions like lighten have been applied to colors, etc. Less has a reputation as a more “programmer-y” pre-processor, and it’s not surprising that Magento’s engineering team picked it over other popular choices like Sass.

Covering all of Less’s functionality would take a book, and my days of slinging background-color are long over. Instead, we’re going to focus on how Magento has incorporated Less into their front end framework code. Whether you plan on embracing Less, finding ways to work with other CSS tools, or leave the styling to designers, understanding how Magento processes and compiles Less code is an important skill for any working Magento 2 developer to master.

Getting Started with Magento and Less

We’re going to start by creating a module named Pulsestorm_StartingWithLess, and then we’ll use this module to add a CSS file to every Magento Page. We’re going to start assuming you’re running Magento in developer mode. If you’re not sure what that means, you’ll want to review our articles so far.

You can create the base module files using the pestle command line framework’s generate_module command.

$ pestle.phar generate_module Pulsestorm StartingWithLess 0.0.1

and then enable the module in Magento by running the following two commands

$ php bin/magento module:enable Pulsestorm_StartingWithLess
$ php bin/magento setup:upgrade    

If you’re interested in creating a module by hand, or curious what the above pestle command is actually doing, take a look at our Introduction to Magento 2 — No More MVC article.

Once created, add the following default layout handle file to the frontend area.

#File: app/code/Pulsestorm/StartingWithLess/view/frontend/layout/default.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Pulsestorm_StartingWithLess::red-alert.css"/>
    </head>
</page>

This is a page layout handle XML file, which means it understands the special head and body sections. The default handle fires on every page request. The contents of the head tag adds the CSS file red-alert.css to the page. Next, add the following red-alert.css file to your module.

#File: app/code/Pulsestorm/StartingWithLess/view/frontend/web/red-alert.css
body
{
    background-color:#f00;
}    

With the above in place, clear your Magento cache, reload the Magento home page, and you should now see an obnoxious red background, and CSS link tag in your document’s source.

<link  rel="stylesheet" type="text/css"  media="all" href="http://magento.example.com/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css" />

Your <link/> tag’s href attribute may look a little different depending on the theme you’re using. This article assume you’re using the luma theme that ships with Magento 2.

If any of the above concepts confused you, you may want to review the articles in our series so far.

Your First Less File

Next up, we’re going to add our first Less file. The following assumes you’re running in developer mode, and that the option at

System -> Stores -> Configuration -> Advanced -> Developer -> Front-end Developer Workflow 

is set to Server Side Less Compilation.

First, remove the red-alert.css file from your module.

rm app/code/Pulsestorm/StartingWithLess/view/frontend/web/red-alert.css 

Confirm you’ve removed it by reloading the home page and noting the non-garish white background is restored. We’ll also want to remove the symlink Magento created in pub

$ find pub/static -name red-alert.css
pub/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css     

$ rm pub/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css     

Next, create the following Less source file.

/* File: app/code/Pulsestorm/StartingWithLess/view/frontend/web/red-alert.less */
@oh-my-blue: #0000ff;
body
{
    background-color:@oh-my-blue;
}      

Notice we’ve put this file in the same location as our CSS file, and given it the same base name. The only difference is the file extension (.less instead of .css). The contents of the file are also slightly different — we’ve used a Less variable (@oh-my-blue) to add a blue background.

With the above in place, clear your cache and reload the homepage. You should now see a garish blue background. Congratulations! You just created your first Less source file.

Understanding Magento’s Less Implementation

If you view the source of your home page, you may be surprised to find your CSS <link/> tag is still in place, and still pointing to red-alert.css

<link  rel="stylesheet" type="text/css"  media="all" href="http://magento.example.com/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css" />

In one way, this makes sense. After all, our default.xml still points to this file. But how is Magento loading red-alert.css file if we deleted this file?

The missing piece of information is understanding Magento’s Less processing. When you add a css file via <css/> tag

<css src="Pulsestorm_StartingWithLess::red-alert.css"/>

Magento will search for this file on disk. If Magento doesn’t find it, Magento will look again, but instead of looking for a .css file, Magento will look for a .less file with the same name (red-alert.less). If Magento finds the Less source file, Magento will transform the file into CSS, and generate a CSS file. If you load the above CSS in a browser, you’ll see Magento has transformed the Less file into a CSS file.

#File: http://magento.example.com/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css
body {
  background-color: #0000ff;
}

If you look for the file in pub, you’ll see it’s a real file, and not a symlink that points to a module file.

$ find pub/static -name red-alert.css
pub/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css

$ ls -l pub/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css
-rw-rw-rw-  1 _www  staff  38 Feb  4 16:29 pub/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css

Remove this generated file, and reload the CSS URL. You’ll see Magento simply regenerates the file for you.

Understanding Less.js

One problem with the above system is that, once a file’s generated, Magento won’t recheck the Less file for any changes. That is, if you changed the Less file to create a red background

/* File: app/code/Pulsestorm/StartingWithLess/view/frontend/web/red-alert.less */
@oh-my-red: #ff0000;
body
{
    background-color:@oh-my-red;
} 

Magento wouldn’t see these changes. Since there’s a red-alert.css file on disk already, Magento will just serve that file. In order to pick up these changes, you need to remove the generated CSS file.

rm pub/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css

You’ll also need to clear Magento’s view_preprocessed folder. This is a cache folder for Less source files

rm -rf var/view_preprocessed/*

While some programmers may be used to the “edit file, compile, view results” workflow, CSS developers are used to editing a file and seeing the results immediately. One solution for this is something called less.js.

The intended use of less.js is

  1. You include less.js on your HTML page
  2. You serve Less source files directly to the browser
  3. The less.js library transforms the Less source into CSS, and applies the CSS to the page

In other words, less.js simulates what it would be like if Less were supported natively in your browser. You can turn on less.js by navigating to the following system configuration section

System -> Stores -> Configuration -> Advanced -> Developer -> Front-end Developer Workflow 

and setting the Front-end Developer Workflow option to Client side less compilation. With this setting enabled (and if you remove any generated files/links from pub/static), changes to Less source files will be immediately reflected on the next page load.

Behind the scenes, when you have less.js enabled, Magento does three things. First, Magento adds the following two javascript files to your page’s source.

<script src="http://magento.example.com/static/adminhtml/Magento/backend/en_US/less/config.less.js"></script>
<script src="http://magento.example.com/static/adminhtml/Magento/backend/en_US/less/less.min.js"></script>

The less.min.js file is the minified less.js source. The config.less.js file is a javascript configuration file used to configure less.js. Having these here enables client side Less compilation.

The second change is each CSS link will have its rel attribute’s value changed from stylesheet

<link  rel="stylesheet" type="text/css" ... />

to stylesheet/less.

<link  rel="stylesheet/less" type="text/css" ... />

This identifies the source of a <link/> as needing the Less transformations provided by less.js.

Finally, and most importantly, when you’re running Magento in developer mode with client side Less compilation on, Magento will serve Less source files instead of CSS files. i.e., if you try to download the red-alert.css file in your system

http://magento.example.com/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css

instead of getting the CSS file, you’ll get the Less source file

@oh-my-blue: #ff0000;
body
{
    background-color:@oh-my-blue;
} 

While this client-side processing is useful, I’d be wary of relying too heavily on it. The GA release contains what seems to be a bug where Magento can serve CSS source files with a rel="stylesheet/less". Processing CSS as Less can create subtle bugs. Also, as we learned last time, Magento uses RequireJS as its foundational javascript framework. RequireJS means there’s DOM altering javascript loading asynchronously in the background, which may interfere with the DOM altering less.js.

Less and Production Mode

Both server side and client side Less compilation are developer mode only features. Let’s try changing Magento into production mode and observe how the system behavior changes.

Switch your system into production mode

#File: .htaccess
SetEnv MAGE_MODE production

and remove the generated red-alert.css file.

rm pub/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css    

Now, try reloading your red-alert.css URL. You’ll end up seeing a 404 Not Found error.

curl -I 'http://magento.example.com/static/frontend/Magento/luma/en_US/Pulsestorm_StartingWithLess/red-alert.css'
HTTP/1.1 404 Not Found

That’s because, in production mode, Magento will not automatically convert your Less source files into CSS, and will not perform client side Less actions. Similar to PHP code generation, this is for performance and security reasons. Instead, Magento will transform your files once, when you run setup:static-content:deploy.

$ php bin/magento setup:static-content:deploy
Requested languages: en_US

=== frontend -> Magento/blank -> en_US ===
............................................................................................................................................................................

After running the above command, each theme in the system will have a CSS file generated if there’s a CSS file configured in a <head/> section with a corresponding Less source file.

Wrap Up

That’s the foundations of Magento 2’s Less implementation. If you’re interested in learning more, the Cascading Style Sheets section of the dev docs site has some good, if scattered, information on styling Magento 2.

I also know Magento has some built-in support for grunt, and there’s talk of unofficial-ish gulp support. My CSS friends tell me grunt and gulp are build tools that can help work around the limitations of solutions like less.js, but I’m just a frozen XHTML 1.0 transition caveman in these matters.

Regardless of how you’re compiling your Less file, or generating your own CSS outside of Magento’s systems, understanding how Magento’s framework code handles generating its own CSS via Less will be a vital skill when working with Magento 2 systems.

Originally published February 6, 2016
Series Navigation<< Magento 2 and RequireJSMagento 2: CRUD Models for Database Access >>