next.js : how to prevail against imported css in node_modules

Have you seen this message before ?

https://nextjs.org/docs/messages/css-npm “One of your dependencies (node_modules) imports a CSS file.”

Well, what ? You are just importing styles in one of your node_modules package like this :

import styles from "./file.css"

Usually it is a source of frustration. css modules can only be imported from pages.
When you try style-loader in the custom webpack config, you get a document is not defined error.
Then while using the string-replace-loader to artificially remove the import css statement, you can get a page load error… this can really drive you nuts.

But what next.js needs is just to understand what is the kind of value behind the styles object.
Here, styles is a Record of classnames, where the key equals the value.

Example :
.h1 {
line-height: 1.25rem;
font-weight: 700;
}
is transpiled into this json : {“h1”: “h1”}.

Then why not making next.js read the json instead of the css file ? that file manipulation is hacky, but you will be able to walk around the error once for all.
Here is the script that you must run before next build in order to transform all your css imports in json imports :

import fastGlob from “fast-glob”;
import { promises } from “fs”;
import cssTree from “css-tree”;
const { parse, walk } = cssTree;

const allStylesheets = await Promise.all(
(
await fastGlob([“node_modules/package1/dist/*.css”, “node_modules/package2/dist/*.css”], {
dot: true,
})
).map((fileName) =>
promises.readFile(fileName).then((content) => ({ fileName, content }))
)
);
const allEcmascriptModules = await Promise.all(
(
await fastGlob([“node_modules/package1/dist/index.esm.js”, “node_modules/package2/dist/index.esm.js”], {
dot: true,
})
).map((fileName) =>
promises.readFile(fileName).then((content) => ({ fileName, content }))
)
);
const allCommonJsModules = await Promise.all(
(
await fastGlob([“node_modules/package1/dist/index.cjs”, “node_modules/package2/dist/index.cjs”], { dot: true })
).map((fileName) =>
promises.readFile(fileName).then((content) => ({ fileName, content }))
)
);

await Promise.all(
allStylesheets.map(async ({ fileName, content }) => {
const result = {};
const abstractSyntaxTree = parse(content);
walk(abstractSyntaxTree, (node) => {
if (node.type === “ClassSelector”) {
result[node.name] = node.name;
}
});
return await promises.writeFile(
fileName.replace(/\.css$/, “.json”),
JSON.stringify(result)
);
})
);

await Promise.all(
allEcmascriptModules.map(async ({ fileName, content }) => {
const fileContents = content.toString();
if (fileContents.indexOf(“import styles from”) === –1)
return Promise.resolve();
const fixedContent = fileContents.replace(
/import styles from ‘.\/([^\.]+).css’/g,
(_, cssFileName) => `import styles from ‘./${cssFileName}.json’`
);
return await promises.writeFile(fileName, fixedContent);
})
);

await Promise.all(
allCommonJsModules.map(async ({ fileName, content }) => {
const fileContents = content.toString();
if (fileContents.indexOf(“var styles = _interopDefault(require(“) === –1)
return Promise.resolve();
const fixedContent = fileContents.replace(
/var styles = _interopDefault\(require\(\.\/([^\.]+)\.css’\)\);/g,
(_, cssFileName) =>
`var styles = _interopDefault(require(‘./${cssFileName}.json’));`
);
return await promises.writeFile(fileName, fixedContent);
})
);


you can now add this script to your build command and you will soon forget about your troubles with css modules in next.js.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.