CRA 项目迁移指南

React
团队不再建议使用create-react-app (CRA)作为打包器来创建新的 React
应用程序。团队和社区意识到,尽管 CRA
是一个快速启动器,但它缺乏配置或管理大型复杂应用程序所需的灵活性。
如今,该团队建议对本机应用程序使用生产级 React 框架,例如NextJS、Remix、Gatsby或Expo。虽然框架是首选,但 React 团队还建议使用Vite或Parcel进行自定义构建过程。
部分原因是CRA
软件包已经大约两年没有更新。这可能会导致一些问题,即已更新到较新版本的软件包无法在现有应用程序中使用。因此,您可能需要通过使用推荐的替代方案(Vite
或 Parcel
)替换 CRA
包来更新现有应用程序。
本文将引导您完成将基于生产的应用程序从 CRA
迁移到 Vite
的步骤。您将了解每个步骤的“原因”、如何保留Jest
以进行测试,以及如何更新您的步骤browserslist
,因为它不能vite
开箱即用。
一、安装依赖
安装迁移所需 Vite 及插件
以下是安装我们需要的软件包的命令:
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
以下内容的文件
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
// vite-env.d.ts
/// <reference types="vite/client" />
四、移动 HTML 文件
在Vite
项目中, index.html
文件是Vite
服务的入口点,需要将该文件需要放置于根目录中
因此, 从 public
目录中,将 index.html
文件移动到项目的根目录
五、更新 HTML 文件
这里需要两处更新
删除路径占位符
Vite
会自动解析内部 index.html
的 URL
,因此不需要 %PUBLIC_URL%
占位符。您可以为此在 index.html
文件中进行搜索和替换。请务必删除所有匹配项。
- 改动前
<link rel="icon" type="image/svg+xml" href="%PUBLIC_URL%/favicon.svg" />
- 改动后
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
将模块脚本添加到正文标签的底部
在Vite
中, index.html
被视为源代码和模块图的一部分。
它会自动解析<script type="module" src="...">
中所引用的 JavaScript
源代码。
因此, 该部分也需要进行改造。我们需要在 index.html
文件中的 body
标签的底部添加脚本
如下所示:
<body>
<!-- others here -->
<script type="module" src="/src/index.tsx"></script>
</body>
六、将 CRA 替换为 Vite
要将CRA
替换为Vite
, 这部分为以下几个步骤
删除 CRA
若要删除 CRA
,请运行以下命令。这将从我们已安装的软件包中删除 react-scripts
yarn remove react-scripts
# OR
npm uninstall react-scripts
# OR
pnpm remove react-scripts
运行上述命令后,删除 react-app-env.d.ts
该文件
为命令启动添加 Vite 脚本
安装 Vite
后,您可以在脚本中使用 vite
命令来启动服务。因此需要更换掉原来的启动命令 react-scripts
我们的重点放在 start
和 build
两个命令上。 preview
则用于在本地预览生产版本
{
"scripts": {
"start": "vite", // start dev server
"build": "tsc && vite build", // build for production
"preview": "vite preview" // locally preview production build
}
}
更新 tsconfig.json
这个部分重点关注isolatedModules
、lib
、target
和 types
几个选项
如果需要了解其他有关于Vite
配置的选项,请移步tsconfig for vite
{
"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 环境变量和模式的更多细节
- 改造前
process.env.REACT_APP_VARIABLE
- 改造后
import.meta.env.REACT_APP_VARIABLE
替换环境变量的前缀
上一步, 更改了环境变了的使用方式。那么还需要更改环境变量的前缀才能完成环境变量的改造
我们要做的是将环境变量中的REACT_
替换为VITE_
因为 Vite
会过滤掉任何不以 VITE_
开头的 env
变量
- 改造前
REACT_APP_API_BASE
- 改造后
VITE_APP_API_BASE
七、运行应用程序
yarn start
# OR
npm start
# OR
pnpm start
此时, 我们已成功完成将应用程序从 CRA
迁移到 Vite
的第一步
如下图, 运行命令后, 我们的服务已经跑起来了
可能遇到的问题
如果服务没跑起来, 可能会遇到下面这几个错误
global
未定义报错
如果出现此错误,请在 vite.config.ts
文件中定义全局,如下所示:
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
yarn add @emotion/babel-plugin -D
# OR
npm install @emotion/babel-plugin -D
# OR
pnpm add @emotion/babel-plugin -D
然后更新vite.config.ts
配置文件中的插件项, 如下所示
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
来完成
以下步骤重点介绍了如何修复单元测试
直接使用 jest
首先,您需要安装 jest
、 ts-jest 和 jest-environment-jsdom
jest
将是我们运行测试的可执行工具文件ts-jest
是一个支持源映射的转换器,它允许在TypeScript
项目中运行测试jest-environment-jsdom
测试运行期间模仿浏览器的行为
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
使用 vite-jest
所以更推荐的方式是使用vite-jest
, 我们可以从 vite-jest 使用示例来做更多的了解
首先安装相关的依赖
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
是独立的配置文件, 请按照下面的配置进行更改
这一部分,主要关注preset
、testEnvironment
、moduleNameMapper
和 modulePaths
选项
preset
设置为 ts-jest/presets/js-with-ts
允许混用 JavaScript
和 TypeScript
。 您也可以根据您的应用程序将其设置为 ts-jest
moduleNameMapper
配置 Jest
以优雅地处理样式表和图像等资产
// 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 配置文件
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
文件,内容如下
// 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
选项,请忽略
- 改造前
"scripts": {
"test": "react-scripts test",
"test:coverage": "react-scripts test --coverage .",
"test:debug": "react-scripts test --inspect-brk --runInBand --no-cache"
}
- 改造后
"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
, 测试脚本如下
"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 ."
}
十一、运行测试
yarn test
# OR
npm test
# OR
pnpm test
如果遇到与 import.meta
相关的变量问题,可以将所有环境变量移动到单个文件并在测试中模拟此文件来解决此问题。
你可以看一下这个提交进行理解
十二、 browserslist配置
这是用于在多个前端存储库之间共享目标浏览器或受支持的浏览器的配置。
根据行业的不同,有不同的标准。例如,在教育科技领域,所有在线学习的用户可能都使用品牌
、版本
和屏幕尺寸
相似的浏览器。这份常用浏览器列表可以轻松成为教育科技行业的标准
browserslist
配置的示例应用程序是当您需要与旧版浏览器兼容时。将此范围传递给 browserslist
配置可帮助您的应用程序使用与目标浏览器兼容的 polyfill
来编译代码。这样,您的页面就可以优化性能并获得良好的用户体验。
Browserslist
配置通常在 package.json
or .browserslistrc
文件中设置,如下所示
package.json
{
"browserslist": [
"iOS >= 9",
"Android >= 4.4",
"last 2 versions",
"> 0.2%",
"not dead"
]
}
.browserslistrc
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
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
文件中,更新如下图所示。
import { defineConfig } from 'vite'
import browserslistToEsbuild from 'browserslist-to-esbuild'
export default defineConfig({
// ..
build: {
// --> ["chrome79", "edge92", "firefox91", "safari13.1"]
target: browserslistToEsbuild(),
},
..
})
然后,您可以按如下所示传递您的配置,
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的样例库