How To Set Classname Without {styles.red} In Next.js
Solution 1:
Next.js has globals.css file and it's in styles folder so you can make:
className="red"
and then in globals.css file:
.red{
//...
}
But I recommend you to use .module.css files because it's cleaner and doesn't make a mess.
Solution 2:
UPDATE:
I hadn't checked this earlier. You can directly use babel-plugin-react-css-modules
.
Ignore the content below
I had earlier wrote a Babel plugin for this. It is not robust, can be improved, but does the job:
// babel-plugin-cx.jsconst __path = require('path');
constplugin = ({ types: t }) => {
const cloneNode = t.cloneNode || t.cloneDeep;
return {
name: 'babel-plugin-cx',
visitor: {
Program: {
enter(path, state) {
state.stylesIdentifier = path.scope.generateUidIdentifier('styles');
},
exit(path, state) {
if (state.hasProp) {
const importDeclaration = t.importDeclaration(
[t.importDefaultSpecifier(state.stylesIdentifier)],
t.stringLiteral(
__path
.parse(state.file.opts.filename)
.name.toLowerCase()
.replace(/^([^]*)$/, state.opts.pathReplace),
),
);
path.node.body.unshift(importDeclaration);
}
},
},
JSXAttribute(path, state) {
if (path.node.name.name !== state.opts.propName) return;
if (
state.opts.ignoredElements.includes(
path.findParent((p) => p.isJSXOpeningElement()).node.name.name,
)
)
return;
path.node.name.name = 'className';
const value = path.get('value');
if (value.isLiteral()) {
value.replaceWith(
t.jsxExpressionContainer(
t.memberExpression(
cloneNode(state.stylesIdentifier),
cloneNode(value.node),
true,
false,
),
),
);
state.hasProp = true;
} elseif (value.isJSXExpressionContainer()) {
const expression = value.get('expression');
expression.replaceWith(
t.memberExpression(
cloneNode(state.stylesIdentifier),
cloneNode(expression.node),
true,
false,
),
);
state.hasProp = true;
}
},
JSXSpreadAttribute(path, state) {
if (
state.opts.ignoredElements.includes(
path.findParent((p) => p.isJSXOpeningElement()).node.name.name,
)
)
return;
const argument = path.get('argument');
if (!argument.isObjectExpression()) return;
const properties = argument.get('properties');
for (const property of properties) {
if (property.node.key.name === state.opts.propName) {
property.node.key.name = 'className';
const value = property.get('value');
value.replaceWith(
t.memberExpression(
cloneNode(state.stylesIdentifier),
cloneNode(value.node),
true,
false,
),
);
state.hasProp = true;
}
}
},
},
};
};
module.exports = plugin;
// .babelrc
{
"presets": ["next/babel"],
"plugins": [
[
"./babel-plugin-cx",
{
"pathReplace": "./$1.module.css",
"propName": "cx",
"ignoredElements": ["circle", "ellipse", "radialGradient"]
}
]
]
}
If using Typescript, you need to add this (also add this file to includes
array in tsconfig.json
:
// custom.d.tsimporttype * asReactfrom'react';
declaremodule'react' {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-unnecessary-qualifierinterfaceHTMLAttributes<T> extendsReact.DOMAttributes<T> {
cx?: string;
}
}
After this, you can simply write:
<divcx="red">Red text!</div>
There is no need to import the module file. It will be automatically imported if necessary from the specified "pathReplace"
option. ($1
indicates the file name using the cx
attribute).
pathReplace
example:
@styles/_$1.module.scss
: if file using cx
is Component.tsx
then the styles will be imported from module @styles/_component.module.scss
.
You can configure the propName
using the provided option. You will also need to change next-env.d.ts
accordingly. And, change the ignoredElements
option accordingly, as your attribute name may be same as some attribute defined in the JSX/HTML standard.
Caveats:
The plugin currently completely ignores
className
attribute ifcx
is set on the element/component, i.e., if you need to merge with some global class name then useclassName={`global ${styles.red}`}
.More than one class in
cx
is not supported. Can be implemented easily, but I was quite lazy.Spread attribute is only partially supported:
// supported: <div {...{cx: "red", other: "attribute"}}>Red text!</div> // not supported: const attrs = {cx: "red", other: "attribute"}; <div {...attrs}>Red text!</div>
You may like to configure this rule in ESLint
react/jsx-props-no-spreading
.
Post a Comment for "How To Set Classname Without {styles.red} In Next.js"