css module 로 생성되는 classname 의 prefix 를 바꿔보자

2023년 10월 27일 · #문제해결


Next (@^13) 에서 css module 을 사용하다보니, .SomeComponent_someClassName__hash 이런 이름으로 생성되는 클래스 이름이 마음에 들지 않았다.

이걸 개발 중에는 .someClassName_hash 이렇게 짧게 나오게 바꾸고, production 빌드에서는 난독화하여 보여주고자 했다. 이 글에 나오는 코드를 내 입맛대로 조금 고쳐서 썼다.

production 빌드에서는 base64 인코딩 후, 문자만 남기고 지워줬다. (+, =, 등 특수문자가 포함되거나 숫자로 시작하는 className은 스타일이 적용되지 않는다. 그리고 어차피 디코딩할 필요가 없으니 괜찮다.)

// next.config.js

const path = require('path');
const isProduction = process.env.NODE_ENV === 'production';

module.exports = {
  sassOptions: {
    includePaths: [path.join(__dirname, 'src/style')],
  },
  webpack: config => {
    console.log('rules', config.module.rules);
    const oneOf = config.module.rules.find(rule => typeof rule.oneOf === 'object');
    console.log('oneOf', oneOf);

    if (oneOf) {
      // Find the module which targets *.scss|*.sass files
      const moduleSassRule = oneOf.oneOf.find(rule => regexEqual(rule.test, /\.module\.(scss|sass)$/));

      if (moduleSassRule) {
        // Get the config object for css-loader plugin
        const cssLoader = moduleSassRule.use.find(({ loader }) => loader.includes('/css-loader/'));
        if (cssLoader) {
          cssLoader.options = {
            ...cssLoader.options,
            modules: cssLoaderOptions(cssLoader.options.modules),
          };
        }
      }
    }

    return config;
  },
};

const regexEqual = (x, y) => {
  return (
    x instanceof RegExp &&
    y instanceof RegExp &&
    x.source === y.source &&
    x.global === y.global &&
    x.ignoreCase === y.ignoreCase &&
    x.multiline === y.multiline
  );
};

// css-loader 플러그인 덮어쓰기
function cssLoaderOptions(modules) {
  const { getLocalIdent, ...others } = modules;
  return {
    getLocalIdent: (context, _, exportName, options) => {
      const localIndent = getLocalIdent(context, _, exportName, options);
      const hash = localIndent.split('_').pop();
      const name = isProduction ? encodeBase64WithOnlyAlphabets(exportName) : exportName;
      const customIndent = name + '_' + hash;
      return customIndent;
    },
    ...others,
  };
}

const encodeBase64WithOnlyAlphabets = str => {
  const base64 = Buffer.from(str, 'utf8').toString('base64');
  return base64.replace(/\W/g, '');
};

AS-IS

TO-BE

development

production