如何在 Next.js 中设置没有 {styles.red} 的 className

IT技术 javascript reactjs next.js
2021-04-27 06:27:25

描述

我只想在 Next.js 中使用没有 {styles.class-name} 约定的类的纯名称,我谷歌它并发现我需要配置 next.config.js 文件。那么,有人对此有很好的参考吗?

我有这个:

它在 Next.js 中运行良好

第一

并且需要这个:

这在 Next.js 中默认不起作用

第二

2个回答

Next.js 有 globals.css 文件,它在样式文件夹中,因此您可以:

className="red"

然后在 globals.css 文件中:

.red{
//...
}

但我建议您使用.module.css文件,因为它更干净并且不会造成混乱。

更新:

我之前没有检查过这个。可以直接使用babel-plugin-react-css-modules


无视以下内容

我之前为此编写了一个 Babel 插件。它不健壮,可以改进,但可以完成工作:

// babel-plugin-cx.js

const __path = require('path');

const plugin = ({ 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;
        } else if (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"]
      }
    ]
  ]
}

如果使用 Typescript,则需要添加此内容(还将此文件添加到includes数组中tsconfig.json

// custom.d.ts

import type * as React from 'react';

declare module 'react' {
  // eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-unnecessary-qualifier
  interface HTMLAttributes<T> extends React.DOMAttributes<T> {
    cx?: string;
  }
}

在此之后,您可以简单地编写:

<div cx="red">Red text!</div>

无需导入module文件。如有必要,它将从指定"pathReplace"选项自动导入$1使用cx属性表示文件名)。

pathReplace 例子:

@styles/_$1.module.scss: 如果文件使用的cxComponent.tsx那么样式将从module导入@styles/_component.module.scss

您可以propName使用提供的选项进行配置您还需要相应地进行更改next-env.d.ts并且,相应地更改ignoredElements选项,因为您的属性名称可能与 JSX/HTML 标准中定义的某些属性相同。

注意事项:

  • 该插件目前完全忽略在元素/组件上设置的className属性 if cx,即,如果您需要与某些全局类名合并,则使用className={`global ${styles.red}`}.

  • cx不支持超过一个class可以很容易地实现,但我很懒惰。

  • 仅部分支持 Spread 属性:

    // supported:
    <div {...{cx: "red", other: "attribute"}}>Red text!</div>
    
    // not supported:
    const attrs = {cx: "red", other: "attribute"};
    <div {...attrs}>Red text!</div>
    

    您可能希望在 ESLint 中配置此规则react/jsx-props-no-spreading