reactjswebpackreact-css-modulesreact-app-rewired

Removing Hash From React Css Modules


I can't seem to remove the custom hash on my class names generated by CSS Modules. I'm trying to adjust according to this thread, this medium, this stack overflow, and the web-pack documentation on css-loader but to no success.

I'm using react-app-rewired and attempting the adjust the webpack configuration modeling myself after react-app-rewire-typings-for-css-module and this thread which has worked well for converting to camelCase.

I'm attempting to use the well-documented localIdentName and/or getLocalIdent but can't seem to get the correct configuration. The syntax continues to follow [name]__[local]__[hash:base64:5]

config-override.js

/* config-overrides.js */
// @link https://github.com/timarney/react-app-rewired/


module.exports = {

    webpack: (config) => {

        const regexes = [
            /\.module\.css$/.toString(),
            /\.module\.(scss|sass)$/.toString()
        ];

        const oneOfs = config.module.rules.find((rule) => !!rule.oneOf).oneOf;

        // @link https://webpack.js.org/loaders/css-loader/
        // camel-case style names from css modules
        for (const oneOf of oneOfs) {
            if (!oneOf.test || !regexes.includes(oneOf.test.toString())) continue;

            const cssLoader = oneOf.use.find(
                (entry) =>
                    entry.loader &&
                    entry.loader.includes('css-loader') &&
                    !entry.loader.includes('postcss-loader')
            );

            const {options} = cssLoader

            if (undefined !== options && options.modules) {
                // @link https://webpack.js.org/loaders/css-loader/
                options.modules.exportLocalsConvention = 'camelCase';
                options.modules.localIdentName = '[local]';
                options.localIdentName = '[local]';
                options.modules.exportGlobals = true;
                options.modules.getLocalIdent = (context, localIdentName, localName, options) => {
                    console.log(localIdentName)
                    return localIdentName;
                };

            }

        }

        return config;

    }

};

My dependencies in package.json

"dependencies": {
    "@carbon/react": "^1.9.0",
    "@fortawesome/fontawesome-free": "^6.1.2",
    "@fortawesome/fontawesome-svg-core": "^6.1.2",
    "@fortawesome/free-solid-svg-icons": "^6.1.2",
    "@fortawesome/react-fontawesome": "^0.2.0",
    "@sweetalert/with-react": "^0.1.1",
    "@types/history": "^4.7.2",
    "@types/jest": "24.0.15",
    "@types/react-dom": "^18.0.0",
    "animate.css": "^3.7.2",
    "axios": "^0.24.0",
    "axios-cache-adapter": "^2.7.3",
    "bootstrap": "5.2.0",
    "canvas-nest.js": "^2.0.4",
    "chartist": "0.10.1",
    "classnames": "^2.3.1",
    "colors": "^1.3.3",
    "core-js": "^3.6.5",
    "css-loader": "^6.7.1",
    "dangerously-set-html-content": "^1.0.9",
    "deepmerge": "^4.2.2",
    "dropzone": "^5.7.1",
    "formik": "^2.2.9",
    "history": "^4.9.0",
    "ip": "^2.0.0",
    "jquery": "^3.6.0",
    "jquery-backstretch": "2.1.16",
    "jquery-bracket": "^0.11.1",
    "jquery-form": "^4.3.0",
    "jquery-pjax": "^2.0.1",
    "jsoneditor": "^9.5.6",
    "moment": "^2.29.3",
    "moment-countdown": "^0.0.3",
    "moment-timezone": "^0.5.34",
    "mustache": "^2.3.2",
    "nouislider": "13.1.5",
    "npm-run-all": "4.1.3",
    "pace-js": "^1.2.4",
    "perfect-scrollbar": "1.4.0",
    "php-serialized-data": "^0.6.1",
    "prop-types": "15.7.2",
    "qs": "^6.9.4",
    "raw.macro": "^0.4.2",
    "react": "^18.0.0",
    "react-animate-on-scroll": "^2.1.5",
    "react-big-calendar": "0.20.1",
    "react-chartist": "0.13.3",
    "react-code-blocks": "0.0.8",
    "react-data-grid": "^7.0.0-beta.13",
    "react-data-table-component": "^7.5.0",
    "react-datetime": "^3.1.1",
    "react-dom": "^18.0.0",
    "react-helmet": "^6.1.0",
    "react-jvectormap": "0.0.15",
    "react-loading-skeleton": "^3.0.1",
    "react-outside-click-handler": "^1.3.0",
    "react-render-html": "^0.6.0",
    "react-responsive": "^8.2.0",
    "react-router-dom": "^6.3.0",
    "react-scripts": "5.0.1",
    "react-select": "^5.3.1",
    "react-slick": "^0.24.0",
    "react-table": "^7.7.0",
    "react-tagsinput": "^3.19.0",
    "react-toastify": "^8.1.0",
    "simplex-noise": "^3.0.0",
    "sweetalert": "^2.1.2",
    "three-js": "^79.0.0",
    "ts-node": "^10.7.0",
    "typed-scss-modules": "^6.5.0",
    "web-vitals": "^2.1.4",
    "yup": "^0.32.11"
  },

Please let me know if you see any problems or need more information! Thanks in advance

EDIT:

I'm trying to gain additional context using const log = getLogger({ name: 'config-overrides.js' }); and after adding have received a new, seemingly unrelated, message

ERROR in ./src/views/Tournaments/style.module.scss (./node_modules/css-modules-typescript-loader/index.js!./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].oneOf[8].use[2]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[0].oneOf[8].use[3]!./node_modules/resolve-url-loader/index.js??ruleSet[1].rules[0].oneOf[8].use[4]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].oneOf[8].use[5]!./src/views/Tournaments/style.module.scss)
Module build failed (from ./node_modules/css-loader/dist/cjs.js):
Error: The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly" or "dashesOnly"
    at getModulesOptions (/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/css-loader/dist/utils.js:635:13)
    at normalizeOptions (/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/css-loader/dist/utils.js:644:26)
    at Object.loader (/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/css-loader/dist/index.js:37:43)

And I'm also using this configuration. After running I'm caught in what seems like an infinite boot.

/* config-overrides.js */
// @link https://github.com/timarney/react-app-rewired/
const getLogger = require('webpack-log');
const rewireTypingsForCssModule = require("react-app-rewire-typings-for-css-module");

const log = getLogger({ name: 'config-overrides.js' });

module.exports = {

    webpack: (config) => {

        config = rewireTypingsForCssModule.factory({
            esModule: true,
            modules: {
                exportLocalsConvention: 'camelCase',
                localIdentName: '[local]',
                exportGlobals: true,
                getLocalIdent: (context, localIdentName, localName, options) => {
                    //log.info('info', context)
                    log.warn('localIdentName', localIdentName)
                    log.trace('localName', localName)
                    log.error('options', options)
                    return localName;
                }
            }
        })(config);

        return config;

    }

};

After letting this configuration run into failure, a heap exhaust was reached. Commenting out this log.info('info', context) will solve the issue.

<--- Last few GCs --->

[5654:0x7fdcff12b000]   196783 ms: Mark-sweep (reduce) 4069.0 (4143.6) -> 4068.1 (4143.9) MB, 5176.7 / 0.0 ms  (average mu = 0.084, current mu = 0.002) allocation failure; scavenge might not succeed
[5654:0x7fdcff12b000]   201782 ms: Mark-sweep (reduce) 4069.3 (4143.9) -> 4068.4 (4144.1) MB, 4989.4 / 0.0 ms  (average mu = 0.044, current mu = 0.002) allocation failure; scavenge might not succeed


<--- JS stacktrace --->

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
 1: 0x10da9b4a8 node::Abort() [/usr/local/Cellar/node/18.7.0/bin/node]
 2: 0x10da9c619 node::OnFatalError(char const*, char const*) [/usr/local/Cellar/node/18.7.0/bin/node]
 3: 0x10dbf524d v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/local/Cellar/node/18.7.0/bin/node]
 4: 0x10dbf51e5 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/local/Cellar/node/18.7.0/bin/node]
 5: 0x10dd51097 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/usr/local/Cellar/node/18.7.0/bin/node]
 6: 0x10dd4fcca v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/local/Cellar/node/18.7.0/bin/node]
 7: 0x10dd44d5d v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/Cellar/node/18.7.0/bin/node]
 8: 0x10dd4557d v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/Cellar/node/18.7.0/bin/node]
 9: 0x10dd2eecb v8::internal::Factory::NewFillerObject(int, v8::internal::AllocationAlignment, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/usr/local/Cellar/node/18.7.0/bin/node]
10: 0x10e0251b2 v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/usr/local/Cellar/node/18.7.0/bin/node]
11: 0x10d8f58b9 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/usr/local/Cellar/node/18.7.0/bin/node]

After about 5-10 iterations, I've stopped receiving logging from the console. Specifically the getLocalIdent function. Below is my base-line webpack configuration which is still logging.

⬡ config-overrides.js: webpack configuration start {"target":["browserslist"],"stats":"errors-warnings","mode":"development","bail":false,"devtool":"cheap-module-source-map","entry":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/src/index.tsx","output":{"path":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/build","pathinfo":true,"filename":"static/js/bundle.js","chunkFilename":"static/js/[name].chunk.js","assetModuleFilename":"static/media/[name].[hash][ext]","publicPath":"/"},"cache":{"type":"filesystem","version":"2173f3a0c48bf394b767b880b6f1a1c1","cacheDirectory":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/.cache","store":"pack","buildDependencies":{"defaultWebpack":["webpack/lib/"],"config":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/react-scripts/config/webpack.config.js"],"tsconfig":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/tsconfig.json"]}},"infrastructureLogging":{"level":"none"},"optimization":{"minimize":false,"minimizer":[{"options":{"test":{},"extractComments":true,"parallel":true,"minimizer":{"options":{"parse":{"ecma":8},"compress":{"ecma":5,"warnings":false,"comparisons":false,"inline":2},"mangle":{"safari10":true},"keep_classnames":false,"keep_fnames":false,"output":{"ecma":5,"comments":false,"ascii_only":true}}}}},{"options":{"test":{},"parallel":true,"minimizer":{"options":{}}}}]},"resolve":{"modules":["node_modules","/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules"],"extensions":[".web.mjs",".mjs",".web.js",".js",".web.ts",".ts",".web.tsx",".tsx",".json",".web.jsx",".jsx"],"alias":{"react-native":"react-native-web","src":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/src"},"plugins":[{"appSrcs":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/src"],"allowedFiles":{},"allowedPaths":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/react-refresh","/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/@pmmmwh/react-refresh-webpack-plugin/lib","/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/babel-preset-react-app","/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/@babel/runtime/helpers/esm","/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/@babel/runtime/regenerator"]}]},"module":{"strictExportPresence":true,"rules":[{"oneOf":[{"test":[{}],"type":"asset","mimetype":"image/avif","parser":{"dataUrlCondition":{"maxSize":10000}}},{"test":[{},{},{},{}],"type":"asset","parser":{"dataUrlCondition":{"maxSize":10000}}},{"test":{},"use":[{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/@svgr/webpack/lib/index.js","options":{"prettier":false,"svgo":false,"svgoConfig":{"plugins":[{"removeViewBox":false}]},"titleProp":true,"ref":true}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/file-loader/dist/cjs.js","options":{"name":"static/media/[name].[hash].[ext]"}}],"issuer":{"and":[{}]}},{"test":{},"include":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/src","loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/babel-loader/lib/index.js","options":{"customize":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/babel-preset-react-app/webpack-overrides.js","presets":[["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/babel-preset-react-app/index.js",{"runtime":"automatic"}]],"babelrc":false,"configFile":false,"cacheIdentifier":"development:babel-plugin-named-asset-import@0.3.8:babel-preset-react-app@10.0.1:react-dev-utils@12.0.1:react-scripts@5.0.1","plugins":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/react-refresh/babel.js"],"cacheDirectory":true,"cacheCompression":false,"compact":false}},{"test":{},"exclude":{},"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/babel-loader/lib/index.js","options":{"babelrc":false,"configFile":false,"compact":false,"presets":[["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/babel-preset-react-app/dependencies.js",{"helpers":true}]],"cacheDirectory":true,"cacheCompression":false,"cacheIdentifier":"development:babel-plugin-named-asset-import@0.3.8:babel-preset-react-app@10.0.1:react-dev-utils@12.0.1:react-scripts@5.0.1","sourceMaps":false,"inputSourceMap":false}},{"test":{},"exclude":{},"use":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/style-loader/dist/cjs.js",{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/css-loader/dist/cjs.js","options":{"importLoaders":1,"sourceMap":true,"modules":{"mode":"icss"}}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/postcss-loader/dist/cjs.js","options":{"postcssOptions":{"ident":"postcss","config":false,"plugins":["postcss-flexbugs-fixes",["postcss-preset-env",{"autoprefixer":{"flexbox":"no-2009"},"stage":3}],"postcss-normalize"]},"sourceMap":true}}],"sideEffects":true},{"test":{},"use":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/style-loader/dist/cjs.js",{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/css-loader/dist/cjs.js","options":{"importLoaders":1,"sourceMap":true,"modules":{"mode":"local"}}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/postcss-loader/dist/cjs.js","options":{"postcssOptions":{"ident":"postcss","config":false,"plugins":["postcss-flexbugs-fixes",["postcss-preset-env",{"autoprefixer":{"flexbox":"no-2009"},"stage":3}],"postcss-normalize"]},"sourceMap":true}}]},{"test":{},"exclude":{},"use":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/style-loader/dist/cjs.js",{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/css-loader/dist/cjs.js","options":{"importLoaders":3,"sourceMap":true,"modules":{"mode":"icss"}}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/postcss-loader/dist/cjs.js","options":{"postcssOptions":{"ident":"postcss","config":false,"plugins":["postcss-flexbugs-fixes",["postcss-preset-env",{"autoprefixer":{"flexbox":"no-2009"},"stage":3}],"postcss-normalize"]},"sourceMap":true}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/resolve-url-loader/index.js","options":{"sourceMap":true,"root":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/src"}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/sass-loader/dist/cjs.js","options":{"sourceMap":true}}],"sideEffects":true},{"test":{},"use":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/style-loader/dist/cjs.js",{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/css-loader/dist/cjs.js","options":{"importLoaders":3,"sourceMap":true,"modules":{"mode":"local"}}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/postcss-loader/dist/cjs.js","options":{"postcssOptions":{"ident":"postcss","config":false,"plugins":["postcss-flexbugs-fixes",["postcss-preset-env",{"autoprefixer":{"flexbox":"no-2009"},"stage":3}],"postcss-normalize"]},"sourceMap":true}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/resolve-url-loader/index.js","options":{"sourceMap":true,"root":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/src"}},{"loader":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/sass-loader/dist/cjs.js","options":{"sourceMap":true}}]},{"exclude":[{},{},{},{}],"type":"asset/resource"}]}]},"plugins":[{"userOptions":{"inject":true,"template":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/public/index.html"},"version":5},{"replacements":{"NODE_ENV":"development","PUBLIC_URL":"","FAST_REFRESH":true,"REACT_APP_NODE_PROXY_URL":"http://local.dropingaming.gg:8080/","REACT_APP_NODE_PROXY_WS_URL_TARGET":"ws://local.dropingaming.gg:8888/","REACT_APP_TAIS_ENV":"1"}},{"appPath":"/Users/richardmiles/IdeaProjects/dropingaming.com/react"},{"definitions":{"process.env":{"NODE_ENV":"\"development\"","PUBLIC_URL":"\"\"","FAST_REFRESH":"true","REACT_APP_NODE_PROXY_URL":"\"http://local.dropingaming.gg:8080/\"","REACT_APP_NODE_PROXY_WS_URL_TARGET":"\"ws://local.dropingaming.gg:8888/\"","REACT_APP_TAIS_ENV":"\"1\""}}},{"options":{"overlay":false,"exclude":{},"include":{}}},{"options":{},"logger":{},"pathCache":{},"fsOperations":0,"primed":false},{"options":{"assetHookStage":null,"basePath":"","fileName":"asset-manifest.json","filter":null,"map":null,"publicPath":"/","removeKeyHash":{},"sort":null,"transformExtensions":{},"useEntryKeys":false,"useLegacyEmit":false,"writeToFileEmit":false}},{"options":{"resourceRegExp":{},"contextRegExp":{}}},{"options":{"async":true,"typescript":{"typescriptPath":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/typescript/lib/typescript.js","configOverwrite":{"compilerOptions":{"sourceMap":true,"skipLibCheck":true,"inlineSourceMap":false,"declarationMap":false,"noEmit":true,"incremental":true,"tsBuildInfoFile":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/.cache/tsconfig.tsbuildinfo"}},"context":"/Users/richardmiles/IdeaProjects/dropingaming.com/react","diagnosticOptions":{"syntactic":true},"mode":"write-references"},"issue":{"include":[{"file":"../**/src/**/*.{ts,tsx}"},{"file":"**/src/**/*.{ts,tsx}"}],"exclude":[{"file":"**/src/**/__tests__/**"},{"file":"**/src/**/?(*.){spec|test}.*"},{"file":"**/src/setupProxy.*"},{"file":"**/src/setupTests.*"}]},"logger":{"infrastructure":"silent"}}},{"key":"ESLintWebpackPlugin","options":{"extensions":["js","mjs","jsx","ts","tsx"],"emitError":true,"emitWarning":true,"failOnError":true,"resourceQueryExclude":[],"formatter":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/react-dev-utils/eslintFormatter.js","eslintPath":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/react-scripts/node_modules/eslint/lib/api.js","context":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/src","cache":true,"cacheLocation":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/.cache/.eslintcache","cwd":"/Users/richardmiles/IdeaProjects/dropingaming.com/react","resolvePluginsRelativeTo":"/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/react-scripts/config","baseConfig":{"extends":["/Users/richardmiles/IdeaProjects/dropingaming.com/react/node_modules/eslint-config-react-app/base.js"],"rules":{}}}}],"performance":false}

Okay so Webpack will cache these generated configurations; you can run rm -rf node_modules/.cache to get the logging to reset. This seems to not be perfect either..

Nearing Solution:

That seems to, for the first time, actually effect the page. The problem now is how explicit it is. My output in the browser now looks like class="[local] [local] [local] [local] [local]". This is encouraging.


Solution

  • Webpack aggressively caches these directives and must be cleared for changes to be seen. I've had some success with running rm -rf node_modules/.cache before I npm start. Generally the code above is good, but verbose. It can be simplified to the following:

    /* config-overrides.js */
    // @link https://github.com/timarney/react-app-rewired/
    const getLogger = require('webpack-log');
    const rewireTypingsForCssModule = require("react-app-rewire-typings-for-css-module");
    
    const log = getLogger({ name: 'config-overrides.js' });
    
    module.exports = {
    
        webpack: (config) => {
    
            // what's going on here? @link https://stackoverflow.com/questions/73551420/removing-hash-from-react-css-modules
            config = rewireTypingsForCssModule.factory({
                modules: {
                    exportLocalsConvention: 'camelCase',
                    mode: 'local',
                    exportGlobals: true,
                    getLocalIdent: (context, localIdentName, localName) => {
                        return localName;
                    }
                }
            })(config);
    
            log.info('webpack configuration post config-overrides.js', JSON.stringify(config))
    
            return config;
    
        }
    
    };