© Martine Dowden and Michael Dowden 2020
M. Dowden, M. DowdenArchitecting CSShttps://doi.org/10.1007/978-1-4842-5750-0_7

7. Preprocessors

Martine Dowden1  and Michael Dowden1
(1)
Brownsburg, IN, USA
 

For CSS there are several preprocessors available. These will take data, written in their own particular syntax, and then output CSS for the browser to consume. The benefit of these includes access to functionality such as color-editing functions or nesting rules that are not yet available in CSS. They also gave us access to CSS variables before they were supported by the language itself. Some of the most popular processors include Sass, Less, and Stylus.

Note

Examples in this chapter will use SCSS.1 These techniques are available using other preprocessors; however, feature availability and syntax will vary based on the preprocessor used.

Implications for Architecture

The way in which code is organized and architected can be very different when using preprocessors than when using pure CSS because of added functionality, such as mixins and the ability to extend classes. The ability to compute values in ways simply not available outside of preprocessors today brings the ability to write DRY semantic code. It can be defined in just one place and then reused throughout the style sheet not much differently than some of the object-oriented principles used in other programming languages.

The downside of using preprocessors is that they add a layer of complexity to the application that does not exist when using pure CSS. Even though some preprocessors, such as Less,2 can be run in the browser directly, it is not recommended for production use because it is less performant and reliable than plain CSS. When using preprocessors, we therefore need some sort of build step in order to compile the code into CSS.

Debugging can also be a challenge, especially when using some of the more complex or advanced features of the code. This stems from the CSS being generated not matching one-to-one with the code being written. For example, the properties and attributes being added to a class could come from a mixin (more about mixins later in this chapter) rather than part of the ruleset. The CSS being applied is the output, not the mixin itself, so tracking back to which mixin created the output can be difficult. Sourcemaps can help with this. A sourcemap is a file that can be generated with the CSS which links the output back to the code that generated it. But again, this needs to be set up specifically as part of the build process.

So first, before even choosing the processor to be used, the question of whether the added complexity is necessary should be asked.

Nesting

Nesting allows us to have a clear visual hierarchy, which CSS does not have. The following CSS (Listing 7-1) can be nested (Listing 7-2), making it evident at a glance what the hierarchy is.
nav {
  padding: 0;
  margin: 0;
}
nav ul {
  padding: 0;
}
nav ul li {
  padding: 10px;
  border: solid 1px blue;
  background: yellow;
  color: yellow;
}
Listing 7-1

CSS

nav {
  padding: 0;
  margin: 0;
  ul {
    padding: 0;
    li {
      padding: 10px;
      border: solid 1px blue;
      background: yellow;
      color: blue;
    }
  }
}
Listing 7-2

Nested SCSS

As much as it makes it very easy to know that the styles set on the list item will only be applied to the navigation list items, nesting makes it really easy to create overly specific rules. In the preceding example, nesting the list item inside of the unordered list is superfluous and does not add any value. Having it nested under the navigation only, one level higher than its current location, would be sufficient.

Nesting can, however, bring clarity to some situations. Let’s look at Listing 7-3.
a:link, a:visited {
  color: gray;
  font-variant: small-caps;
  border: dotted 1px rgba(0, 0, 0, 0);
  text-decoration: none;
  &:hover { border: dotted 1px cornflowerblue; }
  &:focus { border: solid 1px cadetblue; }
  &:active { border: double 1px darkcyan; }
}
Listing 7-3

Nested SCSS

The ampersand refers back to the parent element, so the hover is placed on the anchor tag when it is a link, or a visited link. Nesting here makes it very clear that the hover, focus, and active selectors are all children of a:link and a:visited.

Without nesting, the code would have looked something like this (Listing 7-4):
a:link,
a:visited {
  color: gray;
  font-variant: small-caps;
  border: dotted 1px rgba(0, 0, 0, 0);
  text-decoration: none;
}
a:link:hover,
a:visited:hover {
  border: dotted 1px cornflowerblue;
}
a:link:focus,
a:visited:focus {
  border: solid 1px cadetblue;
}
a:link:active,
a:visited:active {
  border: double 1px darkcyan;
}
Listing 7-4

Nonnested CSS

Without nesting, it is more difficult to tell at a glance that links and visited links also have styles for hover, focus, and active states. Furthermore, the nested code is more concise and does not repeat the root elements, decreasing the chance of typos or errors.

Care when nesting must be taken in order not to create rules that are overly specific. This happens when elements are nested too deep. However, it can help with code legibility.

Color Functions and Variables

Variables, although available today in CSS as discussed in Chapter 2, were first made available through the use of preprocessors. The CSS version (custom properties), although influenced by preprocessor variables, does have some advantages over the preprocessor variables. Custom properties can be accessed and changed via JavaScript, while preprocessor variables cannot. In creating the CSS output, preprocessor variables do not remain variables; they are replaced by their assigned value. CSS custom properties, however, stay variables and can be manipulated at any time, including during runtime.

Although variables can be used for any value, such as default padding amount, they are extremely powerful when used in conjunction with color functions to define the theme of an application. The brand colors for an application include those given in Table 7-1.
Table 7-1

Color Values and Usage

../images/487079_1_En_7_Chapter/487079_1_En_7_Figa_HTML.png

The colors can be set to semantic names based upon what their usage will be and then manipulated using color functions when the color value or saturation needs to be altered (Listing 7-1).

Color functions will vary based on the preprocessor used; most include functions to lighten, darken, or shift the hue, saturation, or transparency of a color. In Listing 7-5, we use scale-color() which takes a color and then can alter any combination of the following color properties: red, green blue, saturation, lightness, and alpha. When setting lightness to 10%, we are making the color 10% lighter than the original and keeping all other values the same.
$primary: #AEC5EB;
$accent: #E9AFA3;
$links: #AEC5EB;
$background: #F9DEC9;
$dark: #3A405A;
$light: #FAFAFA;
$border: solid 1px $light;
$dark-text: $dark;
$light-text: $light;
$spacing: 1.25rem;
body {
  background: scale-color($background, $lightness: 10%);
  color: $dark-text;
  padding: $spacing;
}
a:link, a:visited {
  color: $link;
}
a:hover, a:focus {
  color: scale-color($link, $lightness: -10%);
}
button {
  color: $light-text;
  background: $primary;
  border: $border;
  padding: $spacing;
}
section, article {
  background: scale-color($background, $lightness: 20%);
  padding: $spacing;
  margin-bottom: $spacing;
}
Listing 7-5

Colors

By using color transformation functions and variables, not only don’t we have to remember the exact values for each of the colors and any variations we may have in use, but we also increase our ability to keep our theme consistent. Furthermore, if the colors were to change, this could be done in one place. The eventuality of a color changing is why color names should be based upon their usage rather than their actual color. If the variable name was $pink, for example, and the accent color was changed to purple, we would now have to either find the variable name everywhere and update it, or we would have a variable name that does not represent the color that is assigned to it. Situations like this make maintainability very difficult and code confusing. Selecting semantic variable names is incredibly important to the maintainability of the code.

Mixins

Mixins allow developers to create sets of properties and values that can easily be reused throughout the application.

Simple Mixin

The simple example shown in Listing 7-6 shows the use of a simple mixin to define a set of properties in one place and then include them in another context. The @include property is used to assign the previously defined mixin to the new context – an element in this case – but it could just as easily be included within a class definition.
@mixin card {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  box-shadow: 1px 1px 3px silver
}
div {
  @include card;
}
Listing 7-6

Simple Mixin

Parameters

Mixins can also take parameters in order to change the outputs of the mixin based on the parameters passed, as shown with the $elevation parameter in Listing 7-7.
@mixin card($elevation) {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  $offset: $elevation * 1;
  $blur: $elevation * 2;
  box-shadow: #{$offset}px #{$offset}px #{$blur}px silver;
}
div {
  @include card(3);
}
Listing 7-7

Mixin with Arguments

Arguments

Logic can also be added within the mixin. In Listing 7-8, styles are applied differently based upon a non-zero $elevation.
@mixin card($elevation) {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  @if $elevation == 0 {
    border: solid 1px silver;
  } @else {
    $offset: $elevation * 1;
    $blur: $elevation * 2;
    box-shadow: #{$offset}px #{$offset}px #{$blur}px silver;
  }
}
body {
  padding: 2rem;
}
h1 {
  margin: 0;
}
header {
  @include card(0)
}
div {
  @include card(2);
}
Listing 7-8

Mixin with Arguments and Logic

The advantage of using mixins, especially with arguments, is that it allows for DRY code. The code is written once and managed in one place but applied to multiple classes. The preceding code (Listing 7-8) would compile to what is shown in Listing 7-9 and display Figure 7-1.
body {
  padding: 2rem;
}
h1 {
  margin: 0;
}
header {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  border: solid 1px silver;
}
div {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  box-shadow: 2px 2px 4px silver;
}
Listing 7-9

CSS Output

../images/487079_1_En_7_Chapter/487079_1_En_7_Fig1_HTML.jpg
Figure 7-1

Mixins Output

Mixins are very powerful in preventing the need to duplicate code or nonsemantic class names. Without mixins, the earlier code would require a potentially infinite number of classes, or have to rewrite the border and shadow multiple times and then maintain it in multiple locations. Another great application is when a specific parameter might apply to many aspects of an element, but differs depending on context, while the rest of the element needs to stay consistent regardless of the situation. Informational boxes to the user might be an example, where there is a need for information, successes, warnings, and errors. The boxes need to look the same except for color (Listings 7-10 and 7-11 and Figure 7-2).
<body>
  <p class="message info">Information</p>
  <p class="message success">Success</p>
  <p class="message warning">Warning</p>
  <p class="message error">Error</p>
</body>
Listing 7-10

Informational Boxes HTML

@mixin message($color) {
  background: lighten($color, 40%);
  border: solid 1px $color;
}
body {
  padding: 2rem;
}
.message {
  padding: 1rem;
}
.info {
  @include message(blue);
}
.success {
  @include message(green);
}
.warning {
  @include message(orange);
}
.error {
  @include message(red);
}
Listing 7-11

Informational Boxes SCSS

../images/487079_1_En_7_Chapter/487079_1_En_7_Fig2_HTML.jpg
Figure 7-2

Informational Boxes

Even though the padding could have been included in the mixin, it is separated out into its own class because when a mixin is added, it does its computation and outputs all the code each time; therefore, mixins are not a good use case for static information. Static styles are simply programmatically being copied over into each class increasing the size of the CSS and therefore upload time. Classes, defaults on elements, or the use of the @extend at-rule are much better options for static styles.

@extend

Extend, unlike mixins, prevents the duplication of code in the resulting CSS. While a mixin copies the declaration block for each selector it is included within, extend creates a single declaration block and consolidates the selectors.

The advantage of this methodology is in creating base classes for basic styles toward which semantically named classes will be pointed. The code is neither duplicated nor copied and prevents the use of numerous nonsemantic classes on an HTML element. For code maintainability it also means that the style of the elements is controlled in the CSS. If the style coming from extending another rule is no longer wanted, we need only to remove the @extend. By simply adding the class name to the HTML instead of using @extend , we would have had to edit the HTML in order to change look and feel. By using @extend instead of adding the same class name to a multitude of elements, we continue to maintain a separation of concerns. Our elements can have class names that match their purpose, rather than how they display, and we handle the styling via the CSS.

Revisiting the example found in the “Mixins” section rather than adding both message and the type to each class, we can create one class that determines the type and has the defaults set by .message (see Listings 7-12 and 7-13 and Figure 7-3).
<body>
  <p class="info-message">Information</p>
  <p class="success-message">Success</p>
  <p class="warning-message">Warning</p>
  <p class="error-message">Error</p>
</body>
Listing 7-12

Informational Boxes HTML – Revisited

@mixin message($color) {
  background: lighten($color, 40%);
  border: solid 1px $color;
}
body {
  padding: 2rem;
}
.message {
  padding: 1rem;
}
.info-message {
  @include message(blue);
  @extend .message;
}
.success-message {
  @include message(green);
  @extend .message
}
.warning-message {
  @include message(orange);
  @extend .message
}
.error-message {
  @include message(red);
  @extend .message
}
Listing 7-13

Informational Boxes SCSS – Revisited

../images/487079_1_En_7_Chapter/487079_1_En_7_Fig3_HTML.jpg
Figure 7-3

Informational Boxes – Revisited

Notice both examples have the same resulting appearance. But the latter allows for only having one class dictating the entire class for the element rather than two. For code maintainability, the power of @extend lies in the ability to declare the entire class in one place without copy-pasting or duplicating the code both in the Sass and also in the compiled CSS (see Listing 7-14 for the CSS output).
body {
  padding: 2rem;
}
.message, .error-message, .warning-message, .success-message, .info-message {
  padding: 1rem;
}
.info-message {
  background: #ccccff;
  border: solid 1px blue;
}
.success-message {
  background: #4dff4d;
  border: solid 1px green;
}
.warning-message {
  background: #ffedcc;
  border: solid 1px orange;
}
.error-message {
  background: #ffcccc;
  border: solid 1px red;
}
Listing 7-14

Informational Boxes – Revisited Output CSS

Notice the message class now has multiple other selectors as well, but was not duplicated in the output.

@Import

Imports allow the user to create partial files in which variables, mixins, and reusable code can be placed. Sass imports work similarly to CSS imports in that it copies the SCSS they contain to the style sheet they are being imported by. They must therefore be used with caution. It is very easy to bloat code by repeatedly importing an entire theme, for example, into each component. Sharing mixins and variables, since they are not copied, but produce an output within a style, is a perfect application of the use of @import, because unlike classes, they do not get copied.

Creating import files to have information accessible from anywhere in the application becomes very interesting when dealing with components because more often than not, such as when using Angular out of the box or creating component in JavaScript and Shadow DOM, the CSS is scoped and therefore in a separate file or area of the application than the rest of the CSS. Adding variables and mixins to a partial – a file to be imported into other files that do not have a use on its own – helps keep the code DRY.

Note

Partials are sometimes denoted by having an underscore at the beginning of their name to separate them from style sheets.

Another use case of @import is to prevent an application’s CSS style sheet from becoming an unmaintainable megalith of a file. By breaking the CSS into smaller sections to be imported into a main style sheet, the code can be easier to find, collaborate on, and maintain (see Listing 7-15).
@import "_variables"
@import "nav"
@import "carousel"
 .
 .
 .
a:link, a:visited { ... }
 .
 .
 .
Listing 7-15

@import

Summary

In this chapter we looked at a very small subset of functionality brought to use via the use of preprocessors. We looked at mixins, imports, extends, color function, and variable and how they impact how we might organize and structure our application’s CSS. In the next chapter, we will look at how JavaScript can interact with our CSS especially in the context of modern frameworks.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.74.227