Oversized CSS files? Split your monolithic styles into easily-managed chunks – and then put them back together using SASS and Make.

Compile Your Style: Structuring and automating CSS
Kommentare

Editor’s note: This is the second part of Ragnar Kurm’s guide to Many Layouts Side-By-Side (MLSBS), a methodology for dealing with massive CSS layouts. See the November 2013 issue for part one, which covers smarter CSS with a preprocessor and performance-aware selectors.


©iStockphoto.com/PeterAustin

Structuring

If CSS files grow too large, they can become convoluted, hard to manage and difficult to get an overview of. However, many smaller files have disadvantages of their own.

Therefore, there are two processes to reshaping CSS:

  1. Splitting it up into managable chunks
  2. Joining it back together for distribution

Splitting it up

Why would we need to split up a CSS file? In short, it makes it structured and manageable, and it becomes easy to gain an overview of your CSS. Every time you add some HTML construction (logo, title, main menu, comments list, breadcrumbs, etc) you should create a corresponding SASS file specific to that element.

Suppose we chose to get rid of a secondary menu in a site we were building. In this case, we would also remove the relevant style – by deleting a file. If you change some of these elements, you know exactly where to find corresponding style.

Usually, if a single style file gets big, it is a sign of convolution. It’s not possible to have too many files — in fact, more is generally better. Don’t worry about the browser having load each one separately: we will deal with that issue a bit later.

For a larger project I am working on, my style-pieces are mostly less than 1kb, though few of them are up to 5kb, and each of these files contain CSS for four different layouts (one general and three width-specific layouts).

As I use Drupal extensively, I mostly split CSS in a fairly Drupal-specific way. But you can set it up according to your framework or environment and it would not be dramatically different.

There are many professionals who have shared their way of structuring style. The best source is probably SMACSS. There are other ways of structuring CSS, but I have yet to read into them in detail. For now, I am concentrating on MLSBS.

My main categories are:

  • Definitions file (collection of reusable things, described below)
  • Elements (small structural elements)
  • Structural (major structure of the page)
  • Fields (both when being edited and displayed. For example, a form may have a field ‘Product title’, and upon saving a whole product is displayed including the field ‘Product title’)
  • Views (listings, tables, grids)
  • Nodes (pages of certain types, an article, a product, an event, a person)
  • Pages (typeless pages like frontpage, 404 page, contact page)

Definitions file

This is file where I collect reusable info and it is included by all .sass files. More specifically it contains:

  • Paths providing a base for external URLs, for example $path_img: /style/images/.
  • Automatic image style dimensions to match Drupal Configuration > Image Styles. For example: $image_style_thumbnail_width: 100px
  • Image dimensions, which are are automatically extracted from actual images (more on this later).
  • General dimensions such as margins, certain element widths, different layout sizes, etc.
  • Colors to maintain a consistent color scheme.
  • CSS snippets stored in reusable SASS mixins, for example: rounded().

Overview of other pieces

The kind of structure I use is shown in diagrammatic form below. Note that in many categories, the list is given from more general to more specific.

Figure 1: Example structure of a big SASS project.

Joining the pieces

It is impractical to feed all these style pieces individually. Too many requests will slow down page rendering. Therefore it is more convenient to join them either before or after compiling. It is also worth considering if and how to group them. There are many methods to do this, but it needs at least a bit of automation — including pieces together in .sass file or using shell script for concatenation, for example. More convenient way is to use Make / Makefile.

It is important that whichever way you find convenient for you the compilation and assembly should be in subsecond range, for example 300ms, because this is a frequently-repeated procedure.

1. Include all pieces together (perhaps in a file named combined.sass)

@import "includes/defs.sass"
@import "elements/admin-menu.sass"
@import "elements/ads.sass"
@import "elements/feedback.sass"
@import "elements/help.sass"
@import "elements/image.sass"
@import "elements/system.messages.sass"
...

2. Compile the file

sass --watch combined.scss:combined.css

By adding --watch, If you change any of included files, the target will be automatically recompiled.

Combining style files on the fly

Some frameworks offer the option to use specific CSS pieces only if they are needed – combined, minimized, compressed and delivered to the client. This might sound like a good idea, but think how many possibilities there are. It is a mathematical permutation of a number of style files – and this number could be big. If you have hundreds or thousands of these combined-minimized-compressed files cached on your server – does it really pay off?

Though I have not carried out any evaluation or benchmarks, I have not chosen to add more complexity to my system. I usually deliver only two files to the browser – one generic and one layout-specific (both minimized and compressed) – so that they can be cached easily. It will be explained further in the end of “Media Queries” chapter.

Automated imagework: ImageMagick

ImageMagick is a free software suite to create, edit, compose, or convert bitmap images. It’s a Swiss army knife for command line image manipulation, and can be used to automate mechanical operations that you might otherwise carry out in, say, GIMP.

There are two major ways in which we will use ImageMagick: to modify existing graphics (or generate it) and secondly to extract file information and feed it back to style files.

Image modification

Suppose your designer delivers you 2000x1500px logo which will be used on different pages, in different sizes, and — to complicate things even further — different layouts (screen sizes) require different versions. So you are going to end up number of different sizes multiplied by a number of layouts. Imagine if the logo needs to be changed — the image will need to be converted again many times. This time-consuming process leads to resistance to change of these graphical elements.

Fortunately, ImageMagick has a solution for these issues, allowing us to work with ease on graphics. One of the utilities that ImageMagick provides is:

convert – convert between image formats as well as resize an image, blur, crop, despeckle, dither, draw on, flip, join, re-sample, and much more.

$ convert logo.png -geometry 200x150 logo.jpg

This takes the logo in any format and scales it down to 200×150, keeping the aspect ratio. It could not be any simpler.

Another great utility that comes with ImageMagick is:

identify – describe the format and characteristics of one or more image files.

$ identify logo.jpg
logo.jpg JPEG 114x150 114x150+0+0 8-bit PseudoClass 256c 832B
0.000u 0:00.000

So we can see that the logo was converted to 114×150 px. It is very handy to look up image dimensions with identify rather than execute a full graphical editor.

To automate things further — from time to time there is a need to know exact size of an image inside preprocessor style file, i.e. in a .sass file. For example when you need to fit elements well together. With identify and a small shell script, we can do that painlessly.

#!/bin/sh
img="$1"
label=`echo "$img" | tr 'a-z-' 'A-Z_' | sed 's/.[A-Z]*$//'`
identify -format "$WIDTH_$label: %wpx" "$img"
identify -format "$HEIGHT_$label: %hpx" "$img"

This is a simplified version of the shell script needed to extract image dimensions and output them as variables in .sass format.

$ dimensions.sh logo.jpg
$WIDTH_LOGO: 114px
$HEIGHT_LOGO: 150px

Ideally you would like to get dimensions for all your static theme images and import them into the style files. It frees you from editing style while editing images, because you know that all things will match together automatically without breaking anything.

For simplicity, below is a workflow for one image. Similarly one can construct images.sass which contains info for all images.

Figure 2: sass workflow for images

Usage

The following example is given for one file, but can generalized for all image files.

$ dimensions.sh logo.jpg > logo.sass

In the style file:

@import "logo.sass"
#logo
  width: $WIDTH_LOGO
  height: $HEIGHT_LOGO

All of this image conversion and dimension thing starts to make more sense when it is automatic.

Image creation

ImageMagick tools are not limited to conversion or identification. It has virtually unlimited options to deal with graphics. For example, you can automatically watermark images, generate pretty graphical texts with your own font, generate tile images for background, or even generate dynamically pretty menu buttons. (In the latter case, you need to to your own caching if the button text remains the same.)

And these operations are often one-liners. The following is a demonstration what can be achieved from ImageMagick command line tools. Many of these are one-liners, although some take couple of lines.

Figures 3-6: ImageMagick examples. See listing Z for source.

# Top left
$ convert 
	bg.jpg 
	-crop 600x200+0+0 
	-fill '#0008' 
	-draw 'rectangle 10,130,400,190' 
	-fill white 
	-pointsize 40 
	-annotate +30+175 'The Image'
# Top right
$ convert 
	-size 600x200 xc:white 
	-font MeriendaOne-Regular.ttf 
	-pointsize 80 
	-fill black 
	-annotate +10+135 'Dark Shadows' 
	-channel RGBA 
	-blur 0x5 
	-ordered-dither h8x8a,3
    
# Bottom right
$ convert 
	bg.jpg 
	-crop '550x100+0+0' 
	tmp.png
$ convert 
	-size 600x300 xc:lightblue 
	-background lightblue 
	-fill white 
	-stroke grey60 
	-draw "rectangle 0,0 560,110" tmp.png -geometry +5+5 -composite -rotate -5 
	-draw "rectangle 0,0 560,110" tmp.png -geometry +5+5 -composite -rotate -5 
	-draw "rectangle 0,0 560,110" tmp.png -geometry +5+5 -composite -rotate +5 
	-trim 
	+repage 
	-flatten 
	-gravity center 
	-background lightblue 
	-extent 600x300
$ rm -f tmp.png
# Bottom left
$ convert 
	-size 600x200 xc:white 
	-font MeriendaOne-Regular.ttf 
	-pointsize 80 
	-fill green 
	-stroke green -strokewidth 25 -annotate +35+130 'Heavy Stroke' 
	-stroke white -strokewidth 20 -annotate +35+130 'Heavy Stroke' 
	-stroke green -strokewidth 15 -annotate +35+130 'Heavy Stroke' 
	-stroke white -strokewidth 10 -annotate +35+130 'Heavy Stroke' 
	-stroke green -strokewidth  5 -annotate +35+130 'Heavy Stroke' 
	-stroke none                  -annotate +35+130 'Heavy Stroke'

Futher info about image generation can be found on the official ImageMagick site.

Media Queries

For the sake of completion, I’ll add a few words about media queries. In order to make your page responsive and able to show multiple layouts (one at a time, of course) depending on the screen width, you will need to deploy media queries, which instruct browsers to use certain styles only when conditions are met.

As this is a core part of responsive design, there is plenty of information already available. Here’s a few links to start you off:

What I would like to add from my experience with responsive design is that I used two files. One is a general file, dimension-independent and containing things like colors etc. In addition to this file, I used just one style per layout. It so happens that layout-specific files tend to be almost identical except for dimension values.

Usually, if I have three layouts (narrow, normal, wide) then I would have four CSS files in total (one is a ‘general’ layout). This is actually a bit simplified, because frameworks (Drupal, etc) tend to add their own files too.

I constructed media queries in a way that the general .css file is always loaded, but only one layout-specific file is loaded. I did not find much use stacking them and burdening browser rendering, because they would almost completely overwrite each other.

@import url("general.css");

@media (max-width: 800px) {
    @import url("narrow.css");
}

@media (min-width: 1000px) {
    @import url("normal.css");
}

@media (min-width: 1200px) {
    @import url("wide.css");
}

Make & Makefile

Before we get to the final step, it’s worth introducing Make, a package and command line utility. It is mainly used to compile program code, but it is not specific to that. So it can be used very creatively.

Essentially one executes make, which then reads Makefile in the current directory and executes relevant rules. Makefile contains rules which specify targets (files) and sources (also files) that it depends on. If a dependency file has been updated then the target is remade. Though it allows to specify simple dependencies like target and its direct dependencies, it actually works based on arbitrary size of dependency trees. It will go through only affected branch and will redo necessary operations called recipes. Recipes are always indented by one tab.

Here’s a very simple example Makefile:

typesetting.css: typesetting.sass
    sass typesetting.sass typesetting.css

By executing make from the command line, it compiles typesetting.sass into typesetting.css — only if typesetting.sass is newer than typesetting.css.

Automatic variables

Rules can be generalized by using automatic variables and this gives great power to Make.

  • $@ The file name of the target of the rule. $@ is the name of whichever target caused the rule’s recipe to be run.
  • $< The name of the first prerequisite.
  • $^ The names of all the prerequisites, with spaces between them.
  • $* The stem with which an implicit rule matches. In a static pattern rule, the stem is part of the file name that matched the % in the target pattern.

Makefile examples

Here’s another simple example with SASS:

typesetting.css: typesetting.sass
   sass $< $@

This produces the same result as our first Makefile, but is more general. $< will be replaced by typesetting.sass and $@ will be replaced by typesetting.css.

Another example:

IMAGES = $(addsuffix .jpg, 
    logo 
    title 
)
all: $(IMAGES)
%.jpg: %.png
    @echo "Converting $*"
    @convert $< $@
$(IMAGES): Makefile

This is a more sophisticated real-life example.

  1. It defines variable IMAGES as list of images (logo and title) and adds the extension .jpg to them.
  2. If make is executed the first rule all will be run. This is “virtual” target. File with the same name (all) will never be built, but it serves purpose of having dependencies, IMAGES which are created or recreated if necessary.
  3. There is a rule to convert arbitrary .png files to .jpg. It will echo Converting *filename* to terminal, but not the actual command itself, and then silently converts .png to .jpg assuming ImageMagick is installed. Only these images will be (re)created which are listed as dependencies or sources for target all and have source (the PNG file) newer than the target (the JPEG), if the target already exists.
  4. Any image in IMAGES depends on the Makefile. So when the developer adjusts something in Makefile then all specified images will be recreated upon executing make from the command line.

In context of working with SASS, CSS and images the Make does all the housekeeping. It converts all the images, compiles .sass files and assembles them as .css. With ease you can add your own logic to it. In a sense it creates second meta-layer to your CSS (the first meta-layer being SASS).

If you have good editor then you can even directly execute make from the editor to save a few keystrokes.

For further reading please see the introduction in GNU Make documentation.

Multiple Layouts Side By Side (MLSBS)

As stated earlier, the goal was to be able edit style side-by-side. Let’s see how all these steps fit together in order to accomplish this. SASS and Make / Makefile are central to this.

First we use SASS and its plugin to work on single style pieces which contain style for one element, but have multiple layouts within, side-by-side. SASS allows us to conditionally extract style for the layouts one at a time. We use the SASS plugin to tell SASS which part we need.

Here is the most simple example:

$LAYOUT: env(LAYOUT)

@if $LAYOUT == "narrow"
  #logo
    background-image: url(logo-narrow.png)

@if $LAYOUT == "wide"
  #logo
    background-image: url(logo-wide.png)

Remember to use the additional command line argument to activate the plugin.

$ LAYOUT=narrow sass --require env.rb element.sass element.narrow.css
$ LAYOUT=narrow sass --require env.rb element.sass element.wide.css

So we run SASS for each layout we have and extract-compile respective parts. As a result we save these with different names.

Now you may start to worry about having way too many CSS pieces. After all, the total will be the number of layouts (mobile, narrow, normal, wide, etc) multiplied by number of styled elements (logo, title, menu, etc). Maybe it is a big number, but it will be handled automatically.

The next step in the process is to join all CSS pieces belonging to the same layout. For example we concatenate all ‚wide‘ layout CSS pieces. In the end of this step we will have only few files which equals to number of layouts we have.

Now we are able to do many tricks with style preprocessor SASS and commandline imagework suite ImageMagick. Using these tools means executing additional commands to get desired result, repeatedly. Probably first idea would be to use a script which would compile preprocessor files and derive necessary images etc. But the problem here is that the shell script would compile files which would not need to be compiled and prepare images that are already prepared resulting to slow and inefficient workflow.

This is where Make/Makefile, as covered above, come in. You can see an example Makefile for MLSBS in my boilerplate on GitHub.

Figure 7: Layout schematics

Putting it all together

So, to return to our summary of MLSBS from part 1, we’ve now covered every aspect of the workflow:

  • Style Preprocessor: SASS
  • SASS plugin: Awareness of Environment
  • Style and Evaluation: Matching and Specificity
  • Structuring: Splitting and Joining
  • Automated imagework with ImageMagick
  • Media Queries
  • Automation with Make / Makefile

Of course, there’s no need to adopt all of these at once. Individually they are still very useful and can save time: for example splitting files into manageable chunks with SASS; checking an image’s dimensions on the command line using ImageMagick; or just using more efficient selectors.

Together, though, I find them to be an immensely powerful workflow that takes the pain out of writing CSS. I hope it helps you too!

Ragnar Kurm began programming in his teens and later studied Informatics at Tallinn Technical University. He currently runs his own company, and before that worked for ten years at an ISP. Ragnar has extensive programming experience in all major languages (ca 20–30) and continues to learn. In recent years, he has spent the majority of his time building webpages, but he loves backend programming most.

 

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -