background

When you use CSS in a React project, if you don’t use CSS in JS, you will import a CSS file directly into the JS file, such as import ‘./index.css’. So we’ll use CSS Modules to solve the scope problem, as follows:

import styles from './index.css';

const App = (a)= > (
  <div className={styles.App}>App</div>
);
Copy the code

The idea is to map each className in a CSS file to a unique string according to certain rules to ensure that classnames do not duplicate or conflict in the global scope. The configuration method is also very simple. Just add a modules configuration item to the CSS-loader of Webpack. For details, refer to the csS-Loader documentation.

Sometimes we need to make a CSS file globally valid, and CSS Modules provide a globally valid solution, but it’s not as straightforward as simply importing ‘./foo.css’. The Create React App provides a workaround by enabling CSS Modules for.module. CSS files and not for.css files. Many scaffolds and tools use this workaround as well.

The reason

The suffixed name scheme has never felt anything wrong, but the filename is a bit long and not elegant enough. Until a few days ago, I saw that Umi released version 3.0. Out of curiosity, I casually looked through the document and found a particularly interesting function:

Umi automatically recognizes the use of CSS Modules, and you are using them as CSS Modules.

Such as:

// CSS Modules
import styles from './foo.css';

/ / the CSS Modules
import './foo.css';
Copy the code

It’s a clever feature, intuitive and easy to use. Is also out of curiosity, looked at Umi’s source code, explore how this is achieved.

implementation

Through looking at THE source Umi found that the implementation of this function is not complex, very simple and clever.

We can see that the two import methods are different with or without CSS Modules, so we can use Babel to distinguish between the two.

Babel runs in three phases:

  1. Parse the source code into an AST (abstract syntax tree)
  2. Transformation to add, delete, or modify nodes of the AST
  3. Generate, turning the AST back to source

The Babel plug-in works in the second phase, accessing different types of AST nodes and adding, deleting and modifying them as needed. So we can write a plug-in to recognize the two import methods and add identifiers to differentiate them in WebPack.

How to write a Babel plug-in is beyond the scope of this article, but you can check out the references at the end of this article to learn more.

Let’s first open the AST Explorer and compare the AST of the two statements.

// import './index.css';

{
  "type": "Program",
  "start": 0,
  "end": 21,
  "body": [
    {
      "type": "ImportDeclaration",
      "start": 0,
      "end": 21,
      "specifiers": [],
      "source": {
        "type": "Literal",
        "start": 7,
        "end": 20,
        "value": "./index.css",
        "raw": "'./index.css'"
      }
    }
  ],
  "sourceType": "module"
}
Copy the code
// import styles from './index.css';

{
  "type": "Program",
  "start": 0,
  "end": 33,
  "body": [
    {
      "type": "ImportDeclaration",
      "start": 0,
      "end": 33,
      "specifiers": [
        {
          "type": "ImportDefaultSpecifier",
          "start": 7,
          "end": 13,
          "local": {
            "type": "Identifier",
            "start": 7,
            "end": 13,
            "name": "styles"
          }
        }
      ],
      "source": {
        "type": "Literal",
        "start": 19,
        "end": 32,
        "value": "./index.css",
        "raw": "'./index.css'"
      }
    }
  ],
  "sourceType": "module"
}
Copy the code

It’s obvious that the two specifiers are different, so you can use this field and file name extensions (after all, you can’t handle non-CSS files), and add a query parameter to CSS files that need CSS Modules enabled, so you can’t change the filename directly. Otherwise, webpack will not be able to find the file.

const { extname } = require('path');
const CSS_FILE_EXTENSIONS = ['.css'.'.scss'.'.sass'.'.less'];

module.exports = (a)= > {
  return {
    visitor: {
      ImportDeclaration(path) {
        const { specifiers, source } = path.node;
        const { value } = source;
        if (
          specifiers.length > 0
          && CSS_FILE_EXTENSIONS.includes(extname(value))
        ) {
          source.value = `${value}? css_modules`; ,}}}}; };Copy the code

We use this plugin to access a node of type ImportDeclaration in the AST, determine that the specifiers are not empty and that the file suffix is style file, and add a? Css_modules query parameter. This parameter is used later in webpack to distinguish whether CSS Modules are enabled.

Configure the plug-in in the Babel configuration file, and then start configuring the Loader for WebPack. We added two CSS-related rules to modules.rules.oneof in webpack configuration. The difference between the two rules is that CSS modules are enabled in the former rule, and there are more resourceQuery configuration items in the rule. After the first rule is matched, the remaining rules are not matched down. CSS files with CSS Modules enabled will match the first rule, and CSS files without CSS Modules enabled will match the second rule.

Because test is used to match file paths, and does not match paths? So you need to use resourceQuery to match query parameters. Note that the resourceQuery configuration values are consistent with the parameters added in the Babel plug-in.

The brief configuration is as follows:

// webpack.config.js

module.exports = {
  modules: {
    rules: {
      oneOf: [{test: /\.css$/.resourceQuery: /css_modules/.loader: 'css-loader'.options: {
            modules: true,}}, {test: /\.css$/.loader: 'css-loader',},],},},},};Copy the code

For Less and Sass, add a related Loader.

In this way, the automatic identification of CSS Modules is realized, which is not difficult to achieve, but very clever and practical.

If you want to use Babel in your project, you can directly install Umi’s @umijs/ babel-plugin-auto-CSS-modules, which has been published to NPM. It can customize query parameters through the flag configuration item.

reference

  • Umi Babel plug-in source code
  • How do I create the Babel plug-in
  • AST explorer
  • CSS Modules are configured in csS-loader
  • CSS Modules document
  • Webpack loader configuration
  • Create the CSS Modules in the React App
  • Introduction to Umi 3

Original address: pengtikui.cn/webpack-aut…