Skip to content

CRA 项目迁移指南

作者:Atom
字数统计:3.4k 字
阅读时长:13 分钟

React 团队不再建议使用create-react-app (CRA)作为打包器来创建新的 React 应用程序。团队和社区意识到,尽管 CRA 是一个快速启动器,但它缺乏配置或管理大型复杂应用程序所需的灵活性。

如今,该团队建议对本机应用程序使用生产级 React 框架,例如NextJSRemixGatsbyExpo。虽然框架是首选,但 React 团队还建议使用ViteParcel进行自定义构建过程。

部分原因是CRA 软件包已经大约两年没有更新。这可能会导致一些问题,即已更新到较新版本的软件包无法在现有应用程序中使用。因此,您可能需要通过使用推荐的替代方案(ViteParcel)替换 CRA 包来更新现有应用程序。

本文将引导您完成将基于生产的应用程序从 CRA 迁移到 Vite 的步骤。您将了解每个步骤的“原因”、如何保留Jest以进行测试,以及如何更新您的步骤browserslist,因为它不能vite开箱即用。

一、安装依赖

安装迁移所需 Vite 及插件

以下是安装我们需要的软件包的命令:

bash
yarn add vite @vitejs/plugin-react vite-tsconfig-paths -D
# OR
npm install vite @vitejs/plugin-react vite-tsconfig-paths -D
# OR
pnpm add vite @vitejs/plugin-react vite-tsconfig-paths -D

除了 Vite 之外,我们还添加了两个插件 @vitejs/plugin-react和  vite-tsconfig-paths.

  • vitejs/plugin-react: 支持开发中的快速刷新,使用自动 JSX 运行时以及自定义 Babel 插件或预设。它丰富了您的 React 开发经验。

  • vite-tsconfig-paths: 解析 TypeScript 路径映射的导入。例如,您可以使用components/ComponentName代替./../components/ComponentName.

安装其他 Vite 插件

您还可以在这里查看其他官方 Vite 插件。

二、创建 Vite 配置文件

在终端中运行 vite 命令时 ,Vite 会尝试在项目的根目录中查找 vite.config.ts 文件。

因此, 我们应该首先在应用程序的根目录中,创建一个名为vite.config.ts以下内容的文件

js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import viteTsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  // depending on your application, base can also be "/"
  base: '',
  plugins: [react(), viteTsconfigPaths()],
  server: {
      // this ensures that the browser opens upon server start
      open: true,
      // this sets a default port to 3000
      port: 3000,
  }
})

三、创建 Vite 类型声明文件

默认情况下,Vite 类型用于 NodeJS 环境。对于客户端代码,Vite 提供了 vite/client.d.ts 中的类型定义。需要按照下面步骤来引用类型声明文件,该文件有助于类型检查和智能提示

在应用程序的根目录中,创建包含以下内容的文件 vite-env.d.ts

js
// vite-env.d.ts
/// <reference types="vite/client" />

四、移动 HTML 文件

Vite项目中, index.html文件是Vite服务的入口点,需要将该文件需要放置于根目录中

因此, 从 public 目录中,将 index.html 文件移动到项目的根目录

五、更新 HTML 文件

这里需要两处更新

删除路径占位符

Vite 会自动解析内部 index.htmlURL,因此不需要 %PUBLIC_URL% 占位符。您可以为此在 index.html 文件中进行搜索和替换。请务必删除所有匹配项。

  • 改动前
html
<link rel="icon" type="image/svg+xml" href="%PUBLIC_URL%/favicon.svg" />
  • 改动后
html
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />

将模块脚本添加到正文标签的底部

Vite中, index.html 被视为源代码和模块图的一部分。

它会自动解析<script type="module" src="...">中所引用的 JavaScript 源代码。

因此, 该部分也需要进行改造。我们需要在 index.html 文件中的 body 标签的底部添加脚本

如下所示:

html
<body>
  <!-- others here -->
  <script type="module" src="/src/index.tsx"></script>
</body>

六、将 CRA 替换为 Vite

要将CRA替换为Vite, 这部分为以下几个步骤

删除 CRA

若要删除 CRA,请运行以下命令。这将从我们已安装的软件包中删除 react-scripts

bash
yarn remove react-scripts
# OR
npm uninstall react-scripts
# OR
pnpm remove react-scripts

运行上述命令后,删除 react-app-env.d.ts 该文件

为命令启动添加 Vite 脚本

安装 Vite 后,您可以在脚本中使用 vite 命令来启动服务。因此需要更换掉原来的启动命令 react-scripts

我们的重点放在 startbuild 两个命令上。 preview 则用于在本地预览生产版本

json
{
  "scripts": {
    "start": "vite", // start dev server
    "build": "tsc && vite build", // build for production
    "preview": "vite preview" // locally preview production build
  }
}

更新 tsconfig.json

这个部分重点关注isolatedModuleslibtargettypes几个选项

如果需要了解其他有关于Vite配置的选项,请移步tsconfig for vite

js
{
  "compilerOptions": {
      "lib": ["dom", "dom.iterable", "esnext"],
      "target": "ESNext",
      "types": ["vite/client"],
      "isolatedModules": true,
  }
}

更新环境变量使用方式

如果应用代码中使用了环境变量,则该步骤是必需的。Vite 使用 import.meta.env.REACT_APP_VARIABLE 代替了process.env.REACT_APP_VARIABLE

你可以在这里找到关于Vite 环境变量和模式的更多细节

  • 改造前
js
process.env.REACT_APP_VARIABLE
  • 改造后
js
import.meta.env.REACT_APP_VARIABLE

替换环境变量的前缀

上一步, 更改了环境变了的使用方式。那么还需要更改环境变量的前缀才能完成环境变量的改造

我们要做的是将环境变量中的REACT_替换为VITE_

因为 Vite 会过滤掉任何不以 VITE_开头的 env 变量

  • 改造前
javascript
REACT_APP_API_BASE
  • 改造后
javascript
VITE_APP_API_BASE

七、运行应用程序

bash
yarn start
# OR
npm start
# OR
pnpm start

此时, 我们已成功完成将应用程序从 CRA 迁移到 Vite 的第一步

如下图, 运行命令后, 我们的服务已经跑起来了

Screenshot-2023-10-05-at-17.21.43-1

可能遇到的问题

如果服务没跑起来, 可能会遇到下面这几个错误

global 未定义报错

如果出现此错误,请在 vite.config.ts 文件中定义全局,如下所示:

javascript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  // ...
  define: {
    // here is the main update
    global: 'globalThis',
  },
});

emotion 报错

如果您使用了 @emotion/react@emotion/css。您需要将此事告知 Vite

为此,请安装 @emotion/babel-plugin

shell
yarn add @emotion/babel-plugin -D
# OR
npm install @emotion/babel-plugin -D
# OR
pnpm add @emotion/babel-plugin -D

然后更新vite.config.ts配置文件中的插件项, 如下所示

js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import svgr from 'vite-plugin-svgr';

export default defineConfig({
  // ...
  plugins: [
    // here is the main update
    react({
      jsxImportSource: '@emotion/react',
      babel: {
        plugins: ['@emotion/babel-plugin'],
      },
    }),
  ],
  // ...
});

八、修复单元测试

此刻,运行单元测试的命令pnpm test, 它们可能已经失效了。 因为 CRA 使用 react-scripts test 运行单元测试,所以我们需要切换到使用 jest来完成

以下步骤重点介绍了如何修复单元测试

Screenshot-2023-10-06-at-16.20.33

直接使用 jest

首先,您需要安装 jestts-jestjest-environment-jsdom

  • jest 将是我们运行测试的可执行工具文件

  • ts-jest 是一个支持源映射的转换器,它允许在 TypeScript 项目中运行测试

  • jest-environment-jsdom 测试运行期间模仿浏览器的行为

shell
yarn add -D jest @types/jest ts-jest jest-environment-jsdom @testing-library/react
# OR
npm install -D jest @types/jest ts-jest jest-environment-jsdom @testing-library/react
# OR
pnpm add -D jest @types/jest ts-jest jest-environment-jsdom @testing-library/react

根据 Jest 官网 所述

由于 vite 插件系统的工作方式,vite 不完全支持 Jest,但是有一些非常一流的 vite-jest 库来支持该项工作

使用 vite-jest

所以更推荐的方式是使用vite-jest, 我们可以从 vite-jest 使用示例来做更多的了解

首先安装相关的依赖

sh
yarn add -D jest @types/jest \
            vite-jest \
            jest-environment-jsdom \
            @testing-library/react \
            @testing-library/jest-dom \
            @testing-library/user-event
# OR
npm install -D jest @types/jest \
              vite-jest \
              jest-environment-jsdom \
              @testing-library/react \
              @testing-library/jest-dom  \
              @testing-library/user-event
# OR
pnpm add -D jest @types/jest \
                vite-jest \
                jest-environment-jsdom \
                @testing-library/react \
                @testing-library/jest-dom \
                @testing-library/user-event

九、更新 Jest 配置文件

因为上一步使用的工具差异性, 配置文件也有所不同

更新 jest 的配置文件

如果Jest是独立的配置文件, 请按照下面的配置进行更改

这一部分,主要关注presettestEnvironmentmoduleNameMappermodulePaths选项

preset 设置为 ts-jest/presets/js-with-ts 允许混用 JavaScriptTypeScript。 您也可以根据您的应用程序将其设置为 ts-jest

moduleNameMapper 配置 Jest 以优雅地处理样式表和图像等资产

js
// jest.config.ts
export default {
  preset: 'ts-jest/presets/js-with-ts',
  testEnvironment: 'jest-environment-jsdom',
  moduleNameMapper: {
    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
      '<rootDir>/__mocks__/fileMock.js',
    '\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js'
  },
  modulePaths: [
    // you can update this to match your application setup
    '<rootDir>/src'
  ]
}

由于我们 moduleNameMapper 在上面引用了一个文件,我们需要创建该文件及其对应的文件。步骤十会专门处理此问题。这个handling-static-assets配置在 Jest 的文档中有进一步的解释

适配 vite-jest 配置文件

ts
export default {
  preset: 'vite-jest',
  testEnvironment: 'jest-environment-jsdom',
  setupFilesAfterEnv: ['<rootDir>/setupTests.js'],
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
    '<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
  ]
}

此时还需要在项目根目录中创建一个 setupTests.js 文件,内容如下

js
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

具体的代码示例, 请参考react-app-type-module

十、更新测试脚本

现在已经正确安装jest了,我们可以将react-scripts tests替换为jest, 更改应如下所示

如果您之前在代码中没有 test: coverage or test: debug选项,请忽略

  • 改造前
json
"scripts": {
  "test": "react-scripts test",
  "test:coverage": "react-scripts test --coverage .",
  "test:debug": "react-scripts test --inspect-brk --runInBand --no-cache"
}
  • 改造后
json
"scripts": {
  "test": "jest",
  // you can add this to keep watch mode on
  "test:watch": "jest --watch",
  "test:coverage": "jest --coverage .",
  "test:debug": "jest --inspect-brk --runInBand --no-cache"
}

如果使用的是vite-jest, 测试脚本如下

json
"scripts": {
  "test": "vite-jest --no-cache",
  // you can add this to keep watch mode on
  "test:watch": "vite-jest --watch",
  "test:coverage": "vite-jest --coverage ."
}

十一、运行测试

shell
yarn test
# OR
npm test
# OR
pnpm test

如果遇到与 import.meta 相关的变量问题,可以将所有环境变量移动到单个文件并在测试中模拟此文件来解决此问题。

你可以看一下这个提交进行理解

十二、 browserslist配置

这是用于在多个前端存储库之间共享目标浏览器或受支持的浏览器的配置。

根据行业的不同,有不同的标准。例如,在教育科技领域,所有在线学习的用户可能都使用品牌版本屏幕尺寸相似的浏览器。这份常用浏览器列表可以轻松成为教育科技行业的标准

browserslist 配置的示例应用程序是当您需要与旧版浏览器兼容时。将此范围传递给 browserslist 配置可帮助您的应用程序使用与目标浏览器兼容的 polyfill 来编译代码。这样,您的页面就可以优化性能并获得良好的用户体验。

Browserslist 配置通常在 package.json or .browserslistrc 文件中设置,如下所示

package.json

json
{
  "browserslist": [
    "iOS >= 9",
    "Android >= 4.4",
    "last 2 versions",
    "> 0.2%",
    "not dead"
  ]
}

.browserslistrc

javascript
iOS >= 9
Android >= 4.4
last 2 versions
> 0.2%
not dead

您还可以在此处阅读有关 Browserslist 的更多信息

十三、Browserslist在Vite中的问题

Vite 在底层使用 ESBuild,它所预期的配置格式与browserslist的格式是不同的

ESBuild 的预期格式是: ['es2015', 'safari11', 'ios11']

Browserslist的格式是: ['defaults', 'Safari >= 11', 'ios_saf >= 11']

由于这种差异,Vite 会忽略当前package.json中的browserslist或者.brwserslistrc文件中的配置

要解决此问题,可以按照下面的步骤来操作。使用一个名为 browserslist-to-esbuild 的包,该包会在后台执行转换并将结果传递到vite.config.ts配置文件中的build.target

安装 browserslist-to-esbuild

shell
yarn add browserslist-to-esbuild -D
# OR
npm install browserslist-to-esbuild -D
# OR
pnpm add browserslist-to-esbuild -D

在 Vite 配置中适配 browserslist

vite.config.ts 文件中,更新如下图所示。

javascript
import { defineConfig } from 'vite'
import browserslistToEsbuild from 'browserslist-to-esbuild'

export default defineConfig({
  // ..
  build: {
    // --> ["chrome79", "edge92", "firefox91", "safari13.1"]
    target: browserslistToEsbuild(),
  },
  ..
})

然后,您可以按如下所示传递您的配置,

javascript
export default defineConfig({
  // ..
  build: {
    // you can also pass your usual browserslist config here
    target: browserslistToEsbuild([
    	'>0.2%',
    	'not dead',
    	'not op_mini all'
    ]),
  }
})

结尾

到此为止, 我们的应用已经从CRA迁移到了Vite

如果还有其他疑问, 请参考国外大佬的一个迁移CRA的样例库