React-app-rewired: css module with babel-plugin-import not work.

Created on 19 Oct 2017  Â·  18Comments  Â·  Source: timarney/react-app-rewired

Hello,i used css module with babel-plugin-import,but it does't work.
either css module or babel-plugin-import work.
here's my config-overrides file:


const { injectBabelPlugin,getLoader } = require('react-app-rewired')
const rewireLess = require('react-app-rewire-less')
const cssLoaderMatcher = function(rule) {
    return rule.loader && rule.loader.indexOf(`css-loader`) != -1;
}
module.exports = function override(config, env) {
    let l = getLoader(config.module.rules,cssLoaderMatcher);

    l.options = {
        modules: true,
        importLoaders: 1,
        localIdentName: '[local]___[hash:base64:5]'
    }

    config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);
    config = rewireLess(config, env, {
        modifyVars: { "@primary-color": "#1DA57A" },
    })

    return config;
};

https://ant.design/docs/react/use-with-create-react-app
i wanna use this React Ui library with css module , how should i do it

help wanted

Most helpful comment

@WhatAKitty i forked repo from @codebandits and put the code above in and tweaked it a bit:

https://github.com/andriijas/react-app-rewire-less-modules

I also added less options to less module loader which makes it possible to use theme variables in components (primary colors etc). Check example repo: https://github.com/andriijas/create-react-app-playground/

Check these files:
Footer.less
Footer.js
theme.less
config-overrides.js
.env

All 18 comments

I've done something like this to get it working in my project

const { getLoader } = require('react-app-rewired');

const cssLoaderMatcher = function (rule) {
  return rule.loader && rule.loader.indexOf('css-loader') !== -1;
};

function rewireCSS(config, env) {
  const cssRules = getLoader(config.module.rules, rule => rule.test && String(rule.test) === String(/\.css$/));
  cssRules.test = /\.s?css$/;
  const cssLoader = getLoader(config.module.rules, cssLoaderMatcher);
  cssLoader.options = {
    modules: true,
    importLoaders: 1,
    localIdentName: '[local]___[hash:base64:5]',
  };
  return config;
}
module.exports = rewireCSS;

and

const { getLoader } = require('react-app-rewired');
const path = require('path');


function rewireLess(config, env, options = {}) {
  const lessExtension = /\.less$/;
  const fileLoader = getLoader(config.module.rules, rule => rule.loader && typeof rule.loader === 'string' && rule.loader.endsWith(`file-loader${path.sep}index.js`));
  fileLoader.exclude.push(lessExtension);

  const lessLoader = {
    test: /\.less$/,
    use: [
      'style-loader',
      'css-loader',
      {
        loader: 'less-loader',
        options,
      },
    ],
  };
  config.module.rules.push(lessLoader);
  return config;
}
module.exports = rewireLess;

@TedChenNZ Thanks !

Hello, @TedChenNZ I got empty object from less file that like import styles from './xxx.less';.
If I import less like import from './xxx.less'; and use className in jsx it worked.

@WhatAKitty https://github.com/timarney/react-app-rewired/blob/master/packages/react-app-rewire-less rule.loader.endsWith(file-loader${path.sep}index.js` in the example above has changed in latest version.

@andriijas I had changed it already.This is my code:

const cssLoaderMatcher = function (rule) {
  return rule.loader && rule.loader.indexOf('css-loader') !== -1;
};

function rewireCSS(config, env) {
  const cssRules = getLoader(config.module.rules, rule => rule.test && String(rule.test) === String(/\.css$/));
  cssRules.test = /\.s?css$/;
  const cssLoader = getLoader(config.module.rules, cssLoaderMatcher);
  cssLoader.options = {
    modules: true,
    importLoaders: 1,
    localIdentName: '[name]_[local]_[hash:base64:5]',
  };
  return config;
}

function rewireLess(config, env, options = {}) {
  const lessExtension = /\.less$/;
  const fileLoader = getLoader(
    config.module.rules,
    rule =>
      rule.loader &&
      typeof rule.loader === 'string' &&
      rule.loader.indexOf(`${path.sep}file-loader${path.sep}`) !== -1,
  );
  fileLoader.exclude.push(lessExtension);

  const lessLoader = {
    test: /\.less$/,
    use: [
      'style-loader',
      'css-loader',
      {
        loader: 'less-loader',
        options,
      },
    ],
  };
  config.module.rules.push(lessLoader);
  return config;
}

module.exports = function override(config, env) {
  config = injectBabelPlugin('transform-decorators-legacy', config);
  config = rewireCSS(config, env);
  config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);


  config = rewireLess(config, env, {
    modifyVars: {
      '@primary-color': '#1DA57A',
      '@link-color': '#1DA57A',
      '@border-radius-base': '2px',
      '@font-size-base': '14px',
      '@line-height-base': '1.2',
      '@card-actions-background': '#f5f8fa',
    }
  });
  return config;
};

But I still got empty object with import styles from 'xxx.less' in jsx file while @import in less file worked.

@WhatAKitty hmm okay. this config works for me im using antd as well.

const path = require("path");

const { compose, getLoader, injectBabelPlugin } = require("react-app-rewired");
const rewireLess = require("react-app-rewire-less");

const rewireCSSModules = (config, env) => {
  const cssRules = getLoader(
    config.module.rules,
    rule => rule.test && String(rule.test) === String(/\.css$/),
  );

  const cssLoader = getLoader(
    env === "production" ? cssRules.loader : config.module.rules,
    rule =>
      rule.loader &&
      rule.loader.indexOf(`${path.sep}css-loader${path.sep}`) !== -1,
  );
  cssLoader.options = {
    ...cssLoader.options,
    modules: true,
    localIdentName: `${env === "production" ? "" : "[local]-"}[hash:base64:8]`,
  };

  return config;
};

module.exports = function override(config, env) {
  config = injectBabelPlugin(
    ["import", { libraryName: "antd", style: true, libraryDirectory: "es" }],
    config,
  );

  const rewires = compose(
    rewireCSSModules,
    rewireLess.withLoaderOptions({
      modifyVars: {
        "@primary-color": "#...",
        "@info-color": "#..",
        "@font-size-base": "14px",
      },
    }),
  );

  return rewires(config, env);
};

For it to work properly I needed separate config for production and dev/test otherwise it didnt work with npm run build and npm start. Since you are not using scss/sass you can remove cssRules.test = /\.s?css$/; safely.

Also note all styles are becoming local with modules enabled, you need to use :global for global styles

@andriijas also worked in jsx import styles from 'xxx.less' ? It's so strange.

@andriijas yeah, worked fine with your configuration, I can get value from less files. Thanks!

@WhatAKitty yes it worked but then antd styles stopped working because they are not using css modules. What you can do is to check src of https://github.com/codebandits/react-app-rewire-css-modules and see how they only add module support to *.module.css and add similar for *.module.less

@WhatAKitty what we need to do is duplicate less rules. one for node_modules without css modules, one for src/ dir with css modules enable.
roadhog comes with that automatically, might be worth checking out https://github.com/sorrycc/roadhog/blob/master/src/config/common.js

@andriijas sorry, I had no idea about how to do it. I'm a newer for webpack.
I tried it, but failed. Can you help me?

I'm going to try to get it to work later today because u want it to. Have to work on other stuff right now though @whatakitty

Sent from my iPhone

On 2 Nov 2017, at 13:26, WhatAKitty notifications@github.com wrote:

@andriijas sorry, I had no idea about how to do it. I'm a newer for webpack.
I tried it, but failed. Can you help me?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

Hello @andriijas , thanks a lot for your help, I changed some words with https://github.com/codebandits/react-app-rewire-css-modules and it worked great for me.

const cloneDeep = require('lodash.clonedeep')

const ruleChildren = (loader) => loader.use || loader.oneOf || Array.isArray(loader.loader) && loader.loader || []

const findIndexAndRules = (rulesSource, ruleMatcher) => {
    let result = undefined
    const rules = Array.isArray(rulesSource) ? rulesSource : ruleChildren(rulesSource)
    rules.some((rule, index) => result = ruleMatcher(rule) ? {index, rules} : findIndexAndRules(ruleChildren(rule), ruleMatcher))
    return result
}

const findRule = (rulesSource, ruleMatcher) => {
    const {index, rules} = findIndexAndRules(rulesSource, ruleMatcher)
    return rules[index]
}

const cssRuleMatcher = (rule) => rule.test && String(rule.test) === String(/\.css$/)

const createLoaderMatcher = (loader) => (rule) => rule.loader && rule.loader.indexOf(`/${loader}/`) !== -1
const cssLoaderMatcher = createLoaderMatcher('css-loader')
const postcssLoaderMatcher = createLoaderMatcher('postcss-loader')
const fileLoaderMatcher = createLoaderMatcher('file-loader')

const addAfterRule = (rulesSource, ruleMatcher, value) => {
    const {index, rules} = findIndexAndRules(rulesSource, ruleMatcher)
    rules.splice(index + 1, 0, value)
}

const addBeforeRule = (rulesSource, ruleMatcher, value) => {
    const {index, rules} = findIndexAndRules(rulesSource, ruleMatcher)
    rules.splice(index, 0, value)
}

module.exports = function (config, env, lessOptions = {}) {
    const cssRule = findRule(config.module.rules, cssRuleMatcher)
    const lessRule = cloneDeep(cssRule)
    const cssModulesRule = cloneDeep(cssRule)

    cssRule.exclude = /\.module\.css$/

    const cssModulesRuleCssLoader = findRule(cssModulesRule, cssLoaderMatcher)
    cssModulesRuleCssLoader.options = Object.assign({modules: true, localIdentName: '[local]___[hash:base64:5]'}, cssModulesRuleCssLoader.options)
    addBeforeRule(config.module.rules, fileLoaderMatcher, cssModulesRule)

    lessRule.test = /\.less$/
    lessRule.exclude = /\.module\.less$/
    addAfterRule(lessRule, postcssLoaderMatcher, {
      'loader': require.resolve('less-loader'),
      'options': lessOptions,
    })
    addBeforeRule(config.module.rules, fileLoaderMatcher, lessRule)

    const lessModulesRule = cloneDeep(cssModulesRule)
    lessModulesRule.test = /\.module\.less$/

    addAfterRule(lessModulesRule, postcssLoaderMatcher, require.resolve('less-loader'))
    addBeforeRule(config.module.rules, fileLoaderMatcher, lessModulesRule)

    return config
}

This is my override.js:

const path = require('path');
const cloneDeep = require('lodash.clonedeep')
const { compose, getLoader, injectBabelPlugin } = require('react-app-rewired');
const rewireLessModule = require('./react-app-rewire-less-module');

module.exports = function override(config, env) {
  config = injectBabelPlugin('transform-decorators-legacy', config);
  config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);

  config = rewireLessModule(config, env, {
    modifyVars: {
      '@primary-color': '#1DA57A',
      '@link-color': '#1DA57A',
      '@border-radius-base': '2px',
      '@font-size-base': '14px',
      '@line-height-base': '1.2',
      '@card-actions-background': '#f5f8fa',
    },
  });

  return config;
};

Big thanks @WhatAKitty and @codebandits - saved me a lot of time

I changed it a little, the difference is that all css and less from node_modules , for example antd will load normally
and all css and less loaded from src/ will be treated as css modules. No need for foo.module.less files.

Note the libraryDirectory: "es" in babel import for antd, will use node_modules/antd/es instead of node_modules/antd/lib

const path = require("path");

const { compose, getLoader, injectBabelPlugin } = require("react-app-rewired");

const rewireLessModules = (prefix = "", lessOptions = {}) => {
  const cloneDeep = require("lodash/clonedeep");

  const ruleChildren = loader =>
    loader.use ||
    loader.oneOf ||
    (Array.isArray(loader.loader) && loader.loader) ||
    [];

  const findIndexAndRules = (rulesSource, ruleMatcher) => {
    let result = undefined;
    const rules = Array.isArray(rulesSource)
      ? rulesSource
      : ruleChildren(rulesSource);
    rules.some(
      (rule, index) =>
        (result = ruleMatcher(rule)
          ? { index, rules }
          : findIndexAndRules(ruleChildren(rule), ruleMatcher)),
    );
    return result;
  };

  const findRule = (rulesSource, ruleMatcher) => {
    const { index, rules } = findIndexAndRules(rulesSource, ruleMatcher);
    return rules[index];
  };

  const cssRuleMatcher = rule =>
    rule.test && String(rule.test) === String(/\.css$/);

  const createLoaderMatcher = loader => rule =>
    rule.loader && rule.loader.indexOf(`/${loader}/`) !== -1;
  const cssLoaderMatcher = createLoaderMatcher("css-loader");
  const postcssLoaderMatcher = createLoaderMatcher("postcss-loader");
  const fileLoaderMatcher = createLoaderMatcher("file-loader");

  const addAfterRule = (rulesSource, ruleMatcher, value) => {
    const { index, rules } = findIndexAndRules(rulesSource, ruleMatcher);
    rules.splice(index + 1, 0, value);
  };

  const addBeforeRule = (rulesSource, ruleMatcher, value) => {
    const { index, rules } = findIndexAndRules(rulesSource, ruleMatcher);
    rules.splice(index, 0, value);
  };

   return function(config, env) {
    const cssRule = findRule(config.module.rules, cssRuleMatcher);
    cssRule.exclude = path.resolve(__dirname, "src");
    cssRule.include = path.resolve(__dirname, "node_modules");

    const lessRule = cloneDeep(cssRule);
    lessRule.test = /\.less$/;
    const cssModulesRule = cloneDeep(cssRule);

    cssModulesRule.include = path.resolve(__dirname, "src");
    cssModulesRule.exclude = path.resolve(__dirname, "node_modules");

    const cssModulesRuleCssLoader = findRule(cssModulesRule, cssLoaderMatcher);
    cssModulesRuleCssLoader.options = Object.assign(
      {
        modules: true,
        localIdentName: `${env === "production"
          ? prefix
          : "[local]"}-[hash:base64:8]`,
      },
      cssModulesRuleCssLoader.options,
    );
    addBeforeRule(config.module.rules, fileLoaderMatcher, cssModulesRule);

    addAfterRule(lessRule, postcssLoaderMatcher, {
      loader: require.resolve("less-loader"),
      options: lessOptions,
    });
    addBeforeRule(config.module.rules, fileLoaderMatcher, lessRule);

    const lessModulesRule = cloneDeep(cssModulesRule);
    lessModulesRule.test = lessRule.test;

    addAfterRule(
      lessModulesRule,
      postcssLoaderMatcher,
      require.resolve("less-loader"),
    );
    addBeforeRule(config.module.rules, fileLoaderMatcher, lessModulesRule);

    return config;
  };
};

module.exports = function override(config, env) {
  config = injectBabelPlugin(
    ["import", { libraryName: "antd", style: true, libraryDirectory: "es" }],
    config,
  );

  const rewires = compose(
    rewireHTML,
    rewireLessModules("my-css-prefix", {
      modifyVars: {
        "@primary-color": "#..",
        "@info-color": "#...",
        "@font-size-base": "14px",
      },
    }),
  );

  return rewires(config, env);
};

@WhatAKitty i forked repo from @codebandits and put the code above in and tweaked it a bit:

https://github.com/andriijas/react-app-rewire-less-modules

I also added less options to less module loader which makes it possible to use theme variables in components (primary colors etc). Check example repo: https://github.com/andriijas/create-react-app-playground/

Check these files:
Footer.less
Footer.js
theme.less
config-overrides.js
.env

@andriijas good job

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bilthon picture bilthon  Â·  5Comments

zhihongzhong picture zhihongzhong  Â·  4Comments

Yegair picture Yegair  Â·  5Comments

vl4d1m1r4 picture vl4d1m1r4  Â·  5Comments

brendonco picture brendonco  Â·  3Comments