Large Scale Less Part 3 - Mapping Development To Production

by Matt Perdeck 13. April 2013 14:17

While your developers work on LESS files in the development environment, the generated CSS files are used in the production environment. These environments must meet different requirements, so can be very different. It is the build process that in the end is responsible for translating from development to production. This part 3 discusses options for this mapping.

Contents

Differences between development and production

You probably want both a productive development environment and a high performance web site. As a result, the way you organize your styles in LESS files in your development environment is likely to be different from how they are organized in the generated CSS files in your production environment.

Your development environment should make it easy for your (human) developers to quickly and accurately build and maintain your site's styles:

  • Factoring out common styles and definitions, to improve consistency and reduce duplication.
  • Organizing styles into logical sections to make it easier to read the existing code base.
  • Ensuring developers don't thread on each others' toes, reducing merge conflicts. For example separate files for page specific styles.

Your production environment should ensure the fastest possible page loads for your most important target visitors, by:

  • Minimizing total number of bytes going over the Internet during a page load to load the required styles.
  • Minimizing the number of files being loaded during a page load.

A few options on how to achieve this:

  • One file. Simply combine all styles into a single (minified) CSS file. Every page would have a single link tag for this file.
  • Even file split. If the resulting CSS file is big, you could split your styles over 2 or more files. Each page would have link tags for each file. This allows the browser to load multiple files in parallel, depending on how many JavaScript and other static files need to be loaded.
  • File per section. If your site has separate sections with their own styling, such as an admin section, it makes sense to give each section its own CSS files. Some styles may appear in both files, which is ok if visitors tend to stay in one section.
  • File per page plus shared file. To minimize bandwidth, you could give each page its own CSS file. All common styles would go into their own CSS file, which would be loaded with an additional link tag in your page or a CSS @import rule in the page specific CSS (discussion). This way, the first time a page is loaded, the browser has to load two CSS files. But during subsequent page loads, the common styles would come from browser cache, leaving only the (hopefully small) page specific CSS to be loaded over the Internet.
  • File per page cluster plus shared file. Sometimes pages form a logical progression. For example a wheather page with a list of cities with links to each city's page. In that case it makes sense to combine the styles for both page into one CSS file, so they will be loaded from cache when the second page is opened.
  • Separate file for unpopular styles. Some of your pages will be far less popular than others - how many visits does your terms and conditions page recieve? You could have a single CSS file for all styles for the popular pages, while leaving out any styles specific to unpopular pages. This way popular pages do not need to load styles that are unlikely to be needed. Unpopular pages would load both the "popular" styles and a separate file with "unpopular" styles.

LESS = development, CSS = production

We saw how in LESS we can use the @import-once directive to compile multiple LESS files into a single CSS file. That allows us to map the LESS files in the development environment to the CSS files in production.

On many projects, the CSS files are organized differently in development than in production. For example, they are kept separate in development but are combined in production. This introduces complexity, because pages in development may need different link tags than in production. Also, any bugs caused by combining the CSS files are not caught in development.

A better approach is to organize your CSS files the same in development and in production. This reduces complexity and doesn't really degrade the development experience, because the source maps in the CSS files point to the individual LESS files where each style originated, so the fact that the CSS files are combined doesn't matter.

The only difference between development and production would be that in development source maps are switched on and minification switched off, while in production source maps are switched off and minification is switched on.

Mapping LESS files to CSS files

When it comes to compiling our LESS files to CSS files, our build project needs these pieces of information:

  • Which CSS files need to be build.
  • For each CSS file, which LESS files contain the styles to put into that file.

These are both sets of files. As such, they can be specified in these ways:

  • As an explicit list - for example a list of @import-once directives in a LESS file.
  • Implicitly, as some kind of wildcard file path - for example all LESS files in a given directory.

That gives us this table of options:

CSS files
LESS files Explicit Implicit
Explicit 1. Additional LESS file per CSS file, which imports the constituent LESS files 2. Additional LESS file per CSS file, each matching a wildcard
Implicit 3. Directory per CSS file with LESS files 4. Directory per CSS file with LESS files, each directory matching a wildcard

Option 1. Additional LESS file per CSS file, which imports the constituent LESS files

In this option:

  • You create additional LESS files for each CSS file you want to generate.
  • In those LESS files you place @import-once directives for each LESS file to be compiled into the given CSS file.
  • In your build script, you have a list of all these additional files.
  • During a build, you compile each additional file into a CSS file.

Your additional files could look like this:

Content\CombinedMain.less

@import "home.less";
@import "categories.less";
@import "product.less";

Content\CombinedAdmin.less

@import "login.less";
@import "settings.less";
@import "reports.less";

Your build script would look like this expressed in DOS batch commands:.

rem --- Set lessc parameters, depending on build configuration
if '$(Configuration)'=='Debug' (
   set params=--line-numbers=mediaquery --relative-urls
)
if '$(Configuration)'=='Release' (
   set params=--yui-compress --relative-urls
)

rem --- Compile each additional file
for %%a in (
    CombinedMain
    CombinedAdmin
) do lessc "$(ProjectDir)Content\%%a.less" "$(ProjectDir)Content\%%a.css" %params%

This will compile CombinedMain.less into CombinedMain.css, and CombinedAdmin.less into CombinedAdmin.css. Visual Studio will replace the macro $(Configuration) with the name of the current build configuration, and replace $(ProjectDir) with the root directory of your project (all macros).

You can get Visual Studio to execute these commands after each successful build by making them into a post build event:

  1. In Visual Studio, right click on your project and choose Properties.
  2. Click the Build Events tab.
  3. Copy and paste the DOS batch commands into the box Post-build event command line.
  4. Press cntrl-S to save the post build event.

Option 1 has a few compelling advantages:

  • Flexible - you can combine any files you want into any CSS file.
  • Clear - there is no doubt which LESS files make up which CSS file.
  • Control - allows you to control exactly which LESS files are compiled.

But also disadvantages:

  • Requires additional LESS files to represent the mapping between development and production environments.
  • Developers have to remember to add any new LESS files to the correct additional LESS file.
  • When introducing a new additional file, you need to update the build script.

Option 2. Additional LESS file per CSS file, each matching a wildcard

Suppose you want to explicitly list each LESS file that goes into each CSS file as in option 1, but you do not want to have to update the build script each time you add or remove an additional LESS file.

In that case, you can modify the build script of option 1, so it uses a wildcard to find all additional files. For example, Combined*.less. That would result in this updated build script:

rem --- Set lessc parameters, depending on build configuration
if '$(Configuration)'=='Debug' (
   set params=--line-numbers=mediaquery --relative-urls
)
if '$(Configuration)'=='Release' (
   set params=--yui-compress --relative-urls
)

rem --- Compile each additional file
for /f %%a in ('dir /b $(ProjectDir)Content\Combined*.less') do lessc "$(ProjectDir)Content\%%~na.less" "$(ProjectDir)Content\%%~na.css" %params%

Here the /f option has to be added to the for command, so it gets the dir command to produce a list of file names. The for command lets you apply modifiers to the loop variable (in this case %%a). Here the modifier ~n has been used to get the file name without the extension.

Advantages:

  • Flexible - you can combine any files you want into any CSS file.
  • Clear - there is no doubt which LESS files make up which CSS file.
  • No need to add new additional fiels to the build script.

Disadvantages:

  • Requires additional LESS files to represent the mapping between development and production environments.
  • Developers have to remember to add any new LESS files to the correct additional LESS file.
  • Potential for confusion when a developer adds a "normal" LESS file that happens to match the wildcard specified in the build script.

Option 3. Directory per CSS file with LESS files

Instead of explicitly listing all LESS files that are compiled into each CSS file, you could create an implicit mapping between all LESS files in a directory and a CSS file.

For example,

  • All LESS files in Contents\MainSection are compiled into CSS file Contents\CombinedMainSection.css.
  • All LESS files in Contents\AdminSection are compiled into CSS file Contents\CombinedAdminSection.css.

With this option:

  • The build script contains a list of directories.
  • For each directory, the script creates a temporary LESS file with @import-once directives for each LESS file in the directory.
  • It then compiles the temporary LESS files into CSS files.
  • The names of the CSS files are derived directly from the names of the directories. For example directory AdminSection -> CSS file CombinedAdminSection.css.

This results in this build script:

rem --- Set lessc parameters, depending on build configuration
if '$(Configuration)'=='Debug' (
   set params=--line-numbers=mediaquery --relative-urls
)
if '$(Configuration)'=='Release' (
   set params=--yui-compress --relative-urls
)

set styleDir=$(ProjectDir)Content

rem --- Compile all LESS files in each directory into a matching CSS file
for %%d in (
    MainSection
    AdminSection
) do (
    if exist %styleDir%\__.less (del %styleDir%\__.less)
    for /f %%a in ('dir /b %styleDir%\%%d\*.less') do echo @import-once "%%d\%%a"; >> %styleDir%\__.less
    lessc %styleDir%\__.less %styleDir%\Combined%%d.css %params%
    del %styleDir%\__.less
)

Note that the temporary LESS file must live in the same directory where the CSS file will live, otherwise any relative URLs in the LESS files will be wrongly rewritten.

Advantages:

  • No need to update a additional LESS file when adding a new LESS file.
  • No additional LESS files cluttering your project.
  • Clear which LESS files will be compiled into which CSS files.
  • Precise control over which directories will be processed.

Disadvantages:

  • Stronger coupling between development environment and production environment.
  • Have to update the build script when introducing a new CSS file (if that CSS file is generated by the LESS compiler out of the LESS files in a directory).

Option 4. Directory per CSS file with LESS files, each directory matching a wildcard

Suppose you like option 3, but do not want to have to update the build script each time you add a CSS file that will be generated out of the LESS files in a directory.

Instead of listing the directories explicitly in the build script, you could use a wildcard that matches the directories, such as Content\*Section. This would exclude directories such as Content\Shared.

The resulting script is essentially the same as that of option 3, except that instead of a list of directories it finds the required directories via a wildcard:

rem --- Set lessc parameters, depending on build configuration
if '$(Configuration)'=='Debug' (
   set params=--line-numbers=mediaquery --relative-urls
)
if '$(Configuration)'=='Release' (
   set params=--yui-compress --relative-urls
)

set styleDir=$(ProjectDir)Content

rem --- Compile all LESS files in each directory into a matching CSS file
for /f %%d in ('dir /b /ad %styleDir%\*Section') do (
    if exist %styleDir%\__.less (del %styleDir%\__.less)
    for /f %%a in ('dir /b %styleDir%\%%d\*.less') do echo @import-once "%%d\%%a"; >> %styleDir%\__.less
    lessc %styleDir%\__.less %styleDir%\Combined%%d.css %params%
    del %styleDir%\__.less
)

Advantages:

  • No need to update an additional LESS file when adding a new LESS file.
  • No additional LESS files cluttering your project.
  • Clear which LESS files will be compiled into which CSS files.
  • Reasonable control over which directories will be processed.

Disadvantages:

  • Stronger coupling between development environment and production environment.
  • More potential for developer confusion as to which directories contain LESS files that will be compiled into CSS files.
  • Link or @import

    In some production environments it makes sense to load multiple CSS files during a page load (from browser cache or over the Internet).

    There are two ways to load a CSS file into a page:

    • Using a link tag. These can only be placed in HTML files, not in CSS files.
    • Using an @import rule. These can be places in both HTML files (in a stylesheet embedded in the HTML) and in a CSS file.

    When loading a CSS file directly into HTML, it is better to use the link tag rather than @import, because it is marginally simpler and because of browser issues.

    This leaves situations where a page loads a CSS file which is dependent on some other CSS file. If categoriespage.css requires shared.css, we have these alternatives:

    1. Two link tags:

      categoriespage.htm:

      <link href="shared.css" rel="stylesheet" type="text/css" />
      <link href="categoriespage.css" rel="stylesheet" type="text/css" />
    2. A link tag for categoriespage.css, and an @import rule in categoriespage.css to import shared.css:

      categoriespage.htm:

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

      categoriespage.css:

      @import"shared.css";
      
      /* Category page specific tags */

    Comparing alternative 2 (categoriespage.css imports shared.css) with alternative 1 (two link tags in the HTML):

    • Better maintainability. The @import rule makes the dependency on shared.css immediately clear. Also, whenever categoriespage.css is loaded, you are guaranteed that shared.css will be loaded as well.
    • Slighly slower, because the browser has to at least partly load categoriespage.css before it can start loading shared.css.
  • Add comment

      Country flag

    biuquote
    • Comment
    • Preview
    Loading

    Books

    Book: ASP.NET Site Performance Secrets

    ASP.NET Site Performance Secrets

    By Matt Perdeck

    Details and Purchase

    About Matt Perdeck

    Matt Perdeck PresentingMatt has written extensively on .Net and client side software development.

    more >>