插件使用方法请参考官方文档 和 之前的文档[Next.js] 简化 CSS 开发.
本文主要讲使用 babel-plugin-react-css-modules hash 配置后 scope 样式丢失问题的排查过程与结果。
第一步,在官方文档和google上找答案
-
generateScopedName 必须和 next.config.js 中的cssLoaderOptions.localIdentName 值保持一致,没毛病;
-
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)
}
}
});
大功告成~