Search code examples
cssreactjswebpackcss-loader

Using string class names in React with css-loader and Webpack


I was following a few tutorials on how to set up internal sass/scss/css support for React in Webpack. I finally got something I'm happy with, using HtmlWebpackPlugin to generate the index.html based on a template, sass-loader, typings-for-css-modules-loader (a css-loader wrapper to deal with css modules in typescript), and MiniCssExtractPlugin, to create a separate css file in my built dist folder. My configuration works like this:

module: {
  rules: [

    ...

    { test: /\.(sa|sc|c)ss?$/,
      use: [
        {
          loader: MiniCssExtractPlugin.loader,
          options: {}
        },
        {
          loader: 'typings-for-css-modules-loader',
          options: {
            modules: true,
            namedExport: true,
            camelCase: true
          }
        },
        'sass-loader'
      ]
    }
  ]
}

This setup works when I'm importing my css classes in my React components as objects:

hello.css

.foo {
    color: #5df7ff;
}

hello.tsx

import * as React from 'react';

import { foo } from './hello.css';

export interface HelloProps {
  compiler: string;
  framework: string;
}

export const Hello = (props: HelloProps) =>
  <h1 className={foo}>Hello: {props.compiler} and {props.framework}!!!</h1>; // This works: text is colored correctly

The problem arises when I want to use string class names in the React component, as such (same css as before):

hello.tsx

import * as React from 'react';

import './hello.css';

export interface HelloProps {
  compiler: string;
  framework: string;
}

export const Hello = (props: HelloProps) =>
  <h1 className="foo">Hello: {props.compiler} and {props.framework}!!!</h1>; // This does not work: text is not colored

I assume this is because the Webpack loaders aren't 'smart' enough to correctly resolve the string class names in the React DOM: the loaders do not map the string class names to the hashed ones produced by css-loader.

I understand that using string css class names in React is not the idiomatic way to include css classes: ideally you would have one single complete css class per component using css modules which you would import as in the first example.

The problem arises however when using external UI component libraries (e.g. Ant Design, Semantic UI) which seem to reference their own css string classes.

This puts me off using these external libraries.

Example from using antd (Ant Design):

anthello.tsx

import React from 'react';
import Button from 'antd/lib/button';
import 'antd/es/button/style/index.css';

export interface HelloAntProps {
  message: string;
}

export const HelloAnt = ({ message }: HelloAntProps ) => {
  return (
    <div>
      <h1>Hello {message}</h1>
      <Button type="primary">Test</Button>
    </div>
  );
};

generated css file using the loader stack:

._3WAJv754FZTMKDXGocE913 { /* This class corresponds to the Ant Design ant-btn class */
  line-height: 1.5;
  display: inline-block;
  font-weight: 400;
  text-align: center;
  ...

and the css classes which are looked for in the actual DOM:

<button type="button" class="ant-btn ant-btn-primary"><span>Test</span></button> 
<!-- This does not work, as the only classes present in the css are the hashed ones generated by css-loader -->

I wouldn't know how else to import these libraries and pack them using Webpack into a single css bundle without getting this to work.

Am I missing something?

tl;dr

Is there any way to get css class strings to be correctly resolved with css-loader in the React DOM via some Webpack loader? Is there any workaround I'm missing?


Solution

  • Update: managed to work around the issue by disabling the 'css-modules' options in my css-loader.

    The hashing of the css class names is due to the css-modules option. By disabling this option, the class names are not hashed anymore, enabling external libraries to reference their own classes directly.

    It doesn't look like the issue can be solved easily by keeping the css-modules option.