Search code examples
reactjssassnrwl-nxreact-css-modulesrspack

Setup css modules with rspack using Nx monorepo and react


I'm trying to setup Rspack inside a Nx monorepo that uses React, module federation, Typescript support and css modules using scss. I got almost everything done except for the last part. The css modules just wouldn't work. The css class names get added to the rendered html in the browser (with a weird generated name using the path and all) but the css itself is missing and doesn't get rendered. Has anyone an idea how to set this up correctly? I have looked at the Rspack documentation and that helped me a lot to at least achieve to not get errors and warnings anymore when compiling.

Here is my rspack.config.js:

const {
  ModuleFederationPlugin,
} = require('@module-federation/enhanced/rspack');
const mfConfig = require('./module-federation.config');
const path = require('path');

const sassConfig = {
  test: /\.scss$|\.sass$/,
  use: [
    'style-loader',
    'css-loader',
    {
      loader: 'sass-loader',
      options: {
        // using `modern-compiler` and `sass-embedded` together significantly improve build performance,
        // requires `sass-loader >= 14.2.1`
        api: 'modern-compiler',
        implementation: require.resolve('sass-embedded'),
      },
    },
  ],
  // set to 'css/auto' if you want to support '*.module.(scss|sass)' as CSS Modules, otherwise set type to 'css'
  type: 'css/auto',
};

module.exports = composePlugins(withNx(), withReact(), (config, context) => {
  config.context = path.join(context.context.root, 'apps/container');
  config.plugins.push(new ModuleFederationPlugin({ ...mfConfig, dts: false }));
  config.output.publicPath = '/';
  config.devServer = {
    ...config.devServer,
    port: 4200,
  };
  //  Resolve the paths in tsconfig
  config.resolve = {
    ...config.resolve,
    tsConfig: {
      configFile: path.resolve(
        context.context.root,
        'apps/container/tsconfig.app.json',
      ),
      references: 'auto',
    },
  };
  //  CSS setup
  config.experiments = { css: true };
  config.module = {
    ...config.module,
    rules: [...config.module.rules, sassConfig],
    parser: {
      ...config.module.parser,
      'css/auto': {
        namedExports: false,
      },
    },
  };
  return config;
});

Here is an example of what gets rendered in the browser:

<div class="-_src_components_Header_module_scss-ICN_appBar"></div>

I would rather like to have the class name with an hash at the end like "ICN_appBar--aslkfj" but I'm not sure how to set this up in Rspack either.

Any help would be much appreciated.


Solution

  • Ok, after a week of frustration, reading and trial and error, here is the solution that I have found for the issue mentioned above. It solves:

    • Nx setup for Rspack
    • Module federation setup
    • Resolution of path's in tsconfig.base.json
    • Setup for CSS and SCSS modules

    Resources that helped me on the way:

    RTFM's:

    And here is my rspack.config.js.

    PLEASE NOTE: This works for MY project and I don't consider this a perfect solution. If you have other ideas or something to correct, feel free to let me know.

    Otherwise, I hope this helps somebody.

    const { composePlugins, withNx, withReact } = require('@nx/rspack');
    const {
      ModuleFederationPlugin,
    } = require('@module-federation/enhanced/rspack');
    const mfConfig = require('./module-federation.config');
    const path = require('path');
    
    const sassConfig = {
      test: /\.scss$|\.sass$/,
      use: [
        {
          loader: 'sass-loader',
          options: {
            // using `modern-compiler` and `sass-embedded` together significantly improve build performance,
            // requires `sass-loader >= 14.2.1`
            api: 'modern-compiler',
            implementation: require.resolve('sass-embedded'),
          },
        },
      ],
      // set to 'css/auto' if you want to support '*.module.(scss|sass)' as CSS Modules, otherwise set type to 'css'
      type: 'css/auto',
      exclude: '/node_modules/',
    };
    
    const cssConfig = {
      test: /\.css$/,
      use: [
        {
          loader: 'css-loader',
        },
      ],
      type: 'css/auto',
      exclude: '/node_modules/',
    };
    
    module.exports = composePlugins(withNx(), withReact(), (config, context) => {
      config.context = path.join(context.context.root, 'apps/container'); // Path to app
      config.plugins.push(new ModuleFederationPlugin({ ...mfConfig, dts: false }));
      config.output.publicPath = '/'; // '/' for host, 'auto' for remotes
      config.devServer = {
        ...config.devServer,
        port: 4200, // Make sure port fits to application
      };
      //  Resolve the paths in tsconfig
      config.resolve = {
        ...config.resolve,
        tsConfig: {
          configFile: path.resolve(
            context.context.root,
            'apps/container/tsconfig.app.json',
          ),
          references: 'auto',
        },
      };
      //  CSS / SCSS setup
      config.experiments = { css: true };
      config.module = {
        ...config.module,
        rules: [...config.module.rules, cssConfig, sassConfig],
        parser: {
          ...config.module.parser,
          'css/auto': {
            namedExports: false,
          },
        },
        //  Make sure naming css classes of scss modules is working
        generator: {
          ...config.module.generator,
          'css/auto': {
            localIdentName: '[local]-[id]',
            exportsConvention: 'camel-case',
          },
        },
      };
      return config;
    });