背景
最近有从 vue 转 react 的需求,刚学 react 就想着用官方提供 create-react-app 一把梭了。写到 css module 这一块就特别蛋疼(相对于 vue 来讲确实麻烦不少),然后从社区里得知 babel-plugin-react-css-modules 可以提供一些语法糖。由于不想 eject webpack 配置,就配合着 react-app-rewired,customize-cra 一顿折腾。
使用的版本信息和配置如下:
- cra: 3.4.1
- css-loader: 4.3.0
- babel-plugin-react-css-modules: 5.2.6
// config-override.js
const {
override,
addWebpackAlias,
addBabelPlugin,
adjustStyleLoaders,
} = require("customize-cra");
const path = require("path");
module.exports = override(
addWebpackAlias({
"@": path.join(__dirname, "src"),
}),
addBabelPlugin([
"react-css-modules",
{
exclude: "node_modules",
context: __dirname,
webpackHotModuleReloading: true,
generateScopedName: "[name]_[local]_[hash:base64:5]",
filetypes: {
".scss": {
syntax: "postcss-scss",
},
},
},
]),
adjustStyleLoaders(({ use: [, css] }) => {
if (css.options.modules) {
css.options.modules = {
localIdentName: "[name]_[local]_[hash:base64:5]",
localIdentContext: __dirname,
};
}
})
);
查看了下页面,css 并没有应用在页面上,搜了一下,基本都是围绕这 generateScopedName 和 localIdentName 以及 context 来解决问题。但是我基本都提供了,看来只能自己 debug 了。
排查
- 定位 DOM 元素,发现 styleName 有进行转换,格式也匹配提供
generateScopeName,那么babel-plugin-react-css-modules的配置应该生效了 - 定位生成的 CSS,查看类名,也有进行装换,格式也正确,那么
css-loader的配置也应该生效了 - 对比两者生成的名字,居然 hash 部分是不一致的。说明他们是独立生成 hash 的(巨坑,一开始没仔细看 hash 值)
- 将
localIdentName和generateScopedName更改为[path]_[name]_[local]再次运行项目,发现 css 正常应用了。那么问题就出在 hash 值的生成逻辑了 - 根据这两个字段,分别在对应源码里检索使用的位置,分别定位到
css-loader/utils的defaultGetLocalIdent。默认情况下babel-plugin-react-css-modules使用generic-names来处理 hash 值,继续定位生成代码位置 - 对比双方源码
啊,这。。。双方默认拼接 content 的逻辑不一样,中间拼接用
\x00 和 + 号的区别...
- 打断点验证一下,虽然最后都是用
loader-utils提供的方法进行处理,但是两者提供的 content 不一样,导致最后的 hash 不一样 - 给
css-loader提供getLocalIdent方法,让它和babel-plugin-react-css-modules的规则一致,即可解决问题
最终代码如下
// config-override.js
const {
override,
addWebpackAlias,
addBabelPlugin,
adjustStyleLoaders,
} = require("customize-cra");
const path = require("path");
const genericName = require("generic-names");
const generateRule = "[name]_[local]_[hash:base64:5]";
const generateIdentName = genericName(generateRule, {
context: __dirname,
});
module.exports = override(
addWebpackAlias({
"@": path.join(__dirname, "src"),
}),
addBabelPlugin([
"react-css-modules",
{
exclude: "node_modules",
context: __dirname,
webpackHotModuleReloading: true,
generateScopedName: generateRule,
filetypes: {
".scss": {
syntax: "postcss-scss",
},
},
},
]),
adjustStyleLoaders(({ use: [, css] }) => {
if (css.options.modules) {
css.options.modules = {
localIdentContext: __dirname,
getLocalIdent: (loaderContext, localIdentName, localName, options) => {
const { resourcePath } = loaderContext;
return generateIdentName(localName, resourcePath);
},
};
}
})
);
后续
我寻思着为啥同样的配置别人可以使用,轮到我就不行了,于是查看了下 css-loader 的迭代历史,emmm。
所以 css-loader v3.6.0 以前的版本两者配合着应该是能正常使用的。
总得来说,还是要有独立的 debug 能力才比较好解决问题,npm 包迭代比较快,网上提供的解决方案相对来讲比较滞后,遇到这个问题,建议还是自己 debug 一下。