如何在 Nextjs 中散列 CSS class 名称?

How to hash CSS class names in Nextjs?

如何在 Nextjs 的 Webpack 配置中编辑 css-loaderlocalIdentName 字段,以便我可以 hash/hide/obfuscate css class 命名?

下面的例子来自纽约时报。注意 class 个名字:

不幸的是,Nextjs 中不支持将自定义配置传递给 Webpack 加载程序。但是我们可以通过使用 next.config.js.

来解决这个问题

首先,在项目目录的根目录中创建next.config.js

对于 Nextjs 11

module.exports = {
  webpack(config, { buildId, dev, isServer, defaultLoaders, webpack }) {
    config.module.rules[3].oneOf.forEach((moduleLoader, i) => {
      Array.isArray(moduleLoader.use) &&
        moduleLoader.use.forEach((l) => {
          if (
            l.loader.includes("\css-loader") &&
            !l.loader.includes("postcss-loader")
          ) {
            const { getLocalIdent, ...others } = l.options.modules;

            l.options = {
              ...l.options,
              modules: {
                ...others,
                localIdentName: "[hash:base64:6]",
              },
            };
          }
        });
    });
    return config;
  },
};

对于 Next.js 10.2 或更高版本:

module.exports = {
  webpack(config, { buildId, dev, isServer, defaultLoaders, webpack }) {
    config.module.rules[3].oneOf.forEach((moduleLoader, i) => {
      Array.isArray(moduleLoader.use) &&
        moduleLoader.use.forEach((l) => {
          if (
            l.loader.includes("\css-loader") &&
            !l.loader.includes("postcss-loader")
          ) {
            const { getLocalIdent, ...others } = l.options.modules;

            l.options = {
              ...l.options,
              modules: {
                ...others,
                localIdentName: "[hash:base64:6]",
              },
            };
          }
        });
    });
    return config;
  },
};

否则用这个:

module.exports = {
  webpack(config, { buildId, dev, isServer, defaultLoaders, webpack }) {
    config.module.rules[1].oneOf.forEach((moduleLoader, i) => {
      Array.isArray(moduleLoader.use) &&
        moduleLoader.use.forEach((l) => {
          if (
            l.loader.includes('\css-loader') &&
            !l.loader.includes('postcss-loader')
          ) {
            const { getLocalIdent, ...others } = l.options.modules;

            l.options = {
              ...l.options,
              modules: {
                ...others,
                localIdentName: '[hash:base64:6]',
              },
            };
          }
        });
    });
    return config;
  },
};

如果您只想在生产 中对 class 名称 进行哈希处理,您可以将 process.env.NODE_ENVif 语句一起使用。像这样:

module.exports = {
  webpack(config, { buildId, dev, isServer, defaultLoaders, webpack }) {
    if (process.env.NODE_ENV === "production") {
      ...
      ...

      return config;
    } else {
      return config;
    }
  },
};

这对我有用 Next.js 11:

module.exports = {
        webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
        config.module.rules[3].oneOf.forEach((moduleLoader, i) => {
          Array.isArray(moduleLoader.use) &&
            moduleLoader.use.forEach((l) => {
              if (
                l.loader.includes('\css-loader') &&
                !l.loader.includes('postcss-loader')
              ) {
                const { getLocalIdent, ...others } = l.options.modules;
    
                l.options = {
                  ...l.options,
                  modules: {
                    ...others,
                    localIdentName: '[hash:base64:6]',
                  },
                };
              }
            });
        });
          return config;
      },
    };

None 此线程中的答案将适用于较新的 Next.js 版本(v11.1.3-canary.2 及更高版本),因为在 PR#28529, the Next.js team has switched to a customized version of css-loader that has no defaultGetLocalIdent, nor does it checks if getLocalIndent is null or undefined.

任何最终从加载程序配置中删除 getLocalIdent 的解决方案都将导致 TypeError: getLocalIdent is not a function。换句话说,现在必须提供getLocalIdent 的功能。这是一个例子:

const path = require('path');
const loaderUtils = require('loader-utils');

// based on https://github.com/vercel/next.js/blob/0af3b526408bae26d6b3f8cab75c4229998bf7cb/packages/next/build/webpack/config/blocks/css/loaders/getCssModuleLocalIdent.ts
const hashOnlyIdent = (context, _, exportName) =>
  loaderUtils
    .getHashDigest(
      Buffer.from(
        `filePath:${path
          .relative(context.rootContext, context.resourcePath)
          .replace(/\+/g, '/')}#className:${exportName}`,
      ),
      'md4',
      'base64',
      6,
    )
    .replace(/^(-?\d|--)/, '_');

module.exports = {
  webpack(config, { dev }) {
    const rules = config.module.rules
      .find((rule) => typeof rule.oneOf === 'object')
      .oneOf.filter((rule) => Array.isArray(rule.use));

    if (!dev)
      rules.forEach((rule) => {
        rule.use.forEach((moduleLoader) => {
          if (
            moduleLoader.loader?.includes('css-loader') &&
            !moduleLoader.loader?.includes('postcss-loader')
          )
            moduleLoader.options.modules.getLocalIdent = hashOnlyIdent;

            // earlier below statements were sufficient:
            // delete moduleLoader.options.modules.getLocalIdent;
            // moduleLoader.options.modules.localIdentName = '[hash:base64:6]';
        });
      });

    return config;
  },
};

这也适用于 Next v12。

Demo:

开发中-

生产中 -