NOTE: This solution does not require you to change any of your code, just add the loader and it should work out of the box. Please note the caveat about production builds at the end, or check the source for full instructions at Github.
I have created a Webpack loader that works with CSS/LESS/Other CSS module loaders.
The full source and readme can be found on GitHub.
For those who just want to add the loader to their project, it can be used like this:
Add this webpack loader source file somewhere, e.g /webpack/loaders/css-module-proxy.js
/**
* A CSS/LESS/Style module loader that prepends a proxy in non-production builds.
*
* The proxy checks if the loaded style module actually contains the style we are trying to fetch.
* If it doesn't exist (its accessor returns undefined), we crash on debug (non-production) builds!
*
* Inspired by https://github.com/royriojas/css-local-loader
*/
module.exports = function cssLocalLoader(source, map) {
this.cacheable();
if (process.env.NODE_ENV !== "production") {
// noMatch:
// Makes sure that any access prefixed with underscore are filtered out
// otherwise it will crash at runtime when Webpack is probing the locals export.
// toJsonMatch:
// Makes sure that toJSON access on the locals object gets proxied to the correct
// toJSON function.
const requireWrapper = `
// If the access matches this regexp, skip it
const oldLocals = exports.locals;
const noMatch = /^[_]+/;
const toJsonMatch = /^toJSON$/;
const proxy = new Proxy(oldLocals, {
get: function(target, name) {
if (noMatch.test(name)) {
return undefined;
}
if (toJsonMatch.test(name)) {
return oldLocals.toJSON;
}
const clz = target[name];
if (clz === undefined) {
throw new Error("Error: LESS / CSS class named \\"" + name + "\\" does not exist");
}
return clz;
}
});
exports.locals = proxy;
`;
const newSource = `${source}\n\n${requireWrapper}`;
this.callback(null, newSource, map);
} else {
this.callback(null, source, map);
}
};
And then use it from your webpack config, example below is for LESS:
{
test: /\.module\.less$/,
use: [
{ loader: path.resolve("webpack/loaders/css-module-proxy.js") },
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 1,
localIdentName: "[name]__[local]__[hash:base64:5]",
},
},
{ loader: "less-loader" },
],
},
Don't forget to build your release code with NODE_ENV=production
or it may crash when a user visits your site...