前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >更骚的create-react-app开发环境配置craco

更骚的create-react-app开发环境配置craco

原创
作者头像
Run丘比特
修改2020-11-20 10:04:41
7.7K0
修改2020-11-20 10:04:41
举报
文章被收录于专栏:边城浪子周刊边城浪子周刊

背景

使用 CRA 脚手架创建的项目,如果想要修改编译配置,通常可能会选择 npm run eject 弹出配置后魔改。但是,eject 是不可逆操作,弹出配置后,你将无法跟随官方的脚步去升级项目的 react-script 版本。

如果想要无 eject 重写 CRA 配置,目前成熟的是下面这几种方式

  1. 通过 CRA 官方支持的 --scripts-version 参数,创建项目时使用自己重写过的 react-scripts 包
  2. 使用 react-app-rewired + customize-cra 组合覆盖配置
  3. 使用 craco 覆盖配置

第二种方式相对第三种略复杂一些,我自己很有体会并且我们注意到最新的AntDesign4 官方也开始推荐 craco 了,那我们还等什么还不快行动起来,今天主要在这里详细讨论一下 craco 的使用,也方便大家给出更好的建议。

配置步骤

  1. 首先,使用 create-react-app 创建一个项目,这里我们命名为 my-project
代码语言:javascript
复制
npx create-react-app my-project
  1. 进入项目目录,安装基本依赖
代码语言:javascript
复制
yarn add antd @craco/craco craco-less @babel/plugin-proposal-decorators babel-plugin-import -D
  1. 3、修改 package.json 中的 scripts
代码语言:javascript
复制
{
  "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"
  }
} 
  1. 4、项目根目录创建 craco.config.js 文件
代码语言:javascript
复制
/* craco.config.js */
module.exports = {
  ...
}
  1. 上面用到了几个环境变量: PORT 启动端口 GENERATE_SOURCEMAP 打包时是否生成 sourceMap BUILD_ANALYZER 文件方式输出编译分析基础的配置到此完成了,接下来是处理各种配置的覆盖,完整的 craco.config.js 配置文件结构,可以在 craco 官方的文档中详细查询:Configuration Overview 。

扩展 babel 配置

虽然可以在 configure 中定义 babel 配置,但 craco 也提供了快捷的方式单独去书写,添加 @babel/preset-env 配置示例如下:

代码语言:txt
复制
/* craco.config.js */

module.exports = {
  babel: {
    presets: [
      [
        '@babel/preset-env',
        {
          modules: false, // 对ES6的模块文件不做转化,以便使用tree shaking、sideEffects等
          useBuiltIns: 'entry', // browserslist环境不支持的所有垫片都导入
          // 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, // 使用core-js@3
            proposals: true,
          },
        },
      ],
    ],
    plugins: [
    	// 配置 babel-plugin-import
    	['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }, 'antd'],
  	  // 配置解析器
       ["@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; }
    
  },
}

检测模块编译情况

代码语言:txt
复制
new WebpackBar({ profile: true }),
new CircularDependencyPlugin({
  exclude: /node_modules/,
  include: /src/,
  failOnError: true,
  allowAsyncCycles: false,
  cwd: process.cwd()
})
image.png
image.png
image.png
image.png

观察打包进度

代码语言:txt
复制
const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin')
module export = {
  webpack: {

    plugins: [
      // 查看打包的进度
      new SimpleProgressWebpackPlugin()
    ]
  }
}
gif.gif
gif.gif

修改打包输出目录

代码语言:txt
复制
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'), // 修改输出文件目录
          publicPath: '/'
      }
      return webpackConfig
    }
  }
}

如果觉得繁琐也可以直接使用webpack进行configure覆盖、webpackConfig的信息大概有这么多:

webpack (1).gif
webpack (1).gif

热更新Hot-loader扩展

启动热更新如何避免频繁刷新 常用的热更新方案 react-hot-loader、craco也帮我们提供了两种craco-plugin-react-hot-reloadcraco-fast-refresh

react-hot-loader配置如下(传送门)
代码语言:txt
复制
step1:webpack.config.js中引入别名配置解除相关警告
yarn add @hot-loader/react-dom
module.exports = {
  // ...
  resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom',
    },
  },
};
step2:注入引用App.js
import { hot } from 'react-hot-loader/root'
function App {
	return (
  	<div>ceshi</div>
 	)
}
export default hot(App)
craco-plugin-react-hot-reload配置如下(传送门)
代码语言:txt
复制
/* craco.config.js */
const reactHotReloadPlugin = require('craco-plugin-react-hot-reload')
const reactHotReloadPlugin = require('craco-plugin-react-hot-reload')
module.exports = {
  plugins: [{
    plugin: reactHotReloadPlugin
  }]
}
craco-fast-refresh 配置如下(传送门)

这是最近发现的新 craco plugin,相对于 react-hot-loader好用得多,零配置,不需要修改项目代码,据说性能也更好。

代码语言:txt
复制
step1:增加插件
/* craco.config.js */
const FastRefreshCracoPlugin = require('craco-fast-refresh')
module.exports = () => {
  return {
    plugins: [{
      plugin: FastRefreshCracoPlugin
    }],
  };
};
step2: 注入引用App.js
import React from 'react'
import { hot } from 'react-hot-loader'
const App = () => <div>Hello World!</div>

export default hot(module)(App)

Antd自定义主题配置

配置antd主题颜色可随意对以下方案就行选取

结合lessOptions
代码语言:txt
复制
step1:运行 yarn add craco-less
step2:引入 const CracoLessPlugin = require('craco-less')
step3:配置
{
  plugin: CracoLessPlugin,
  options: {
    lessLoaderOptions: {
      lessOptions: {
        modifyVars: {
          '@primary-color': '#1DA57A'
        },
        javascriptEnabled: true
      }
    }
  }
}

同时craco 也提供了专门的 plugin 来处理 antd 的集成(传送门)配置方式有区别

Craco自定义支持

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

代码语言:txt
复制
step1: yarn add craco-antd
step2: const CracoAntDesignPlugin = require('craco-antd')
step3 
{
  plugin: CracoAntDesignPlugin,
  options: {
    customizeTheme: {
      '@primary-color': '#FF061C'
    }
  }
}

针对customizeTheme 如果想单独抽离可采取如下方案

代码语言:txt
复制
step1: 新建antd.customize.less文件
---------
@primary-color: #FF061C;
---------
step2:读取模式
{
  plugin: CracoAntDesignPlugin,
  options: {
  customizeThemeLessPath: path.join(__dirname,"antd.customize.less")}
 }

相对来讲使用会更简洁一些,推荐使用。

总结

确实能够在不 eject 弹出配置的情况下,能够自定义所有的 cra 构建配置,之前进行了详细的说明,有这方面的需求可以去看看(传送门)。因此在后续的编码中,我们可以随便使用这两种方式构建自己的webpack配置。

注意:_configure配置和_craco配置会互斥谨慎使用

以下,是我整理完整的 craco.config.js 配置,相应的demo方便参照 craco 还提供一些其他 plugin具体根据实际情况自行加入(传送门)

代码语言:txt
复制
/* 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('|') + ')$'),
            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,
      }
    }
  }
}

同时我们也可以看一下官方给我们暴露了哪些Api

代码语言:txt
复制
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: {}
        }
    ]
};

这么多的信息使用起来是不是很爽,想探索的赶快行动起来共同进步啦

参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 配置步骤
    • 扩展 babel 配置
      • 检测模块编译情况
        • 观察打包进度
          • 修改打包输出目录
            • 热更新Hot-loader扩展
              • react-hot-loader配置如下(传送门)
              • craco-plugin-react-hot-reload配置如下(传送门)
              • craco-fast-refresh 配置如下(传送门)
            • Antd自定义主题配置
              • 结合lessOptions
              • Craco自定义支持
          • 总结
          • 参考
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档