background

For projects created using CRA scaffolding, if you want to modify the build configuration, you might normally select NPM Run eject to pop up after the configuration. However, eject is an irreversible operation, and after eject configuration, you won’t be able to follow the official steps of upgrading the react-Script version of your project.

If you want to override the CRA configuration eject free, there are several approaches that are currently ripe for use

  1. Officially supported by CRA--scripts-versionParameter to create the project using your react-scripts package
  2. Override the configuration using the React-app-rewired + customize-cra combination
  3. Override configuration using crACO

The second way is a little more complicated than the third way. I have experienced it very much and we have noticed that the latest AntDesign4 official has also started to recommend craco. So what are we waiting for?

The configuration steps

  1. First, usecreate-react-appCreate a project, which we’ll call heremy-project
npx create-react-app my-project
Copy the code

  1. Go to the project directory and install the basic dependencies
yarn add antd @craco/craco craco-less @babel/plugin-proposal-decorators babel-plugin-import -D
Copy the code

3. Modify the scripts in package.json

{
  "scripts":{
    "start": "set PORT=5000 && craco start FAST_REFRESH=true",
    "build": "set GENERATE_SOURCEMAP=false && craco build",
    "analyzer": "env NODE_ENV=production BUILD_ANALYZER=true yarn start",
    "test": "craco test"
  }
} 
Copy the code

4. Create craco.config.js file in the project root directory

/* craco.config.js */

module.exports = {
  ...
}
Copy the code

Several environment variables are used:

PORT Startup PORT

GENERATE_SOURCEMAP Whether to generate sourceMap when packaging

BUILD_ANALYZER Output compilation analysis in file mode

The complete craco Configuration file structure can be found in the official crACO document: Configuration Overview.

Extend the Babel configuration

Although you can define a Babel configuration in configure, craco also provides a quick way to write it separately. Add @babel/preset-env as follows:

/* craco.config.js */ module.exports = { babel: { presets: [ [ '@babel/preset-env', { modules: }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} 'entry', / / browserslist environment does not support all gasket import / / / / https://babeljs.io/docs/en/babel-preset-env#usebuiltins https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md corejs: {version: 3, {version: 3, {version: 3,},},], plugins: [// config babel-plugin-import ['import', {libraryName: 'antd', libraryDirectory: 'es', style: True}, 'antd'], // configure the parser ["@babel/plugin-proposal-decorators", {" Legacy ": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }], ["babel-plugin-styled-components", { "displayName": true }] ], loaderOptions: {}, loaderOptions: (babelLoaderOptions, { env, paths }) => { return babelLoaderOptions; }}},Copy the code

Check module compilation

new WebpackBar({ profile: true }),
new CircularDependencyPlugin({
  exclude: /node_modules/,
  include: /src/,
  failOnError: true,
  allowAsyncCycles: false,
  cwd: process.cwd()
})
Copy the code

Observing the Packing Progress

const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin') module export = { webpack: { plugins: [/ / check the progress of the packaging new SimpleProgressWebpackPlugin ()]}}Copy the code

Modify the package output directory

module.exports = { webpack: { configure: (webpackConfig, { env, paths }) => { // paths.appPath='public' paths.appBuild = 'dist' webpackConfig.output = { ... webpackConfig.output, // ... { // filename: whenDev(() => 'static/js/bundle.js', 'static/js/[name].js'), // chunkFilename: 'static/js/[name].js' //}, path: path.resolve(__dirname, 'dist'), // modify publicPath: '/' } return webpackConfig } } }Copy the code

If you feel cumbersome, you can also directly use WebPack to configure overlay, webpackConfig information about this:

Hot update hot-loader extension

How to avoid frequent refreshes when starting hot updates

React-hot-reload and Craco quick-refresh plugin are also available for react-hot-reload and Craco quick-refresh

React-hot-loader configuration is as follows (portal)

Yarn add@hot-loader /react-dom module.exports = {// exports... resolve: { alias: { 'react-dom': '@hot-loader/react-dom', }, }, }; Step2: Import {hot} from 'react-hot-loader/root' function App {return (<div>ceshi</div>)} export default hot(App)Copy the code

Craco-plugin-react-hot-reload configuration

/* craco.config.js */ const reactHotReloadPlugin = require('craco-plugin-react-hot-reload') const reactHotReloadPlugin =  require('craco-plugin-react-hot-reload') module.exports = { plugins: [{ plugin: reactHotReloadPlugin }] }Copy the code

The craco-fast-refresh configuration is as follows (portal)

This is a newly discovered craco plugin that works much better than React-Hot-Loader, has zero configuration, does not require any changes to the project code, and is said to perform better.

Step1: /* craco. Config. js */ const FastRefreshCracoPlugin = require('craco-fast-refresh') module.exports = () => {return { plugins: [{ plugin: FastRefreshCracoPlugin }], }; }; Import React from 'React' import {hot} from 'react-hot-loader' const App = () => <div>Hello World! </div> export default hot(module)(App)Copy the code

Antd Custom topic configuration

Antd theme color can be configured for the following options

Combining lessOptions

Step1: run yarn add craco-less step2: introduce const CracoLessPlugin = require('craco-less') step3: configure {plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { modifyVars: { '@primary-color': '#1DA57A' }, javascriptEnabled: true } } } }Copy the code

Craco also provides specialized plugins to handle the antD integration (portal) configuration differently

Craco custom support

craco-antd includes:

  • Less (provided by craco-less)
  • babel-plugin-import to only import the required CSS, instead of everything
  • An easy way to customize the theme. Set your custom variables in ./antd.customize.less
Step1: yarn add craco-antd step2: const CracoAntDesignPlugin = require('craco-antd') step3 {plugin: CracoAntDesignPlugin, options: { customizeTheme: { '@primary-color': '#FF061C' } } }Copy the code

If you want to separate the customizeTheme, take the following solution

Step1: create an antd.customize.less file --------- @primary-color: #FF061C; --------- step2: Read mode {plugin: CracoAntDesignPlugin, options: {customizeThemeLessPath: path.join(__dirname,"antd.customize.less")} }Copy the code

Relatively speaking, it is more concise to use and recommended to use.

conclusion

It is indeed possible to customize all crA build configurations without eject pop-up configurations, as explained in detail earlier, if you need to (portal). So in future coding, you can build your WebPack configuration in either of these ways.

Note:

**configure configuration and **

The craco configuration is mutually exclusive. Use it with caution

Below is the complete configuration of craco.config.js that I have sorted out, and the corresponding demo is easy to refer to

Craco also provides a number of other plugins that can be added on a case-specific basis (portal)

/* craco.config.js */
/**
 * TODO: 区分环境 —— NODE_ENV
 * - whenDev ☞ process.env.NODE_ENV === 'development'
 * - whenTest ☞ process.env.NODE_ENV === 'test'
 * - whenProd ☞ process.env.NODE_ENV === 'production'
 */
const {
  when, whenDev, whenProd, whenTest, ESLINT_MODES, POSTCSS_MODES
} = require('@craco/craco')
const webpack = require('webpack')
const CracoLessPlugin = require('craco-less')
const CracoAntDesignPlugin = require('craco-antd')
const CracoVtkPlugin = require('craco-vtk')
const WebpackBar = require('webpackbar')
const CircularDependencyPlugin = require('circular-dependency-plugin')
const FastRefreshCracoPlugin = require('craco-fast-refresh')
const TerserPlugin = require('terser-webpack-plugin')
const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin')
const {
  BundleAnalyzerPlugin
} = require('webpack-bundle-analyzer')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const DashboardPlugin = require('webpack-dashboard/plugin')
const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin')

const path = require('path')

// 判断编译环境是否为生产
const isBuildAnalyzer = process.env.BUILD_ANALYZER === 'true'

const pathResolve = pathUrl => path.join(__dirname, pathUrl)

module.exports = {
  webpack: {
    // 别名配置
    alias: {
      '@': pathResolve('.'),
      src: pathResolve('src'),
      assets: pathResolve('src/assets'),
      common: pathResolve('src/common'),
      components: pathResolve('src/components'),
      hooks: pathResolve('src/hooks'),
      pages: pathResolve('src/pages'),
      store: pathResolve('src/store'),
      utils: pathResolve('src/utils')
        // 此处是一个示例,实际可根据各自需求配置
    },
    plugins: [
      // webpack构建进度条
      new WebpackBar({
        profile: true
      }),
      new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
      // 查看打包的进度
      new SimpleProgressWebpackPlugin(),
      // 时间转换工具采取day替换moment
      new AntdDayjsWebpackPlugin(),
      // // 新增模块循环依赖检测插件
      ...whenDev(
        () => [
          new CircularDependencyPlugin({
            exclude: /node_modules/,
            include: /src/,
            failOnError: true,
            allowAsyncCycles: false,
            cwd: process.cwd()
          }),
          // webpack-dev-server 强化插件
          new DashboardPlugin(),
          new webpack.HotModuleReplacementPlugin()
        ], []
      ),
      /**
       * 编译产物分析
       *  - https://www.npmjs.com/package/webpack-bundle-analyzer
       * 新增打包产物分析插件
       */
      ...when(
        isBuildAnalyzer, () => [
          new BundleAnalyzerPlugin({
            analyzerMode: 'static', // html 文件方式输出编译分析
            openAnalyzer: false,
            reportFilename: path.resolve(__dirname, `analyzer/index.html`)
          })
        ], []
      ),
      ...whenProd(
        () => [
          // new TerserPlugin({
          //   // sourceMap: true, // Must be set to true if using source-maps in production
          //   terserOptions: {
          //     ecma: undefined,
          //     parse: {},
          //     compress: {
          //       warnings: false,
          //       drop_console: true, // 生产环境下移除控制台所有的内容
          //       drop_debugger: true, // 移除断点
          //       pure_funcs: ['console.log'] // 生产环境下移除console
          //     }
          //   }
          // }),
          // 打压缩包
          new CompressionWebpackPlugin({
            algorithm: 'gzip',
            test: new RegExp('\\.(' + ['js', 'css'].join('|') + ')/div>),
            threshold: 1024,
            minRatio: 0.8
          })
        ], []
      )
    ],
    //抽离公用模块
    optimization: {
      splitChunks: {
        cacheGroups: {
          commons: {
            chunks: 'initial',
            minChunks: 2,
            maxInitialRequests: 5,
            minSize: 0
          },
          vendor: {
            test: /node_modules/,
            chunks: 'initial',
            name: 'vendor',
            priority: 10,
            enforce: true
          }
        }
      }
    },
    /**
     * 重写 webpack 任意配置
     *  - configure 能够重写 webpack 相关的所有配置,但是,仍然推荐你优先阅读 craco 提供的快捷配置,把解决不了的配置放到 configure 里解决;
     *  - 这里选择配置为函数,与直接定义 configure 对象方式互斥;
     */
    configure: (webpackConfig, {
      env, paths
    }) => {
      // paths.appPath='public'
      paths.appBuild = 'dist' // 配合输出打包修改文件目录
        // webpackConfig中可以解构出你想要的参数比如mode、devtool、entry等等,更多信息请查看webpackConfig.json文件
        /**
         * 修改 output
         */
      webpackConfig.output = {
          ...webpackConfig.output,
            // ...{
            //   filename: whenDev(() => 'static/js/bundle.js', 'static/js/[name].js'),
            //   chunkFilename: 'static/js/[name].js'
            // },
            path: path.resolve(__dirname, 'dist'), // 修改输出文件目录
            publicPath: '/'
        }
        /**
         * webpack split chunks
         */
        // webpackConfig.optimization.splitChunks = {
        //   ...webpackConfig.optimization.splitChunks,
        //   ...{
        //     chunks: 'all',
        //     name: true
        //   }
        // }
        // 返回重写后的新配置
      return webpackConfig
    }
  },
  babel: {
    presets: [],
    plugins: [
      // AntDesign 按需加载
      ['import', {
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: true
      }, 'antd'],
      ['@babel/plugin-proposal-decorators', {
        legacy: true
      }] // 用来支持装饰器
    ],
    loaderOptions: {},
    loaderOptions: (babelLoaderOptions, {
      env, paths
    }) => {
      return babelLoaderOptions
    }
  },
  /**
   * 新增 craco 提供的 plugin
   */
  plugins: [
    // 热更新
    ...whenDev(
      () => [{
        plugin: FastRefreshCracoPlugin
      }, {
        plugin: CracoVtkPlugin()
      }, {
        plugin: new AntdDayjsWebpackPlugin()
      }], []
    ),
    // 方案1、配置Antd主题less
    // {
    //   plugin: CracoLessPlugin,
    //   options: {
    //     lessLoaderOptions: {
    //       lessOptions: {
    //         modifyVars: { '@primary-color': '#1DA57A' },
    //         javascriptEnabled: true
    //       }
    //     }
    //   }
    // },
    // 方案2、配置Antd主题
    // {
    //   plugin: CracoAntDesignPlugin,
    //   options: {
    //     customizeTheme: {
    //       '@primary-color': '#FF061C'
    //     }
    //   }
    // },
    // 方案3、配置Antd主题
    {
      plugin: CracoAntDesignPlugin,
      options: {
        customizeThemeLessPath: path.join(
          __dirname,
          "antd.customize.less"
        ),
      },
    },
  ],
  devServer: {
    port: 9000,
    proxy: {
      '/api': {
        target: 'https://placeholder.com/',
        changeOrigin: true,
        secure: false,
        xfwd: false,
      }
    }
  }
}
Copy the code

We can also take a look at what apis have been exposed to us

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; },
        loaderOptions: { /* Any eslint-loader configuration options: https://github.com/webpack-contrib/eslint-loader. */ },
        loaderOptions: (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: [],
        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: {}
        }
    ]
};
Copy the code

So much information to use is not very cool, want to explore the action to progress together

reference

  • Craco configuration
  • Less-loader official document
  • Ant Design official documentation
  • Craco practice