使用CRACO进行辅助配置

CRACO 是除 customize-cra 以外的另一个更加优秀的辅助配置层选择。CRACO 适配于 Create React App 4.0 以上的版本,如果要在项目里使用,可以直接执行以下两个命令中的一个。

npm install @craco/craco --save-dev
yarn add @craco/craco --dev

在项目中添加 CRACO 以后,还需要手动在项目跟目录里添加一个名为 craco.config.js 的文件。并且同时还需要修改 package.jsonscripts 区段的内容,由 craco 代替之前的 react-scripts 完成项目的启动、编译等工作。更改以后的 package.json 文件的 scripts 区段至少是以下这个样子。

{
  "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test"
  }
}

CRACO 主要是通过 craco.config.js 文件来完成 React 应用的扩展配置的,而这个文件中则主要是导出了一个配置对象。以下是这个配置文件的全貌,在进行实际配置的时候可以从中选择所需要的内容进行配置。

const { when, whenDev, whenProd, whenTest, ESLINT_MODES, POSTCSS_MODES } = require('@craco/craco');

module.exports = {
  reactScriptsVersion: 'react-scripts' /* (default value) */,
  style: {
    modules: {
      localIdentName: ''
    },
    css: {
      loaderOptions: {
        /* Any css-loader configuration options: https://github.com/webpack-contrib/css-loader. */
      },
      loaderOptions: (cssLoaderOptions, { env, paths }) => {
        return cssLoaderOptions;
      }
    },
    sass: {
      loaderOptions: {
        /* Any sass-loader configuration options: https://github.com/webpack-contrib/sass-loader. */
      },
      loaderOptions: (sassLoaderOptions, { env, paths }) => {
        return sassLoaderOptions;
      }
    },
    postcss: {
      mode: 'extends' /* (default value) */ || 'file',
      plugins: [],
      env: {
        autoprefixer: {
          /* Any autoprefixer options: https://github.com/postcss/autoprefixer#options */
        },
        stage: 3,
        /* Any valid stages: https://cssdb.org/#staging-process. */ features: {
          /* Any CSS features: https://preset-env.cssdb.org/features. */
        }
      },
      loaderOptions: {
        /* Any postcss-loader configuration options: https://github.com/postcss/postcss-loader. */
      },
      loaderOptions: (postcssLoaderOptions, { env, paths }) => {
        return postcssLoaderOptions;
      }
    }
  },
  eslint: {
    enable: true /* (default value) */,
    mode: 'extends' /* (default value) */ || 'file',
    configure: {
      /* Any eslint configuration options: https://eslint.org/docs/user-guide/configuring */
    },
    configure: (eslintConfig, { env, paths }) => {
      return eslintConfig;
    },
    pluginOptions: {
      /* Any eslint plugin configuration options: https://github.com/webpack-contrib/eslint-webpack-plugin#options. */
    },
    pluginOptions: (eslintOptions, { env, paths }) => {
      return eslintOptions;
    }
  },
  babel: {
    presets: [],
    plugins: [],
    loaderOptions: {
      /* Any babel-loader configuration options: https://github.com/babel/babel-loader. */
    },
    loaderOptions: (babelLoaderOptions, { env, paths }) => {
      return babelLoaderOptions;
    }
  },
  typescript: {
    enableTypeChecking: true /* (default value)  */
  },
  webpack: {
    alias: {},
    plugins: {
      add: [] /* An array of plugins */,
      remove: []
      /* An array of plugin constructor's names (i.e. "StyleLintPlugin", "ESLintWebpackPlugin" ) */
    },
    configure: {
      /* Any webpack configuration options: https://webpack.js.org/configuration */
    },
    configure: (webpackConfig, { env, paths }) => {
      return webpackConfig;
    }
  },
  jest: {
    babel: {
      addPresets: true /* (default value) */,
      addPlugins: true /* (default value) */
    },
    configure: {
      /* Any Jest configuration options: https://jestjs.io/docs/en/configuration. */
    },
    configure: (jestConfig, { env, paths, resolve, rootDir }) => {
      return jestConfig;
    }
  },
  devServer: {
    /* Any devServer configuration options: https://webpack.js.org/configuration/dev-server/#devserver. */
  },
  devServer: (devServerConfig, { env, paths, proxy, allowedHost }) => {
    return devServerConfig;
  },
  plugins: [
    {
      plugin: {
        overrideCracoConfig: ({ cracoConfig, pluginOptions, context: { env, paths } }) => {
          return cracoConfig;
        },
        overrideWebpackConfig: ({ webpackConfig, cracoConfig, pluginOptions, context: { env, paths } }) => {
          return webpackConfig;
        },
        overrideDevServerConfig: ({
          devServerConfig,
          cracoConfig,
          pluginOptions,
          context: { env, paths, proxy, allowedHost }
        }) => {
          return devServerConfig;
        },
        overrideJestConfig: ({ jestConfig, cracoConfig, pluginOptions, context: { env, paths, resolve, rootDir } }) => {
          return jestConfig;
        }
      },
      options: {}
    }
  ]
};

在默认情况下,CRACO 将会使用内容扩展(extends)来修改配置选项,但是在一些提供了 mode 的配置项中,还可以通过将 mode 改成 file 来提供一个配置文件替换配置项的全部配置值。

为了能够让一套配置文件适应多种环境和条件,CRACO 还提供了一系列的辅助函数来对当前的运行环境和自定义条件进行判断,并在运行过程中采用不同的配置值。这套函数的调用方式比较统一,都是以下形式。

when(condition, fact, [unmetValue]);
whenDev(fact, [unmetValue]);

其中 fact 表示给定的条件为真时配置项所取得值,而 unmetValue 则表示给定条件为假时配置项所取得的值。这一系列的函数都是以 when 开头的,主要会实现以下功能。

  • when,使用自定义条件进行选择。
  • whenDev,判断应用的运行环境是否是 DEV 环境。
  • whenProd,判断应用的运行环境是否是 PROD 环境。
  • whenTest,判断应用的运行环境是否是 TEST 环境。

craco.config.js 中除了可以导出一个对象以外,还可以导出一个函数和一个 Promise 对象或者是一个异步函数,CRACO 会将其转换为所需要的内容。并且使用函数或者 Promise 对象还可以实现更加动态的配置效果。

例如我们可以这样利用 CRACO 来配置应用使用 Less 处理样式并且做一个反向代理的配置。

const CracoLessPlugin = require('craco-less');

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  },
  plugins: [
    {
      plugin: CracoLessPlugin,
      options: {
        lessLoaderOptions: {
          lessOptions: {
            javascriptEnabled: true
          }
        }
      }
    }
  ]
};

Warning

截止本文完成的时候,craco-less 插件仅能支持 5.5 版本的 CRACO,而最新版的 CRACO 已经是 6.1 版本了,所以直接使用 craco-less 会无法启动应用。这个情况目前看起来还依旧无解。