一锅烩了那些React项目开发绕不过去的配置

写 React 已经有一段时间了,从开始到现在都是一路摸爬滚打过来的,踩过不少坑。虽然学习 React 已经很久了,但是和真正写还是有很大区别的,今天就从头开始记录下使用过程的点点滴滴,权且当作学习笔记吧,也希望能给将要入坑的小伙伴一点点启示吧!

前言

习惯了使用Vue开发的小伙伴,如果突然切换到React技术栈,刚开始肯定会有些手足无措,因为相比Vue的脚手架Vue-cliReact官方提供的脚手架工具create-react-app则显得没有那么友好。在Vue-cli中,如果想要扩展webpack配置,只需要在项目的根目录新建一个vue.config.js文件,在该文件中进行配置即可。但是create-react-app则没有那么简单,它把所有的webpack配置都集成在了react-scripts的依赖包中,我们无法直接修改的,官方提供了一个npm run eject命令可以执行脚本将react-scripts配置释放到本地,来让我们进行配置,但这种方式也会有缺陷,具体后面会详细说明,同时,社区也提供了一种方案,可以使用react-app-rewired包来重写webpack配置。接下来我们就详细来说明这两种方式,帮你一举扫清环境配置的各种障碍。

使用create-react-app初始化项目

  • 创建项目
npx create-react-app react-demo
  • 启动项目
cd react-demo
npm run start

至此,一个简单的react项目就创建和启动完毕了。

扒一扒create-react-app背后的东西

通过项目创建大概有小伙伴会说这比vue-cli创建vue项目简单,但是两个命令背后发生了什么应该也会有疑问,接下来我们就一点点扒开它的真实面目。

两个疑问

1、为什么使用npx创建项目?

在使用create-react-app创建项目时,通常是使用如下命令:

// 全局安装create-react-app
npm install create-react-app -g
// 使用create-react-app创建项目
create-react-app react-demo

但是我们这里为什么使用了 npx 就可以直接创建项目呢 ?
那就要从认识npx说起了。
npxnpm提供的一个命令,它可以不用全局安装包文件,就可以执行命令,在使用npx时它会先从下载一个临时包文件,等使用完成之后就会自动删除,以避免额外的占用磁盘空间。

2、执行npm run start后发生了什么?

相比很多的小伙伴大概也会跟我一样,对于执行一个命令就启动起来项目了,这背后到底发生了什么很好奇?那么现在我们就来一起一探究竟。

首先,查看项目根目录的package.json文件:

{
  "name": "react-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.1",
    "web-vitals": "^0.2.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": ["react-app", "react-app/jest"]
  },
  "browserslist": {
    "production": [">0.2%", "not dead", "not op_mini all"],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

查看package.json中的scripts配置,我们知道了,执行npm run start命令其实是执行了react-scripts start,那么react-scripts又是何方圣神呢?
其实npm执行的命令默认都会去项目的node_modules下面.bin目录下找可执行脚本。

一锅烩了那些React项目开发绕不过去的配置_第1张图片

其中react-scripts.cmdwindows系统下的可执行命令,react-scriptlinux环境下的命令。扒开文件内容我们可以看到:

@IF EXIST "%~dp0\node.exe" (
  "%~dp0\node.exe"  "%~dp0\..\react-scripts\bin\react-scripts.js" %*
) ELSE (
  @SETLOCAL
  @SET PATHEXT=%PATHEXT:;.JS;=;%
  node  "%~dp0\..\react-scripts\bin\react-scripts.js" %*
)

通过上面命令我们就知道了脚手架的webpack配置入口文件是react-scripts包中bin目录下的react-scripts.js文件。
一锅烩了那些React项目开发绕不过去的配置_第2张图片

然后在入口文件中又根据执行命令(参数)不同加载scripts目录下不同的文件。
一锅烩了那些React项目开发绕不过去的配置_第3张图片

由入口文件可知,如果执行的是npm run start就会执行scripts/start.js文件。
一锅烩了那些React项目开发绕不过去的配置_第4张图片

start.js文件中引入了config目录下webpack.config.js文件,那么这个文件就是项目脚手架中核心的webpack配置文件了。

扒到这里,大概大家就明白了,服务是如何启动的了,以及脚手架背后的webpack是如何配置的了。

那么,接下来我们就来说一说在日常项目中我们通常都怎样在脚手架的基础上自定义webpack配置的。

自定义webpack配置

通常,脚手架生成的默认配置是很难满足我们项目需要的,因此自定义配置项目的webpack是我们无法避免的。接下来我就介绍两种自定义webpack的方式。以及日常开发中常用到的配置。

一、使用eject,释放默认webpack配置

package.json文件的scripts命令中,有一个npm run eject的命令,这个是create-react-app官方提供了一个可以释放默认webpack的命令。

执行npm run eject命令会将react-scripts释放到本地项目中,可以通过修改对应的文件完成配置。同时会删除react-scripts依赖包,修改package.json中命令。
一锅烩了那些React项目开发绕不过去的配置_第5张图片

命令执行完毕以后,我们就会看到本地项目中多出了scriptsconfig两个目录文件。同时,package.json文件命令也被修改了。

"scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js"
}

此时,如果在执行启动或者打包命令,就是直接执行的scripts目录下对应的文件了。我们可以很方便的根据需要修改对应的文件配置即可。

但值得注意的是,npm run eject命令是不可逆的,即执行之后不可恢复,这就造成了如果后续我们想通过脚手架的react-scripts包增加新的特性,比如PWA支持,是不可行的,因此,请根据需要谨慎释放。

说完如何释放脚手架的webpack配置,接下来我们就来说一下如何通过释放默认配置的方式进行一些常用的自定义配置

添加less支持

  • 安装lessless-loader
npm install less less-loader
  • config/webpack.config.js文件中做如下配置:

1、添加匹配.less.module.less结尾的文件的正则
一锅烩了那些React项目开发绕不过去的配置_第6张图片

2、在modulerules中配置less-loader规则
一锅烩了那些React项目开发绕不过去的配置_第7张图片

至此,对less文件的支持已经配置好了,在项目中创建less样式文件并引入,就可以生效了。

  • 拓展

    看了上面的配置,可能有些人会有疑问:

    为什么要在配置中匹配.module.less文件,并进行单独的解析?

    这是因为在create-react-app创建的项目中,组件中的样式没有作用域隔离的概念,不像是vue中给style添加scoped属性,样式文件只针对当前组件有效,不会影响到子组件。所以在react项目中如果不进行处理很容易造成全局样式污染的问题。

    针对此,通常有两种解决方案:

    1、每个组件在最外层标签上都定义一个唯一的类名,所有的样式写在这个类下面。但是该方式当项目大了,参入人员多了就很难把控。

    2、定义组件样式文件为组件名.module.less的形式,并以模块的方式引入。
    一锅烩了那些React项目开发绕不过去的配置_第8张图片

    解析到页面的时候就会给对应标签添加一个唯一标志的类名,这样就可以有效的避免样式污染问题。
    一锅烩了那些React项目开发绕不过去的配置_第9张图片

按需引入 Antd 组件

antd组件是使用React技术栈使用最为普遍的 UI 组件库,如果使用css书写样式,那么按照官方文档配置使用即可:
一锅烩了那些React项目开发绕不过去的配置_第10张图片

但是,如果我们使用less作为样式书写方式,使用上面的方式,antd的样式文件会不生效,如果将在App.less顶部引入的antd样式文件改为antd.less则会报错,因为在antddist目录下根本就找不到antd.less文件。

那么,我们就需要借助babel-plugin-import这个加插件了,使用这个插件在按需引入组件的时候会自动引入.js.css|.less文件。
一锅烩了那些React项目开发绕不过去的配置_第11张图片

接下来,就需要安装和配置babel-plugin-import

  • 安装
npm install babel-plugin-import
  • 配置

package.json文件中配置babel:

"babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [["import",{
        "libraryName": "antd",
        "libraryDirectory": "es",
        "style": true
    }]]
  }

或者在根目录创建.babelrl文件,配置:

// .babelrl
{
  "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      ["import", {
        "libraryName": "antd",
        "libraryDirectory": "es",
        // css
        // "style": "css"
        //less
        "style": true
      }]
    ]
}

以上两种配置babel的方式取一种即可。

另外需要提及一下的是,以上配置style取值不同,加载的样式文件类型也不一样。
一锅烩了那些React项目开发绕不过去的配置_第12张图片

  • 踩坑

    配置style: true时,如果使用lessless-loader较高版本时会报错,在一番尝试后,将版本降级即可。我这里使用如下版本:

  "less": "2.7.3",
  "less-loader": "5.0.0",

当然,经过探索,使用高版本的less-loader也是可以的,但是less的版本要匹配,我这里使用了less-loader最新版本7.1.0,通过查看less-loader配置文件,发现依赖的less版本是3.5.0,因此我将less版本降为了对应版本。可光这样还是不够,还需要对webpackless-loader进行对应的配置。
一锅烩了那些React项目开发绕不过去的配置_第13张图片

自定义 Antd 主题

搞定上面的less-loader配置以后,自定义主题就非常容易了。antd组件样式是使用less开发的,我们只需要使用less提供的 modifyVars 的方式进行覆盖变量即可。
一锅烩了那些React项目开发绕不过去的配置_第14张图片

配置跨域

跨域是前后端分离的开发方式绕不过去的问题,因此也是我们进行项目开发时首先要考虑的,React开发的跨域配置有别于Vue,下面我们就来详细的看看。
通过阅读create-react-app官方文档可知,有两种方式可以配置跨域:

1、在package.json中,proxy字段进行配置:

"proxy": "http://192.168.0.0/api"

这时,如果访问后端接口本地的localhost就会被代理到http://192.168.0.0/api上。

但是,这种方式存在一个缺陷,如果你想同时访问多台服务器这种方式就没办法满足了,类似如下配置:

// 伪代码
"proxy": {
    "/api": {
        "target": "http://192.168.0.0",
    },
    "/abc": {
        "target": "http://192.168.0.1",
    }
}

此时,启动就会报错,提示proxy字段只接收字符串,不接受对象。

2、针对第一种的缺陷,官方比较推荐使用 http-proxy-middleware插件的方式
一锅烩了那些React项目开发绕不过去的配置_第15张图片

具体使用如下:

  • 安装http-proxy-middleware
yarn add http-proxy-middleware
  • 在项目的src目录下创建setupProxy.js,并进行配置
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
    app.use(
        '/api',
        createProxyMiddleware({
            target: 'http://172.16.0.0:8082',
            changeOrigin: true,
        })
    ),
    app.use(
        '/abc',
        createProxyMiddleware({
            target: 'http://172.16.0.1:8082',
            changeOrigin: true,
        })
    ),
}

然后重启项目,就可以了。

设置路径别名

config目录下找到webpack.config.js文件,做如下配置即可:
一锅烩了那些React项目开发绕不过去的配置_第16张图片

关闭生产环境 sourcemap

通常在开发的时候,我们会配置开发环境生成 sourcemap 文件,方便调试,但是在生产环境都要关闭,避免代码泄露和打包多余文件,体积太大。通过查看config/webpack.config.js文件的devtool配置如下:
一锅烩了那些React项目开发绕不过去的配置_第17张图片

因此,可以通过cross-env插件在打包命令中添加全局变量。

  • 安装cross-env
yarn add cross-env
  • 在打包命令中配置
"scripts": {
    "build": "cross-env GENERATE_SOURCEMAP=false node scripts/build.js",
  },

打包开启 gzip 压缩

前端代码打包开启gzip压缩是前端性能优化重要的策略之一,因此这里也说下eject后如何配置打包开启gzip压缩。开启打包代码压缩需要使用到插件compression-webpack-plugin

  • 安装
yarn add compression-webpack-plugin
  • 配置

config/webpack.config.js文件下做如下配置:

const CompressionPlugin = require("compression-webpack-plugin");
// 在命令中添加一个GENERATE_GZIP环境变量控制是否开启打包压缩
const isGzip = process.env.GENERATE_GZIP === "true";
plugins: [
  // ...
  isEnvProduction &&
    isGzip &&
    new CompressionPlugin({
      // 匹配开启gzip压缩的文件
      test: /\.(js|css|html|png|jpg)$/,
      // 压缩后静态资源文件名
      filename: "[path][base].gz",
      // 使用的压缩算法
      algorithm: "gzip",
      // 只处理10M以上的资源压缩
      threshold: 10240,
      // 只处理压缩率小于0.8的资源
      minRatio: 0.8,
      // 压缩后是否删除源文件
      deleteOriginalAssets: true,
    }),
];

  • 在命令脚本设置是否开启压缩打包变量
"scripts": {
    "test": "cross-env GENERATE_GZIP=true node scripts/build.js",
    "prod": "cross-env GENERATE_GZIP=true node scripts/build.js"
  },

配置全局环境变量

create-react-app官方文档中提供了通过.env文件来配置全局环境变量的方式,但是只提供了开发环境和生产环境两种,但是我们日常开发中,必定会有测试环境,甚至是体验环境,因此仅仅使用官方提供的无法满足我们的需求,因此需要做些小的修改。

需求:

打包后的文件名通过.env.xx环境变量REACT_APP_MODE来决定。

步骤:

  1. 在项目根目录创建三个文件:.env.env.test.env.prod。内容如下:
// .env 开发环境环境变量
NODE_ENV = development;
REACT_APP_MODE = dev;
// .env.test 测试环境环境变量
NODE_ENV = production;
REACT_APP_MODE = test;
// .env.prod 生产环境环境变量
NODE_ENV = production;
REACT_APP_MODE = prod;
  1. 修改package.json文件命令脚本
"scripts": {
    "start": "node scripts/start.js",
    "test": "cross-env MODE_ENV=test node scripts/build.js",
    "prod": "cross-env MODE_ENV=prod node scripts/build.js"
  },
  1. 修改 config/env.js 文件
    一锅烩了那些React项目开发绕不过去的配置_第18张图片

  2. 修改config/paths.js文件
    一锅烩了那些React项目开发绕不过去的配置_第19张图片

几点说明

  • 在命令脚本设置环境变量使用了cross-env插件,因此需要先安装。
  • 在命令脚本加入MODE_ENV环境变量是为了根据命令加载不同的.env.xx环境变量文件。
  • .env.xx配置的环境变量必须以REACT_APP_xx开头。
  • .env.xx配置的环境变量在全局范围内通过process.env.变量名获取到。

二、不使用eject,重写项目默认webpack配置

相比于eject释放配置后,需要进行复杂的webpack配置不同,不使用eject则简单很多,添加less支持Antd组件按需引入自定义主题跨域配置设置路径别名打包开启 gzip 压缩生产环境关闭 sourceMap等都可以通过插件react-app-rewired、customize-cra在一个配置文件内完成,同时还支持自定义扩展,根据需求配置(添加或修改)webpackpluginsloader。具体操作如下:

安装依赖

yarn add react-app-rewired customize-cra babel-plugin-import less less-loader antd

修改package.json命令配置

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build"
}

在项目根目录创建config-overrides.js文件,并进行如下配置:

const {
  override,
  overrideDevServer,
  fixBabelImports,
  addLessLoader,
  addWebpackAlias,
} = require("customize-cra");
const path = require("path");
const CompressionPlugin = require("compression-webpack-plugin");
const devServerConfig = () => (config) => {
  return {
    ...config,
    // 跨域配置
    proxy: {
      "/api": {
        target: "http://172.16.15.50:8082/api",
        secure: false,
        changeOrigin: true,
        pathRewrite: {
          "^/api": "",
        },
      },
      "/abc": {
        target: "http://172.16.0.1:9003",
        secure: false,
        changeOrigin: true,
        pathRewrite: {
          "^/abc": "",
        },
      },
    },
  };
};
// 自定义插件配置
const selfPlugins = [
  new CompressionPlugin({
    // 匹配开启gzip压缩的文件
    test: /\.(js|css|html|png|jpg)$/,
    // 压缩后静态资源文件名
    filename: "[path][base].gz",
    // 使用的压缩算法
    algorithm: "gzip",
    // 只处理10M以上的资源压缩
    threshold: 10240,
    // 只处理压缩率小于0.8的资源
    minRatio: 0.8,
    // 压缩后是否删除源文件
    deleteOriginalAssets: true,
  }),
  // 更多自定义插件配置.....
];
module.exports = {
  // webpack配置
  webpack: override(
    // 按需引入antd组件
    fixBabelImports("import", {
      libraryName: "antd",
      libraryDirectory: "es",
      style: true,
    }),
    // 添加less支持和自定义主题
    addLessLoader({
      lessOptions: {
        modifyVars: { "@primary-color": "#1DA57A" },
        javascriptEnabled: true,
      },
    }),
    // 设置路径别名
    addWebpackAlias({
      "@": path.resolve(__dirname, "./src"),
    }),
    /**暴露webpack的配置
     * 当插件内置方法不足以满足我们的要求时,可以接收一个函数来暴露webpack所有配置,
     * 进行添加或修改loader,plugins,如此就可以满足所有的需求了
     */
    (config) => {
      console.log("config", config);
      // 开发环境开启source map,生产环境去掉map文件
      config.devtool =
        config.mode === "development" ? "cheap-module-source-map" : false;
      /**
       * 添加或修改plugins配置
       */
      if (process.env.NODE_ENV === "production") {
        // 生产环境开启gzip压缩
        config.plugins = [...config.plugins, ...selfPlugins];
      }
      /**
       * 添加和修改loader配置
       */
      // 获取所有的loader信息:
      let loaders = config.module.rules.find((rule) =>
        Array.isArray(rule.oneOf)
      ).oneOf;
      // 获取到所有的loader之后,根据需求添加或修改具体的某一项loader.....
      return config;
    }
  ),
  // 本地服务devServer配置
  devServer: overrideDevServer(devServerConfig()),
};

几点说明:

  1. 添加less支持会根据lessless-loader版本不同会有所差异,我这里使用的版本是:"less": "3.5.0""less-loader": "^7.1.0"

  2. 这种配置less支持的方式,不管是直接使用.less文件书写样式,还是通过.module.less文件的方式都可以支持。

  3. 跨域代理配置这并不是唯一的方法,也可以使用前面介绍到的http-proxy-middleware插件。

  4. 使用compression-webpack-plugin开启gzip压缩时,如果使用最新版本打包时会报错,版本降为6.0.0后正常。
    一锅烩了那些React项目开发绕不过去的配置_第20张图片

  5. 这种方式,除了使用插件内置的方法配置外,还支持自定义扩展配置,通过暴露webpack的配置来完成自定义添加或修改pluginsloader,这样就可以最大限度来满足项目的多样化需求了。

环境变量配置差异

相较于释放webpack配置配置环境变量,重写webpack配置设置环境变量则有所不同,我们无法在config-overrides.js中配置根据环境引入不同的.env.xx文件。因此只能借助工具来完成,通过dotenv插件我们可以在命令脚本配置引入哪个.env.xx文件。我们用同样的需求演示。

需求:

打包后的文件名通过.env.xx环境变量REACT_APP_MODE来决定。

步骤:

  • 安装dotenv-cli
yarn add dotenv-cli
  • 在项目根目录下创建.env.env.test.env.prod文件,并进行如下配置:
// .env 开发环境环境变量
REACT_APP_MODE = dev;
// .env.test 测试环境环境变量
REACT_APP_MODE = test;
// .env.prod 生产环境环境变量
REACT_APP_MODE = prod;
  • 修改package.json文件命令脚本
"scripts": {
    "start": "react-app-rewired start",
    "test": "dotenv -e .env.test react-app-rewired build",
    "prod": "dotenv -e .env.prod react-app-rewired build"
}
  • config-overrides.js中修改默认打包后的文件名
    一锅烩了那些React项目开发绕不过去的配置_第21张图片

完整配置文件

const {
  override,
  overrideDevServer,
  fixBabelImports,
  addLessLoader,
  addWebpackAlias,
} = require("customize-cra");
const path = require("path");
const CompressionPlugin = require("compression-webpack-plugin");
const devServerConfig = () => (config) => {
  return {
    ...config,
    // 跨域配置
    proxy: {
      "/api": {
        target: "http://172.16.15.50:8082/api",
        secure: false,
        changeOrigin: true,
        pathRewrite: {
          "^/api": "",
        },
      },
      "/abc": {
        target: "http://172.16.0.1:9003",
        secure: false,
        changeOrigin: true,
        pathRewrite: {
          "^/abc": "",
        },
      },
    },
  };
};
// 自定义插件配置
const selfPlugins = [
  new CompressionPlugin({
    // 匹配开启gzip压缩的文件
    test: /\.(js|css|html|png|jpg)$/,
    // 压缩后静态资源文件名
    filename: "[path][base].gz",
    // 使用的压缩算法
    algorithm: "gzip",
    // 只处理10M以上的资源压缩
    threshold: 10240,
    // 只处理压缩率小于0.8的资源
    minRatio: 0.8,
    // 压缩后是否删除源文件
    deleteOriginalAssets: true,
  }),
  // 更多自定义插件配置.....
];
module.exports = {
  /**
   * 路径信息配置
   *  要修改什么路径,打印出来paths,更改对应的位置即可
   */
  paths: function (paths) {
    // 打包后的文件名
    paths.appBuild = path.join(__dirname, `./${process.env.REACT_APP_MODE}`);
    console.log(paths);
    return paths;
  },
  // webpack配置
  webpack: override(
    // 按需引入antd组件
    fixBabelImports("import", {
      libraryName: "antd",
      libraryDirectory: "es",
      style: true,
    }),
    // 添加less支持和自定义主题
    addLessLoader({
      lessOptions: {
        modifyVars: { "@primary-color": "#1DA57A" },
        javascriptEnabled: true,
      },
    }),
    // 设置路径别名
    addWebpackAlias({
      "@": path.resolve(__dirname, "./src"),
    }),
    /**暴露webpack的配置
     * 当插件内置方法不足以满足我们的要求时,可以接收一个函数来暴露webpack所有配置,
     * 进行添加或修改loader,plugins,如此就可以满足所有的需求了
     */
    (config) => {
      // console.log('config', config)
      // 开发环境开启source map,生产环境去掉map文件
      config.devtool =
        config.mode === "development" ? "cheap-module-source-map" : false;
      /**
       * 添加或修改plugins配置
       */
      if (process.env.NODE_ENV === "production") {
        // 生产环境开启gzip压缩
        config.plugins = [...config.plugins, ...selfPlugins];
      }
      /**
       * 添加和修改loader配置
       */
      // 获取所有的loader信息,
      let loaders = config.module.rules.find((rule) =>
        Array.isArray(rule.oneOf)
      ).oneOf;
      // 获取到所有的loader之后,根据需求添加或修改具体的某一项loader.....
      return config;
    }
  ),
  // 本地服务devServer配置
  devServer: overrideDevServer(devServerConfig()),
};

至此,项目开发中的常用配置就已经分享完了,搭建好环境后就可以愉快的撸码了。

码字不易,且行且珍惜!!!!

你可能感兴趣的:(React,react.js,javascript,webpack)