Using CSS Modules in Mid-Backend Projects

In the work, I found that the use of front-end CSS is varied, including Sass, Less, CSS in JS, TailWindCSS, and CSS Modules. With so many types, how do we choose? In this article, I will introduce a method that is best used in middle and back-end projects under the current micro-frontend trend and the best combination of development experience. CSS Modules focuses on pure techniques for solving local scoping and module dependencies.

This best practice is based on CSS Modules. Why should we choose it? In real work, the most painful problem we encounter is style isolation. Under the micro-frontend framework, there will be style coverage between sub-applications, between sub-applications and the main application, and even between different pages of the same project, even if various micro-frontend frameworks try to solve the problem of style isolation. Whether it is through engineering plus namespace or shadow dom, it cannot be solved once and for all, and it has its drawbacks. Compared with Less, Sass technology requires an artificial namespace on each page or component. There are no technical constraints on this process, and verbal norms between people alone are useless. But CSS Modules are undoubtedly a way to completely solve the problem of style conflicts.

The documentation for CSS Modules is fairly simple and can be learned in 10 minutes. Basic mainstream engineering tools and scaffolding are supported. For example, vite supports it by default, and CRA is also naturally supported without any additional configuration. The development experience of CSS Modules is excellent. Writing CSS has never been so smooth. I'll go into details later.

1. CSS Modules + Less

CSS Modules are very simple, so the module.css file still follows the specification of CSS files, so nesting cannot be written. To solve this problem, we introduce Less. To put it simply, the file format of the module.less is used so that we can use the ability of Less to write nested code.

Why not use Sass? In fact, there is not much difference between Sass and Less in essence, and there is no difference between good and bad. The reason I choose Less is that I use antd's component library a lot in my project, and antd uses the Less solution. If you want to customize the theme of antd, you must use Less. With Less, you can effectively make up for many of the shortcomings of CSS Modules, especially nesting. For example, see the code below.

.container {
  .header {
    color: red;
  }
}

2. Definition and Use of Variables

Both Less and CSS Modules support the definition and use of variables. Let's see how it works.

// Define common.less
@width: 10px;
@height: @width + 10px;

// Use
@import './common.less';
.header {
   width: @width;
   height: @height;
}
// Define colors.css
@value blue: #0c77f8;
@value red: #ff0000;
@value green: #aaf200;

// Use
@value colors: "./colors.css";
@value blue, red, green from colors;
.title {
   color: red;
   background-color: blue;
}

These two methods are more troublesome to define and use. When using it, you need to import it explicitly. So what I recommend is another way, which is the way CSS natively supports. Use the documentation to see the basic usage of MDN CSS Variables as follows.

// Define global variables
:root {
   --main-color: #fff;
}

// Define local variables
.container {
   --main-color: #000;
}

// Use variable
.component {
   color: var(--main-color);
}

We can see that variables have a clear -- prefix, which is easier to distinguish and easy to use. No imports are required, and it's easy to do overrides. If we look at the component library of the latest version of antd-mobile, this native method is used extensively for theme customization and style coverage.

As for compatibility, in the middle and background scenarios, Chrome's support is very good and basically does not need to be considered.

3. Reuse of Class

There are inheritance methods based on extensive and mixins in Less. But I don't think the inheritance method of CSS Modules is more convenient. Mixins is an anti-common-sense way of using the code. Once the code is not written well, it is easy to break up, and it is not easy to maintain. It is also difficult for newbies to understand. The way to use composed CSS Modules is as follows.

// Define
.container {
   color: #fff;
}
// Called from the same file
.component {
   composes: container;
}

// Called from different files
.component {
   composes: container from './index.module.less';
   color: #000;
}

The above code will eventually be compiled into <div/>, and the final effective color is #000.

How to Override 3rd Party Component Styles

We often override the styles of third-party components in our usual coding. For example, we use the style of Button in antd. In module.less, we can use the :global keyword. Hash is not automatically added at compile time wherever it is used. And in this way, you can also set the class of the only parent element to him. This way you change the style of a third-party component without affecting the styles of other places that also reference the component.

.container {
  :global(.ant-button) {
    color: var(--main-color);
  }
}

Calculated Style Classnames

If a component's class may require multiple or may require a certain amount of calculations, the traditional way of using CSS Modules is rather ugly. Therefore, we use a more elegant way to solve it, that is, with the help of third-party npm packages, the ability of classnames. See as below.

// When className requires multiple classes, we directly use classnames to pass multiple parameters
<div className={classnames(style.container1, style.container2)} />
// Will eventually compile to <div class="_contianer1_i323u _container2_i889k" />

// If a class needs a certain logical judgment, you can pass in an object and use the value of false or true
// to control the presence or absence of class
<div className={classnames({ [style.container1]: true, [style.container2]: false })} />

// This method is a combination of the above two methods, classnames can receive multiple parameters, objects, and even arrays
<div className={classnames('body', {[style.container1]: true, [style.container2]: false })} />

An Addicting Development Experience

Traditionally writing CSS is difficult to quickly display or locate the style code by pressing and holding cmd + click on the div className of JSX through the editor. But if we use CSS Modules, and after installing the VSCode CSS Modules extension, we can easily achieve positioning and display without switching to the Less file.

Of course, another huge and obvious benefit of using CSS Modules is that we don't need to struggle with class names. We can even define the same name in different components, for example:

import style from './index.module.less';
const Login = () => (
   <div className={style.container}>
     <div className={style.header}>Login</div>
   </div>);
    
const Register = () => (
   <div className={style.container}>
     <div className={style.header}>Registration</div>
   </div>);

As shown above, for the Login and Register components, we both use the container and header classes, and we don't need to add the component prefix in front. This is more conducive to code reuse, and can well express the structure of the page.

What If I Write NPM Components

There is no problem with CSS Modules being used in the business code of the project. If we want to make some components into NPM packages for others to use, and we use CSS Modules, the compiled NPM package will also add Hash to the class, which is dynamic. So when someone wants to override your style, it's very difficult. How to solve this problem?

Indeed, there are two views on this issue. One is to try to override the styles of other components, which is a Hack behavior in itself. We should implement it in a more elegant way, allowing NPM components to provide the corresponding APIs for external calls to modify. The second is that there is a toolkit available, react-css-themr. Each NPM component accepts an external theme parameter (CSS module object) to define all styles. Examples are as follows.

import React from 'react';
import { AppBar } from 'react-toolbox/lib/app_bar';
import theme from './PurpleAppBar.css';

const PurpleAppBar = (props) => (
  <AppBar {...props} theme={theme} />
);

export default PurpleAppBar;

The above content is summed up in our practice, and I hope it will be helpful to you.



Leave a reply



Submit