Locally scoped CSS

Now, it is time to create our app, which will consist of a Simple button, of the same sort we used in previous examples. We will use it to show all the features of the CSS modules.

Let's create an index.js file, which is the entry we specified in the webpack configuration, and let's import React and ReactDOM as well:

  import React from 'react';
import { render } from 'react-dom';

We can then create a Simple button. As usual, we are going to start with a nonstyled button, and we will add the styles step by step:

  const Button = () => <button>Click me!</button>;

Finally, we can render the button into the DOM:

  render(<Button />, document.body);

Please note that rendering a React component into the body is bad practice, but in this case, we are doing it for simplicity.

Now, suppose we want to apply some styles to the button—a background color, the size, and so on.

We create a regular CSS file, called index.css, and we put the following class into it:

  .button { 
background-color: #ff0000;
width: 320px;
padding: 20px;
border-radius: 5px;
border: none;
outline: none;
}

Now, we said that with CSS modules, we could import the CSS files into the JavaScript; let's look at how it works.

Inside our index.js where we defined the button component, we can add the following line:

  import styles from './index.css';

The result of this import statement is a styles object, where all the attributes are the classes defined in the index.css.

If we run console.log(styles), we can see the following object in DevTools:

  { 
button: "_2wpxM3yizfwbWee6k0UlD4"
}

So, we have an object where the attributes are the class names and the values are (apparently) random strings. We will see later that they are nonrandom, but let's check what we can do with that object first.

We can use the object to set the class name attribute of our button, as follows:

  const Button = () => ( 
<button className={styles.button}>Click me!</button>
);

If we go back to the browser, we can now see that the styles we defined in index.css have been applied to the button.

And that is not magic, because if we check in DevTools, the class that has been applied to the element is the same string that's attached to the style object we imported inside our code:

  <button class="_2wpxM3yizfwbWee6k0UlD4">Click me!</button>

If we look at the header section of the page, we can now see that the same class name has also been injected into the page:

  <style type="text/css"> 
._2wpxM3yizfwbWee6k0UlD4 {
background-color: #ff0000;
width: 320px;
padding: 20px;
border-radius: 5px;
border: none;
outline: none;
}
</style>

This is how the CSS and the style loaders work.

The CSS loader lets you import the CSS files into your JavaScript modules and, when the modules flag is activated, all the class names are locally scoped to the module they are imported into.

As we mentioned previously, the string we imported was nonrandom, but it is generated using the hash of the file and some other parameters in a way that is unique within the codebase.

Finally, the style loader takes the result of the CSS module's transformation and it injects the styles inside the header section of the page.

This is very powerful because we have the full power and expressiveness of the CSS, combined with the advantages of having locally scoped class names and explicit dependencies.

As mentioned at the beginning of this chapter, CSS is global, and that makes it very hard to maintain in large applications. With CSS modules, class names are locally scoped and they cannot clash with other class names in different parts of the application, enforcing a deterministic result.

Moreover, explicitly importing the CSS dependencies inside our components helps us see clearly which components need which CSS. It is also very useful for eliminating dead code because when we delete a component for any reason, we can tell exactly which CSS it was using.

CSS modules are regular CSS, so we can use pseudo-classes, media queries, and animations.

For example, we can add CSS rules like the following:

  .button:hover { 
color: #fff;
}

.button:active {
position: relative;
top: 2px;
}

@media (max-width: 480px) {
.button {
width: 160px
}
}

This will be transformed into the following code and injected into the document:

  ._2wpxM3yizfwbWee6k0UlD4:hover { 
color: #fff;
}

._2wpxM3yizfwbWee6k0UlD4:active {
position: relative;
top: 2px;
}

@media (max-width: 480px) {
._2wpxM3yizfwbWee6k0UlD4 {
width: 160px
}
}

The class names get created and they get replaced everywhere the button is used, making it reliable and local, as expected.

As you may have noticed, those class names are great, but they make debugging pretty hard because we cannot easily tell which classes generated the hash.

What we can do in development mode is add a special configuration parameter, with which we can choose the pattern that's used to produce the scoped class names.

For example, we can change the value of the loader as follows:

  { 
test: /.css/,
use: [
'style-loader',
'css-loader?modules=true&localIdentName=[local]--[hash:base64:5]'
]
}

Here, localIdentName is the parameter and [local] and [hash:base64:5] are placeholders for the original class name value and a five-character hash.

Other available placeholders are [path], which represents the path of the CSS file, and [name], which is the name of the source CSS file.

Activating the previous configuration option, the result we have in the browser is as follows:

  <button class="button--2wpxM">Click me!</button>

This is way more readable and easier to debug.

In production, we do not need class names like this, and we are more interested in performance, so we may want shorter class names and hashes.

With webpack, it is pretty straightforward because we can have multiple configuration files that can be used in the different stages of our application life cycle. Also, in production, we may want to extract the CSS file instead of injecting it into the browser from the bundle so that we can have a lighter bundle and cache the CSS on a CDN for better performance.

To do that, you need to install another webpack plugin, called mini-css-extract-plugin, which can write an actual CSS file, putting in all the scoped classes that were generated from CSS modules.

There are a couple of features of CSS modules that are worth mentioning.

The first one is the global keyword. Prefixing any class with :global, in fact, means asking CSS modules not to scope the current selector locally.

For example, let's say we change our CSS as follows:

  :global .button { 
...
}

The output will be as follows:

  .button { 
...
}

This is good if you want to apply styles that cannot be scoped locally, such as third-party widgets.

My favorite feature of CSS modules is composition. With composition, we can classes from the same file or external dependencies and get all the styles applied to the element.

For example, extract the rule to set the background to red from the rules for the button into a separate block, as follows:

  .background-red { 
background-color: #ff0000;
}

We can then compose it inside our button in the following way:

  .button { 
composes: background-red;
width: 320px;
padding: 20px;
border-radius: 5px;
border: none;
outline: none;
}

The result is that all the rules of the button and all the rules of the composes declaration are applied to the element.

This is a very powerful feature and it works in a fascinating way. You might expect that all the composed classes are duplicated inside the classes where they are referenced as SASS @extend does, but that is not the case. Simply put, all the composed class names are applied one after the other on the component in the DOM.

In our specific case, we would have the following:

<button class="_2wpxM3yizfwbWee6k0UlD4 Sf8w9cFdQXdRV_i9dgcOq">Click me!</button>

Here, the CSS that is injected into the page is as follows:

  .Sf8w9cFdQXdRV_i9dgcOq { 
background-color: #ff0000;
}

._2wpxM3yizfwbWee6k0UlD4 {
width: 320px;
padding: 20px;
border-radius: 5px;
border: none;
outline: none;
}
..................Content has been hidden....................

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