Create React App(以下简称 CRA)是一个官方支持的创建 React 单页应用的脚手架,它提供了一个零配置的现代构建设置,将一些复杂工具(比如 webpack, Babel)的配置封装了起来,让使用者不用关心这些工具的具体配置,从而降低了工具的使用难度。

创建方法

npx: npx 来自 npm 5.2+ 或更高版本

npx create-react-app my-app npm: npm init 在 npm 6+ 中可用

npm init react-app my-app Yarn: yarn create 在 Yarn 0.25+ 中可用

yarn create react-app my-app Scripts 在新创建的项目中,你可以运行一些内置命令:

npm start 或 yarn start 在开发模式下运行应用程序, 默认在浏览器打开http://localhost:3000。如果更改代码,页面将自动重新加载。

npm test 或 yarn test 以交互模式运行测试程序。 默认情况下,运行与上次提交后更改的文件相关的测试。

npm run build 或 yarn build 将生产环境的应用程序构建到 build 目录。 它能将 React 正确地打包为生产模式中并优化构建以获得最佳性能。构建将被压缩,文件名中将包含哈希。

npm run eject 注意:这是单向操作。一旦 eject ,就回不去了! 执行完这个命令后会将封装在 CRA 中的配置全部反编译到当前项目,这样开发者完全取得 webpack 文件的控制权,可以自定义修改webpack打包配置。

默认文件结构

创建后,项目文件结构如下所示:

my-app node_modules public favicon.ico index.html // 页面模板 manifest.json src App.css App.js App.test.js index.css index.js // 项目入口 logo.svg reportWebVitals.js setupTests.js .gitignore package.json README.md yarn.lock 为了加快重新构建的速度,Webpack 只处理 src 中的文件。 你需要将 JS 和 CSS 文件放在 src 中,否则 Webpack 将发现不了。 只能在 public/index.html 中使用 public 中的文件。 manifest.js: 将Web应用程序安装到设备的主屏幕,为用户提供更快的访问和更丰富的体验。

项目升级

Create React App 分为两个包:

create-react-app 是一个全局命令行实用程序,可用于创建新项目。 react-scripts 包含Create React App的脚本与配置 当你运行 create-react-app 时,它始终使用最新版本的 react-scripts 创建项目,新创建的应用会获得所有新功能和改进。

CRA 将所有新特性委托给 react-scripts , 只需要更新 react-scripts, 不需要更新 create-react-app 就可以升级CRA 的特性。比如用老版本 CRA 创建了一个项目,这个项目不具备 PWA 功能,但只要项目升级了 react-scripts 包的版本就可以具备 PWA 的功能,项目本身的代码不需要做任何修改。

如何在启动项目时不清空 terminal? 在运行 yarn start 或 npm start 来启动 create-react-app 创建的项目时,默认会清空 terminal。实际在进行开发调试时,需要在 webpack 中输出一些信息。

解决方法: 1、修改node_modules/react-dev-utils/clearConsole.js 文件:

‘use strict’;

function clearConsole() { if (process.env.REACT_APP_NO_CLEAR_CONSOLE) { return; } process.stdout.write( process.platform === ‘win32’ ? ‘\x1B[2J\x1B[0f’ : ‘\x1B[2J\x1B[3J\x1B[H’ ); }

module.exports = clearConsole; 2、修改 package.json文件,将变量 REACT_APP_NO_CLEAR_CONSOLE 添加到script命令中:

// package.json { “scripts”: { “start”: “REACT_APP_NO_CLEAR_CONSOLE=true react-app-rewired start”, } } 如何扩展 Create React App 的 Webpack 配置 Create React App已经封装了webpack 配置,如果想对 webpack 配置做一些修改,这个时候应该怎么办呢?CRA提供了以下几种方式来修改 webpack 的配置:

eject 命令

替换 react-scripts 包 使用 react-app-rewired scripts 包 + override 组合 customize-cra 【推荐】 eject 命令 使用 CRA 创建完项目以后,在package.json里面提供了这样一个命令:

{ … “scripts”: { “eject”: “react-scripts eject” }, … } 执行yarn eject后会将封装在 CRA 中的配置全部复制到当前项目。eject 后项目根目录下会新增 config与scripts 文件夹,修改package.json与yarn.lock文件。

config jest env.js getHttpsConfig.js modules.js paths.js pnpTs.js webpack.config.js webpackDevServer.config.js scripts build.js start.js test.js 如果使用了eject命令,虽然扩展了 webpack 配置,但是再也享受不到 CRA 升级带来的好处了。因为react-scripts已经是以文件的形式存在于你的项目,而不是以包的形式,所以无法对其升级。

替换 react-scripts 包

react-scripts 是 CRA 的一个核心包,一些脚本和工具的默认配置都集成在里面,使用 CRA 创建项目默认就是使用这个包。但是 CRA 还提供了另外一种方式来创建 CRA 项目,用自定义 scripts 包的方式。

// 默认方式 $ create-react-app my-app

// 自定义 scripts 包方式 $ create-react-app my-app –scripts-version 自定义包 自定义包可以是下面几种形式:

react-scripts包的版本号,比如0.8.2,这种形式可以用来安装低版本的react-scripts包。 一个已经发布到 npm 仓库上的包的名字,比如your-scripts,里面包含了修改过的 webpack 配置。 一个 tgz 格式的压缩文件,比如/your/local/scripts.tgz,通常是未发布到 npm 仓库的自定义 scripts 包,可以用 npm pack 命令生成。 这种方式相对于之前的eject是一种更灵活地修改 webpack 配置的方式,而且可以做到和 CRA 一样,通过升级 scrips 包来升级项目特性。 自定义 scripts 包的结构可以参照react-scripts包的结构,只要修改对应的 webpack 配置文件,并安装上所需的 webpack loader 或 plugin 包就可以。

使用 react-app-rewired react-app-rewired 是 react 社区开源的一个修改 CRA 配置的工具,这种方式让开发者既不用eject项目也不用自己创建 scripts 包就可以拓展webpack。

如何使用

1.在 CRA 创建的项目中安装react-app-rewired

npm install react-app-rewired –save-dev 2.在项目根目录下创建config-overrides.js 文件(支持自定义文件路径)

/ config-overrides.js /

module.exports = function override(config, env) { // 参数中的 config 就是默认的 webpack config

// 对 config 进行任意修改 config.mode = ‘development’;

// 一定要把新的 config 返回 return config; } config-overriders.js 导出的是一个函数,这个函数的签名是 const override = (oldWebpackConfig, env) => newWebpackConfig。

3.修改 scripts 命令:

/ package.json /

“scripts”: {

  • “start”: “react-scripts start”,
  • “start”: “react-app-rewired start”,
  • “build”: “react-scripts build”,
  • “build”: “react-app-rewired build”,
  • “test”: “react-scripts test”,
  • “test”: “react-app-rewired test”, “eject”: “react-scripts eject” } 自定义 config-overrides.js 文件路径 通过package.json 的config-overrides-path设置自定义路径:

/ package.json / { … “config-overrides-path”: “src/app”, // src/app/config-overrides.js … } config-overrides.js 文件 默认情况下,config-overrides.js文件导出一个函数,这个函数的签名是 const override = (oldWebpackConfig, env) => newWebpackConfig(oldWepbackConfig 和 newWebpackConfig 实际指向同一个对象,因为直接在原来的 webpack config 对象上进行修改)。

也可以改为此文件导出一个对象,该对象最多包含三个字段,每个字段都是一个函数。

module.exports = { // The Webpack config webpack: function(config, env) { // …add your webpack config return config; }, // The Jest config jest: function(config) { // …add your jest config customisation… return config; }, // create a webpack dev server devServer: function(configFunction) { return function(proxy, allowedHost) { const config = configFunction(proxy, allowedHost); const fs = require(‘fs’); config.https = { key: fs.readFileSync(process.env.REACT_HTTPS_KEY, ‘utf8’), cert: fs.readFileSync(process.env.REACT_HTTPS_CERT, ‘utf8’), ca: fs.readFileSync(process.env.REACT_HTTPS_CA, ‘utf8’), passphrase: process.env.REACT_HTTPS_PASS }; return config; }; }, paths: function(paths, env) { // …add your paths config return paths; }, }

实现原理实现原理

编译时,react-app-rewired 会先取到 create-react-app 生成的默认的 webpack config,然后调用 override(config) 方法,对 config 进行修改,得到新的 webpack config。webpack 最终会使用这个新的 config 进行打包。

流程大致如下:

const overrides = require(’../config-overrides’); const webpackConfigPath = paths.scriptVersion + “/config/webpack.config.prod”;

// load original config const webpackConfig = require(webpackConfigPath);

// override config in memory require.cache[require.resolve(webpackConfigPath)].exports = overrides.webpack(webpackConfig, process.env.NODE_ENV);

// run original script require(paths.scriptVersion + ‘/scripts/build’); scripts 包 + override 组合 虽然 react-app-rewired 的方式已经可以很方便地修改 webpack 的配置了,但也可以在自定义的 script 包中实现类似的功能。

以 build.js 为例,在获取基本 webpack 配置对象和使用 webpack 对象之间加入以下代码:

// override config const override = require(paths.configOverrides); const overrideFn = override || ((config, env) => config); const overrideConfig = overrideFn(config, process.env.NODE_ENV); overrideConfig 就是修改后的 webpack 对象,最后修改调用了 webpack 对象的代码,将原来的 webpack 对象替换成修改后的 webpack 对象。

customize-cra react-app-rewired 原生写法,对 webpack config 的修改全部写在 override() 一个方法中,不够模块化。customized-cra 提供了一些 helper 方法,可以将每一个独立的修改放到单独的函数中,再串行执行这些函数。 customize-cra 依赖于 react-app-rewired 库,通过 config-overrides.js 来修改底层的 webpack,babel等配置。

安装 yarn add customize-cra react-app-rewired –dev

使用 customize-cra 导出 customizers 和 utilities 两种类型的api,查看 api docs了解更多。

customizers: 是对配置对象进行修改的方法, 让用户轻松启用或禁用 webpack,webpack-dev-server,babel等功能。 utilities: 定制一些方法用于浏览其配置。 所有代码都应写在 config-overrides.js 文件中。 参阅 api.md 文档获取有关 customize-cra 提供的功能。 const { override, addWebpackAlias, } = require(“customize-cra”); const path = require(“path”);

module.exports = override( // add an alias for “page” imports addWebpackAlias({ page: path.resolve(__dirname, “src/page”) }), ); override() 新的 override() 方法,是一个高阶函数,接受可变数量的参数,每个参数都是签名为 const fn = (oldConfig) => newConfig 的函数;同时会返回一个新的函数,这个函数的签名也是 const fn = (oldConfig) => newConfig。

override() 会在内部依次调用传入的参数函数,把前一个函数返回的 newConfig 作为后一个函数的 oldConfig 参数,得到最终的 webpack config。

大致实现如下:

function override(fns) { return function (oriConfig) { let finalConfig = oriConfig for (const fn of fns) { finalConfig = fn(finalConfig) } return finalConfig } } 如何添加文件别名? 通过 addWebpackAlias,添加文件别名。

// config-overrides.js const { override, addWebpackAlias } = require(‘customize-cra’) const path = require(“path”)

module.exports = override( addWebpackAlias({ “components”: path.resolve(__dirname, “src/components”) }), ) fixBabelImports() babel 模块化导入插件。具体查看 babel-plugin-import

const { override, fixBabelImports } = require(“customize-cra”);

module.exports = override( fixBabelImports(‘antd’, { “libraryName”: “antd”, libraryDirectory: ’es’, “style”: true, }) ); style style: true 导入CSS源文件,在编译期间进行优化。可以明显减小分发包的大小,具体取决于对库的使用情况。 style: css 将预捆绑的CSS文件直接导入。

如何自定义 babel? 通过 useBabelRc,实现自定义 babel。

// config-overrides.js const { override, useBabelRc } = require(‘customize-cra’)

module.exports = override( useBabelRc(), ) 项目根目录新建 babelrc文件:

// .babelrc { “presets”: ["@babel/env"], “plugins”: ["@babel/plugin-transform-runtime"] } 如何添加插件? 通过 addWebpackPlugin,添加插件。

// config-overrides.js const { override, addWebpackPlugin } = require(‘customize-cra’) const DefinePlugin = require(‘webpack’).DefinePlugin const BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin

module.exports = override( addWebpackPlugin( new DefinePlugin({ ’test’: 12244, }), ), addWebpackPlugin(new BundleAnalyzerPlugin()), ) 如何 SplitChunks? 通过 setWebpackOptimizationSplitChunks,自定义拆包。

// config-overrides.js const { override, setWebpackOptimizationSplitChunks } = require(‘customize-cra’)

module.exports = override( setWebpackOptimizationSplitChunks({ maxSize: 1024 1024 3, minChunks: 2, }) )