Apr 25, 2014

Abstract

You won’t have to change the original sources. You won’t even have to download them manually.

I’ll also show how you can inspect the LESS sources in your browser (in stead of the generated CSS) and how to get productive
with automatic compilation.

Quick start

If you already have gradle:

There’s only one file to download: build.gradle :)
Save it in the directory less-bootstrap and then:

cd less-bootstrap
gradle init
gradle lesscDaemon --info

You don’t have Gradle yet:

Clone the github project with git clone https://github.com/houbie/less-bootstrap
or download and unpack the zip distribution
and use the included gradle wrapper:

cd less-bootstrap
gradlew init
gradlew lesscDaemon --info

Now you can:

open less-bootstrap/web/bootstrap-examples/theme/index.html (or an other example)

change less-bootstrap/web/less/custom-variables.less

reload the page to see the changes

Tip

Before reloading the page in your browser, you’ll have to wait a few seconds until the style sheets are compiled.
If you don’t like waiting (who does?), see Speeding up compilation

Exploring the examples

The examples are located in less-bootstrap/web/bootstrap-examples. When you open f.e. less-bootstrap/web/bootstrap-examples/theme/index.html
and inspect the Learn more button in Google Chrome, you will see something like this:

Figure 1. Inspecting an element in Chrome

The inspector shows the LESS sources because the the lessc task is configured to generate source maps:

Enabling source maps in build.gradle

lessc {
...
//generate source maps so that Google Chrome shows the less source in the inspector i.s.o. the raw CSS
options.sourceMap = true//the generated css file contains a reference to the source map, this reference will be relative to//the sourceMapBasepath in this case it will be in the same directory as the css itself//(default location of source maps)
options.sourceMapBasepath = file("$webDir/css").absolutePath
//we could try to specify the sourceMapxxx options so that the browser can load the LESS sources//directly, but this is not trivial since our sources reside in different locations//therefore we just copy all the less source code into the source map file
options.sourceMapLessInline = true
}

Note

At the moment of writing, Chrome is the only browser that supports source maps for CSS files.
This blog shows how to enable CSS source maps
if they are not enabled by default.

Customizing the style sheet

Where is the code?

We would like to change the background color of the Learn more button, but how do we locate its definition?

Again, by inspecting the element in Chrome, we see that the background color is defined in buttons.less.

Figure 2. The button’s background colour

When we double-click buttons.less, the source is opened in the inspector:

Figure 3. Buttons.less

We see that the variable that we need to change is btn-primary-bg. Unfortunately, we cannot navigate further anymore
in the inspector, so we have to search manually.

The bootstrap source files can be found in less-bootstrap/build/bootstrap/web-app/less. A text search leads us to mixins.less
for the button-variant definition and to variables.less for the background color variable:

@btn-primary-bg: @brand-primary;

Albeit not perfect, it is a lot easier to find the code in this way than if we would only see the raw CSS in the inspector.

At this point we have to decide whether we want to change only the color of the primary buttons, or if we want to change
the primary brand color.

Modifying styles

There are 3 ways to modify the style sheets:

Overwrite variables in build.gradle

Modify a copy of the original source

Create our own customization LESS file

Overwriting variables in build.gradle

The lesscss compiler allows us to declare new or overwrite existing variables via the commandline or in build.gradle:

lessc {
...
options.modifyVars = ['brand-primary': 'purple']
}

Overwriting variables this way can be useful if you want f.e. to use a different color scheme for development builds than for release builds, but
it is not suitable for more involved customizations.

Modify a copy of the original source

The lessc task is configured to lookup LESS files first in less-bootstrap/web/less:

This means that if we would copy variables.less to less-bootstrap/web/less and modify it, it will take precedence over
the original file.

However, when we would like to upgrade to a newer bootstrap version, we would need to apply all our changes again in the
new file, which is far from ideal.

Note

Changing brand-primary in our copy of variables.less won’t have any effect, it will always be overridden by
the value in build.gradle!

Create our own customization LESS file

The main LESS file, bootstrap.less, consists of only import statements. If we would append a few import statements to
include our own customization files, we wouldn’t have to change the original LESS code. This is exactly what the init
task does when it unpacks the bootstrap sources:

Use the fast node.js compiler in the lesscDaemon task to save time when developing the style sheets. Keep the default
(java rhino based) compiler in the lessc task to avoid installing node.js on your CI server and to have deterministic
builds that always use the same compiler version.

Setup

I recently released a new version of lesscss that supports 3 execution engines:

rhino 1.7R4: it runs a pre-compiled version of less.js in the highest optimization level (9)

nashorn: the JDK8 built-in version, which does not (yet) support pre-compilation nor optimization

node.js lessc: prepare a commandline string and execute it with Runtime.exec

I compiled the Twitter Bootstrap style sheets using different engines.
I used my mac book (2.66GHz core i7 with 8GB memory and JVM args -Xms512m -Xmx1500m -XX:MaxPermSize=320m).
For each run I instantiated one engine for 15 consecutive compilations.

As you can see, both rhino and nashorn perform better when the JVM warmed up, but the differences in performance are huge:

Time needed to compile Twitter Bootstrap (seconds)
The first row includes JavaScript compilation in case of Nashorn

#

Rhino
(optimized)

Nashorn

node.js

1

3.9

18.4

0.9

2

1.9

7.9

0.6

3

1.7

6.1

0.6

4

1.6

5.8

0.6

5

1.4

5.0

0.6

6

1.4

4.9

0.6

7

1.5

4.5

0.6

8

1.4

5.3

0.6

9

1.3

5.8

0.6

10

1.3

4.4

0.6

11

1.0

4.0

0.6

12

1.0

3.8

0.6

13

1.0

3.5

0.6

14

0.9

3.1

0.6

15

0.9

2.8

0.6

Conclusions

Although this is not a real benchmark,
the figures differed less then 20% between runs and the trends are clear (at least for this use case):