create-react-app 之 使用 @craco/craco 和 craco-less 实现支持 css-modules 的解决方案
使用 CRA 脚手架创建的项目,如果想要修改编译配置,通常可能会选择
npm run eject
弹出配置后魔改。但是,eject 是不可逆操作,弹出配置后,你将无法跟随官方的脚步去升级项目的react-script
版本。
如果想要无 eject 重写 CRA 配置,一般可以有以下几种方式:
- 通过 CRA 官方支持的
--scripts-version
参数,创建项目时使用自己重写过的react-scripts
包- 使用
react-app-rewired + customize-cra
组合覆盖配置- 使用
craco
覆盖配置
在 AntDesign 官方文档中,已经推荐使用 craco
来覆写相关 webpack 配置;
# 安装 @craco/craco
yarn add @craco/craco
由于 CRA 脚手架默认不支持 less
,所以需要通过拓展来实现:
# 安装 craco-less 包,支持覆写 webpack loader 相关配置
yarn add craco-less -D
# 同时需要安装 less 和 less-loader
yarn add less less-loader -D
项目根目录下新建一个 craco.config.js
文件:
const CracoLessPlugin = require("craco-less");
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {},
javascriptEnabled: true,
},
},
},
},
],
};
问题来了:如果要支持 css-modules ,该如何处理?
在实践时,按照官方提供的文档,没法实现支持 css-modules
的功能,官方文档提供的配置方法如下:
const CracoLessPlugin = require("craco-less");
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
cssLoaderOptions: {
modules: { localIdentName: "[local]_[hash:base64:5]" },
},
},
},
],
};
如果需要解决这个问题,这里有两个方案:
方案一:继续使用 craco-less
来进行配置,但是需要新增一个配置选项,配置代码如下所示:【推荐】
// craco.config.js
const CracoLessPlugin = require("craco-less");
const lessModuleRegex = /\.module\.less$/;
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { "@primary-color": "#1DA57A" },
javascriptEnabled: true,
},
},
// A callback function that receives two arguments: the webpack rule, and the context. You must return an updated rule object.
modifyLessRule: (lessRule, context) => {
lessRule.test = lessModuleRegex;
lessRule.exclude = /node_modules|antd\.css/;
return lessRule;
},
// Passing an options object to configure the css-loaders
cssLoaderOptions: {
modules: { localIdentName: "[local]_[hash:base64:5]" },
},
},
},
],
};
方案二:不再使用 craco-less
,改用 craco-antd
;如果在项目中使用了 AntDesign
的UI库,那么 craco-antd
能让你更容易去使用它们。
This is a craco plugin that makes it easy to use the Ant Design UI library with create-react-app version >= 2.
实际上,craco-antd
包括了对 less 的支持,是需要依赖 craco-less
的;如果安装 craco-antd
则不需要单独安装 craco-less
了
如果依赖的版本如下所示:
“craco-antd”: “^1.18.0”,
“@craco/craco”: “^5.6.4”,
“craco-less”: “^1.17.0”,
可以参考如下配置:
const CracoAntDesignPlugin = require("craco-antd");
const lessModuleRegex = /\.module\.less$/;
module.exports = {
plugins: [
{
plugin: CracoAntDesignPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true,
sourceMap: false,
},
},
// 支持 css-modules
cssLoaderOptions: {
modules: {
getLocalIdent: (context, localIdentName, localName, options) => {
if (context.resourcePath.includes("node_modules")) {
return localName;
}
return getCSSModuleLocalIdent(
context,
localIdentName,
localName,
options
);
},
},
},
// 这个是按需导入
babelPluginImportOptions: {
libraryName: "antd",
libraryDirectory: "es",
style: true,
},
},
},
],
};
如果直接安装最新版本的包,可以参考下面的代码:
const CracoAntDesignPlugin = require("craco-antd");
const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent");
const lessModuleRegex = /\.module\.less$/;
module.exports = {
plugins: [
{
plugin: CracoAntDesignPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true,
sourceMap: false,
},
},
cssLoaderOptions: {
modules: {
// 通过这个生成的 classname 结构如:ComponentName_className_hash:5
getLocalIdent: (context, localIdentName, localName, options) => {
if (context.resourcePath.includes("node_modules")) {
return localName;
}
return getCSSModuleLocalIdent(
context,
localIdentName,
localName,
options
);
},
},
},
modifyLessRule: (lessRule, context) => {
lessRule.test = lessModuleRegex;
lessRule.exclude = /node_modules|antd\.css/;
return lessRule;
},
babelPluginImportOptions: {
libraryName: "antd",
libraryDirectory: "es",
style: true,
},
},
},
],
};
实际上,这个解决方案与方案一是一样的,如果 @craco/craco > 6.0.0
,按照作者文档提供的配置方式,还是无法实现支持 css-modules
的,初步的怀疑就是最新版本的 craco
存在兼容问题及一些BUG,由于作者精力不够,维护的不是十分频繁,可能在某一个版本会修复该问题;
当然,也还可以同时使用 craco-less
和 craco-antd
,两者配合使用也是可以的(建议调整 @craco/craco
版本低于 6.0.0
),参考代码如下:
const CracoAntDesignPlugin = require("craco-antd");
const CracoLessPlugin = require('craco-less');
module.exports={
plugins: [
{
plugin: CracoAntDesignPlugin,
},
{
plugin: CracoLessPlugin,
options: {
modifyLessRule: function(lessRule, _context) {
lessRule.test = /\.(module)\.(less)$/;
lessRule.exclude = /node_modules/;
return lessRule;
},
cssLoaderOptions: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
}
},
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true,
},
}
},
},
]
}