概述
这个问题在网上有许多解答,但都不外乎两种:一种方式是使用 craco ,另一种就是通过 npm run eject 的方式弹出 webpack 的配置信息,在其中进行修改。
本文主要描述后者,以及我在配置过程中的思考与收获。
具体做法
安装 less 支持
安装 less 与 less-loader ,以支持 webpack 中对 *.less 文件的解析。
npm install less less-loader
弹出配置信息
使用 npm run eject 弹出项目的默认配置信息。
注意该操作为不可逆操作,且弹出配置信息前不允许有
git未提交或暂存的文件
修改配置文件
首先找到 /config/webpack.config.js ,其使用 module.exports 导出一个函数,该函数接收一个字符串 development 或 production 表示当前环境为开发环境或生产环境。
(......)
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
(......)
该函数将返回一个配置对象,用于 webpack 的配置。找到该配置对象的 module.rules.oneOf 部分。这里需要解释一下,该配置对象中的 oneOf 数组,其官方文档中解释为:
An array of
Rulesfrom which only the first matching Rule is used when the Rule matches.
即不需要对一种文件,把所有的 loader 都尝试匹配,只需要匹配第一个符合条件的 loader 即可。提高了 loader 的匹配效率。
在 module.rules.oneOf 中,找到关于 sass 的 loader 配置信息(有两个,第一个是匹配 *.sass 或 *.scss 文件的,一个是匹配 *.module.sass 或 *.module.scss 文件的):
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
在这段 Rules 下,添加我们关于 *.less 文件的 loader 配置:
{
test: lessRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader'
),
},
其中 lessRegex 需要被事先定义,其为匹配 *.less 文件名的正则表达式:
const lessRegex = /\.less$/;
说明
在添加的这段配置信息中, test 属性负责检查文件名是否符合 lessRegex ,即我们所定义的 /\.less$/,若匹配该正则表达式则使用 use 属性(其为一个数组)中定义的 loader 对该文件进行处理。在这里我们并没有直接定义其为
[
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'postcss-loader' },
{ loader: 'resolve-url-loader' },
{ loader: 'less-loader' }
]
而是使用了 getStyleLoaders 这一函数。该函数接收两个参数,第一个参数是提交给 css-loader 的 options 对象(详见下文),第二个参数是我们希望使用的 loader。
该函数负责生成一个 loader 配置的数组交给配置对象中的 use 属性使用。在生产环境中,其第一个 loader 为 MiniCssExtractPlugin.loader ,在开发环境中则为 style-loader 。数组中其余的 loader 依次为 css-loader , postcss-loader , resolve-url-loader ,最后才是我们在第二个参数中传入的 loader。因此我们可以通过向函数的第二个参数传入 'less-loader' ,指定我们需要的最后一个 loader 为 less-loader 。
关于这些 loader ,前两个不必赘述, postcss-loader 可以负责处理浏览器前缀,压缩 CSS 等, resolve-url-loader 则可以解决 CSS 中,因为使用 @import 造成的资源 url 链接错误问题。
将目光放回我们刚刚添加的配置信息,我们对 use 属性传入了这样的内容:
getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader'
),
getStyleLoaders 的第二个参数 'less-loader' 的含义我们已经了然。对于第一个参数,我们该如何理解呢?通过查找该函数的源码(也在同一个文件,即 /config//webpack.config.js )下,可以看见:
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
//......
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
//......
由此可知,第一个参数会被当做 css-loader 的 options 对象。而关于该对象,我们在其中配置的三个属性( importLoaders , sourceMap , modules ),皆可以在官方文档中查得其含义.
对于 importLoaders :
The option
importLoadersallows you to configure how many loaders beforecss-loadershould be applied to@imported resources and CSS modules/ICSS imports.
即 importLoaders 选项允许你配置在 css-loader 之前有多少 loader 应用于 @import 的资源与 CSS 模块/ICSS 导入。在这里我们配置为 3 ,因为在 css-loader 之前有 3 个 loader 需要被应用——在 getStyleLoaders 函数中添加的 postcss-loader , resolve-url-loader ,以及我们传入的 less-loader 。
对于 sourcemap :
Default: depends on the
compiler.devtoolvalueBy default generation of source maps depends on the
devtooloption. All values enable source map generation exceptevalandfalsevalue.
即该选项控制是否启用 sourse map 。
对于 modules :
Default:
undefinedAllows to enable/disable CSS Modules or ICSS and setup configuration:
undefined- enable CSS modules for all files matching/.module.\w+$/i.test(filename)and/.icss.\w+$/i.test(filename)regexp.true- enable CSS modules for all files.false- disables CSS Modules for all files.string- disables CSS Modules for all files and set themodeoption, more information you can read hereobject- enable CSS modules for all files, ifmodules.autooption is not specified, otherwise themodules.autooption will determine whether if it is CSS modules or not, more information you can read here
而我们刚刚设置的 modules 为 { getLocalIdent: getCSSModuleLocalIdent } ,继续查看官方文档,可查得:
getLocalIdentType:
type getLocalIdent = ( context: LoaderContext, localIdentName: string, localName: string ) => string;Default:
undefinedAllows to specify a function to generate the classname. By default we use built-in function to generate a classname. If the custom function returns
nullorundefined, we fallback to the built-in function to generate the classname.
即 getLocalIdent 指定一个自定义函数,该函数用以生成独一无二的类名,以此实现 CSS 的模块化。这里我们传入了 getCSSModuleLocalIdent 。其声明于同一文件(还是 /config/webpack.config.js )下:
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
其定义位于 react-dev-utils/getCSSModuleLocalIdent 下,该函数生成类名的规则为:
- 若文件名符合
/index\.module\.(css|scss|sass)$/,则取其所在文件夹名字,否则取其文件名字,定义为fileNameOrFolder;(由于 React 尚未提供对 less 的支持,所以该函数此处的正则表达式仅考虑了 CSS 与 SASS 、 SCSS 三种后缀,即对于*.less文件,此处fileNameOrFolder永远为文件名) - 根据文件路径和原本类名(
className)生成一个 5 位的哈希值hash; - 生成形如
${fileNameOrFolder}_${className}__${hash}的独一无二的类名,生成过程中将所有.module字段去除,并将所有.替换为_;
举例来说,位于 Mycomponent.module.less 下的 MyClass 类生成的类名即为 MyComponent_MyClass_[hash] , 而位于 MyFolder/MyComponent.module.css 下的 MyClass 类则会生成 MyFolder_MyClass__[hash] 的类名。
因此,将目光放回刚刚的 modules 属性,我们将其指定为 { getLocalIdent: getCSSModuleLocalIdent } ,即意在使用 React 提供的 CSS 模块的类名生成工具函数,来生成 less 文件中的类名。同时,我们没有指定 modules.auto 属性,因此 css-loader 会对其所有应用的文件启用 CSS 模块特性。
总结
本文主要描述了对 create-react-app 项目进行修改,对其添加 less 支持的一种方式。
该方式首先使用 npm run eject 弹出配置信息后,更改 /config/webpack.config.js 文件,在其导出函数的返回对象中的 module.rules.oneOf 属性中添加一个 Rule 对象,以配置对 *.less 文件的解析以及需要对其应用的 loader 。