[Next.js] babel-plugin-react-css-modules 使用 hash 配置

3,592 阅读2分钟

插件使用方法请参考官方文档 和 之前的文档[Next.js] 简化 CSS 开发.

本文主要讲使用 babel-plugin-react-css-modules hash 配置后 scope 样式丢失问题的排查过程与结果。

第一步,在官方文档和google上找答案

  1. generateScopedName 必须和 next.config.js 中的cssLoaderOptions.localIdentName 值保持一致,没毛病;

  2. babel-plugin-react-css-modules 的 context 必须和 webpack 的context 保持一致。

    .babelrc 只支持json,添加 context,需要改成 。.babelrc.js

    const path = require('path');
    
    module.exports = {
        presets: [
            'next/babel'
        ],
        plugins: [
            ['react-css-modules', {
                context: path.resolve(__dirname),
                generateScopedName: '[local]__[hash:base64:5]',
                exclude: 'node_modules',
                filetypes: {
                    '.scss': {
                        'syntax': 'postcss-scss'
                    }
                }
            }]
        ]
    };
    

    context 已经指向根目录了,问题并没有解决。

第二步,项目git Issues 里找答案

找到了几个问题相关,但是都处于open状态,没有解决,closed 状态的回答也是context。麻烦了,找不到更多答案了。

第三步,调试插件源码确认问题源头

样式去哪了?

找到webpack打包生成的css文件 .next/static/css/styles.chunk.css,可以看到 local-container 转换成了 local-container__3xXP-,dom标签上写的是 local-container__1KbHQ,哈希值不一致;

层层定位,找到生成哈希值的地方:

css-loader 和 babel-plugin-react-css-modules 都使用了loader-utils 这个插件来完成格式转换;

css-loader 哈希转换前用到的字符串是 components\Example\style.scss+local-container;

babel-plugin-react-css-modules 是 components/Example/style.scss+local-container

文件路径中的斜杠不一样。

回溯代码,babel-plugin-react-css-modules 和 loader-utils 之间还有一层插件 generic-names,这个插件对将斜杠 \ 转成了 /

问题找到了,怎么改配置

babel-plugin-react-css-modules 设置 scope 规则的配置项 generateScopedName,可以是字符串也可以是function;

css-loader 有两种方法配置scope规则,字符串形式用 localIdentName,fuction 形式用 getLocalIdent;

两者都支持 function 自定义 scope 处理方式。

在 babel-plugin-react-css-modules 的 一个 Issue 中提到 Mac 没有异常,只有 Window 异常。

在 windows 和 mac 上做了验证,跟 Issue 一样,Mac 没问题,并且 mac 上生成的哈希值与 babel-plugin-react-css-modules 插件生成的哈希值是一样的。

于是,问题变成了 css-loader 在 windows 和 mac 上生成的哈希值不一致。解决这个问题,上面的问题也就迎刃而解了。

mac 系统文件路径斜杠是 / , 而 windows 是 \, 斜杠不一致导致生成的哈希值不一致。

在css-loader 的 getLocalIdent 配置项中,利用 generic-names 插件做 scope 转换,即可解决问题。

修改 next.config.js 中 css-loader 的配置:

const path = require('path');
const genericNames = require('generic-names');

const generate = genericNames('[local]__[hash:base64:5]', {
    context: process.cwd()
});

const generateScopedName = (localName, filePath) => {
    var relativePath = path.relative(process.cwd(), filePath);
    return generate(localName, relativePath);
};

module.exports = withSass({
    // 开启css模块化
    cssModules: true,
    cssLoaderOptions: {
        importLoaders: 1,
        // scoped class 格式
        // localIdentName: "[local]__[hash:base64:5]",
        getLocalIdent: (context, localIdentName, localName) => {
            return generateScopedName(localName, context.resourcePath)
        }
    }
});

大功告成~