1.How does it work ?
With Magento 1, you could just include your CSS files through XML, for example like this, in page.xml file.
<default translate="label" module="page"> <block type="page/html" …… template="page/3columns.phtml"> <block type="page/html_head" name="head" as="head"> <action method="addCss"><stylesheet>css/styles.css</stylesheet></action> </block> </block> </default>
And you could use ( or not ) whatever you want to generate that file. Madison Island theme, that first came out with Magento 1.9, is using Sass preprocessor for compiling CSS.
In Magento 2 story, you still add your CSS files through XML, It looks a bit different now. But that is a whole different story.
<page> <head> <css src="css/styles-m.css" /> <css src="css/styles-l.css" media="screen and (min-width: 768px)"/> <css src="css/print.css" media="print" /> </head> </page>
2.Request Flow ?
As you can see from the diagram, the system first searches for the file in pub/static directory. If file is present there, it will just return that file.
If file is not in that folder, system will search for it in your active theme. If it founds it there, it will create a file in pub/static/… with a symlink to the actual file located in your theme.
If system does not find the file in your theme either, it will search for the same file, but with .less extension, and then the whole LESS preprocessing is put in motion, which we’ll cover later.
First step of this flow practically means that we could work in Magento 2 the same way we did in Magento 1: add css file via XML, the system will search for it either in pub/static or our theme, and if it finds it it will just use that file. No LESS preprocessing is triggered. You could hypothetically use whatever you want to generate that CSS file…
But, what about…?
But, in that case, you are coding against Magento 2 best practices. Extensions and themes will not work together “out of the box”.
If you want to follow the rules, and keep your theme modular and compatible with extensions on the market, you will use built-in LESS preprocessor.
LESS is preprocessor, which extends the capabilities of CSS, adding features like variables, functions, mixins or nesting, turning it into something similar to a primitive programming language. Developers write their code in LESS syntax, which is in the end compiled to CSS.
For Magento, it was important to make a system where extensions or themes would work out of the box. Where a merchant can install extension or theme on his own, and it would just work. So they had to figure out a way to let extensions communicate with the themes, making style inheritance or overrides simple.
But why did they choose LESS, when you know that most of frontend developers use more powerful Sass preprocessor? There is no big mystery here: at the time of decision making, LESS was the only preprocessor with stable PHP compiler.
4.Frontend development workflow
In Magento 2, there are two modes of frontend development workflow available: Client side and server side. This is configurable in Magento administration under: Stores > Configuration > Advanced > Developer > Frontend development workflow
Client side – compilation happens inside the browser via native less.js compiler.
Server-side is default workflow and the only option available in production mode. It uses Magento built-in PHP compiler.
If you are serious into Magento development, you won’t be using client-side preprocessing. But you probably aren’t going to use server side PHP compiler either. We’ll talk about the reasons little later.
So how does LESS compilation actually work? We already discussed CSS file request flow.
- System searches for requested .css file. If found, preprocessor stops execution.
- System searches for the same file, with .less extension, following theme fallback mechanism. If file is not found, preprocessor stops execution
- Reads the contents of .less files and resolves @magento_import and default @import directives
- Resolves all paths in .less files to relative paths in the system using theme fallback mechanism. All resolved paths are stored to var/view_preprocessed/less directory
- All source files are passed to PHP compiler and resulting .css files are published to pub/static/frontend/<Vendor>/<theme>/<locale>
Default LESS @import imports contents of specified file into current file. It allows developers to break down their styles into smaller logical files:
@import "modules/global.less"; @import "modules/header.less"; @import "modules/search.less"; @import "modules/forms.less"; @import "modules/buttons.less"
Magento had a problem here, since standard LESS @import directive isn’t aware of fallback mechanism and isn’t aware which modules are installed and how to add their own .less files.
They solved this by adding //@magento_import directive, which allows including multiple files with the same name. This allows extension developers to “plug in” their styles via module.less file.
To avoid conflicts with default LESS, @magento_import must be written as a comment – with two slashes.
This is what I call Magento pre-preprocessor, or how Sandro poetically called it, Pre2processor.
So, when compiler hits @mageno_import, in our example, it will look for all source/_module.less files in our theme, following fallback rules. And it will replace @magento_import with default LESS @import directives:
@import '../Magento_AdvancedCheckout/css/source/_module.less'; @import '../Magento_Bundle/css/source/_module.less'; @import '../Magento_Catalog/css/source/_module.less'; @import '../Magento_CatalogEvent/css/source/_module.less'; @import '../Magento_CatalogSearch/css/source/_module.less'; ….
This files, with resolved paths are published to var/view_preprocessed directory, and then passed to standard LESS compiler.
If our exentions had only .css file, you need to creat folder source/module, move .css file(s) to this folder and import these .css file in _module.less . Please make sure you use @
import (inline) to css output without being processed. Eg:
@import (inline) 'module/extentionsstyle.css'
6. Cleaning & Reloading
As you could see, when request for .css file is made, system will search it in pub/static folder. If it is there, it will just serve that file. That means, if you make any changes to you .less or .css file in your theme, the system will not be aware of it. You will have to delete the file in pub/static/ folder so that system picks up the changes in .css files in your theme.
Also, Pre2processor has generated files with resolved paths in var/view_preprocessed folder, so if you make any changes in .less files, you will have to clean that folder also.
Hmm, That sound like we have big issue? Compiling takes forever, so if you make changes in your css and want to see the result in the browser, it will take a lot of patience. But, we everything still have solutions. Move to next part and we can see what solution we had.
7.Automated Preprocessing with Grunt
Yes, we have a old friend – Grunt.
When it became obvious that this workflow will be too slow and painful for frontend devs, Magento decided to add automation (and speed) to this process, by adding the support for Grunt task runner.
Magento 2 comes with built in grunt tasks, but there are some steps you need to take before you can use it.
The problem with Grunt is that we are using node.js for compilation, and it is not aware of theme fallback mechanism and doesn’t know what @magento_import is…. Luckily, there is a Magento CL directive:
- Resolves all fallback paths
- Creates symlinks to source (.less) files
- Expand all “@magento_import” to import single files
- Publish all files in a tree to pub/static folder
So, now you have the whole tree in pub/static folder, and you can watch for changes and compile those files using Grunt tasks.
There are several tasks available:
- grunt clean:<theme>
- Removes static files from pub and var folders
- grunt exec:<theme>
- Creates whole tree with symlinks to the source .less files in pub/static/frontend/vendor/theme/web
- grunt less:<theme>
- Compiles .css files using symlinks published in pub folder
- grunt watch
- Watches for changes in source .less files, compiles .css and injects new styles in browser without page refresh
Please note that most of these tasks are just a wrappers for bin/magento directives.
There are few quirks with this approach. As already stated, node.js isn’t aware of magento flavored LESS, so in case you make changes to root LESS files ( styles-l.less ), or add, remove or rename imported .less files, you will have to rebuild the whole tree again. This means -> stop the watcher -> grunt exec -> grunt watch.
Compiling with grunt takes aprox. 8-10 seconds, depending on the size of your .less files, which is a great progress compared to PHP compiler.