如果你还没听说过 Vite.js
,那你应该去试一试。
Vite 提供了一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
在开始改造之前,先来看一下本项目用了哪些东西:
- Create React App (CRA) 创建项目
- SASS
- react-app-rewired 启动
- customize-cra 自定义 CRA 配置,其中包括别名定义
-
.js
后缀的组件 - Mobx,且用到了
@
装饰器的方式来使用 - antd v4.17.3
- CRA 的 .env 文件配置了
PUBLIC_URL
- process.env.NODE_ENV
下面是迁移步骤:
- 安装 Vite
- 更改文件
- SASS
- Mobx 改造
- vite.config.js 配置
- 变量使用
1 安装 Vite
打开通过 CRA
创建的项目并进入命令行,执行命令,安装 vite
和 @vitejs/plugin-react
到 devDependencies
:
# npm
$ npm install -D vite @vitejs/plugin-react
# yarn
$ yarn add -D vite @vitejs/plugin-react
更新 package.json
中的 scripts
:
"scripts": {
- "start": "react-app-rewired start",
- "build": "react-app-rewired build",
- "test": "react-app-rewired test",
- "eject": "react-scripts eject"
+ "start": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
},
如果需要使用 Typescript 则可以配置 "build": "tsc && vite build"
。
2. 更改文件
index.html 文件
文件 index.html
移动到根目录,并且移除 index.html
中的所有 %PUBLIC_URL%
:
-
+
-
+
-
+
并且在 body
标签内追加代码
+
如果是 Typescript 则使用 。
更改 .js 为 .jsx 后缀
在 vite 中需要将 .js
后缀的组件改成 .jsx
或 .tsx
后缀,参考:react支持.js后缀文件 · Issue #1552 · vitejs/vite (github.com)
3. SASS
如果项目使用了 sass,则需要执行命令进行安装:
# npm
$ npm install -D sass
# yarn
$ yarn add -D sass
如果 scss 文件里面引入了一些 node_modules 的 css 是使用 ~
符号的,可以做出调整:
@import '~antd/dist/antd.css';
@import '~react-perfect-scrollbar/dist/css/styles.css';
调整为
@import 'antd/dist/antd.css';
@import 'react-perfect-scrollbar/dist/css/styles.css';
可以参考 issue - Cannot import CSS from node_modules using "~" pattern。
4. Mobx 使用方式改造
去除装饰器
由于 esbuild 本身不支持装饰器,问题参考:decorators not support in js for prebuild · Issue #2349 · vitejs/vite (github.com),而本人的 JavaScript 项目使用了 mobx 的装饰器 @
,本来期望通过配置 babel 来解决但没有生效。
关于在 CRA 中启用装饰器主要是用到两个插件: @babel/plugin-proposal-decorators
和 @babel/plugin-proposal-class-properties
,这里列出参考的链接:
- vite - decorators not support in js for prebuild
- 知乎 - vite react 项目中使用装饰器 开启 decorators-legacy
- vite react 项目中使用装饰器 开启 decorators-legacy
- @vitejs/plugin-react
- stackoverflow - How do i enable "@babel/plugin-proposal-decorators" with vite
- bilibili - vite 追加 decorators
经过一系列尝试,最终还是决定移除装饰器,需将 Mobx 里面用到的装饰器改成函数调用的形式,mobx用法改写过程可以参考链接,最新版的 mobx(本机版本:"mobx": "^6.3.12"
)可以参考官方示例 - React integration · MobX,下面列出本人的调整方式。
Store 的创建方式调整
项目的 Mobx 版本如下:
"mobx": "^6.3.12",
"mobx-react": "^7.2.1",
现在需要将使用到的装饰器移除,如下:
import { observable, action } from "mobx";
class ArticleStore {
@observable articleList = [];
@observable articlePage = 1;
articlePageSize = 10;
@observable articleCount = 0;
@action async getArticleList(page = 1) {
// ...
}
@action async unshiftArticle(data) {
// ...
}
}
export default ArticleStore;
// 改造后
import { observable, computed, runInAction, action, makeObservable, makeAutoObservable } from "mobx"
class ArticleStore {
articleList = []
articlePage = 1
articlePageSize = 10
articleCount = 0
constructor() {
makeObservable(this, {
articleList: observable,
articlePage: observable,
articleCount: observable,
getArticleList: action,
unshiftArticle: action
})
// makeAutoObservable(this)
}
async getArticleList(page = 1) {
// ...
const result = await ...
// ...
runInAction(() => {
// ...
})
}
unshiftArticle(data) {
// ...
}
}
export default ArticleStore
关于报错
MobX: Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed
参考链接:MobX: Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed
解决方法:如果一个 action
方法使用到了 async
的话,则需要写一个 runInAction
在内部来修改 observable
属性,或者调用另外一个 action
方法修改,或者使用其他方式来实现 async actions:
import { runInAction, makeAutoObservable } from "mobx"
class AuthStoreClass {
authUser = null
constructor() {
makeAutoObservable(this)
}
login = async (params) => {
const { data: { data: authUser } } = await loginUser(params)
runInAction(() => {
this.authUser = authUser
})
// 或者使用独立方法
this.setUser(authUser)
}
// 这个方法将被自动封装成`action`,因为使用了 `makeAutoObservable`
setUser = (user) => {
this.authUser = authUser
}
}
组件中 Observer 和 Inject 的改造
在 mobx-react 文档中提到,新的编码方式已不需要使用 Provider
/ inject
,可以参考 mobx 官方示例或使用 React.createContext
来传递 store
。
Note: usually there is no need anymore to use
Provider
/inject
in new code bases; most of its features are now covered byReact.createContext
.
但由于本人项目从一开始的装饰器更改过来,继续采取 Provider
/ inject
更便于修改,来看页面组件中的修改:
import React from 'react';
import { observer, inject } from "mobx-react";
@inject("userStore", "articleStore")
@observer
class HomePage extends React.Component {
render() {
}
}
export default HomePage
// 改造后
import React from 'react';
import { observer, inject } from "mobx-react";
class HomePage extends React.Component {
render() {
}
}
export default inject('userStore', 'articleStore')(observer(HomePage))
// 搭配使用 react-router-dom 时
export default withRouter(inject('userStore')(observer(RouterPage)))
Provider 使用方式
// 定义多个 store 示例如下
import HomeStore from "./homeStore";
import UserStore from "./userStore";
import ArticleStore from "./articleStore";
import FileStore from "./fileStore";
let homeStore = new HomeStore();
let userStore = new UserStore();
let articleStore = new ArticleStore();
let fileStore = new FileStore();
const stores = {
homeStore, userStore, articleStore, fileStore
};
// 默认导出接口
export default stores;
// 在 `App.jsx` 中的使用示例:
import React from 'react';
import { HashRouter as Router } from "react-router-dom";
import { Provider } from "mobx-react";
import stores from "./store";
import RouterPage from './pages/RouterPage';
import './App.scss';
function App() {
return (
);
}
export default App;
参考文档
segmentfault - react-mobx6+使用案例
mobx-react - inject-as-function
mobx - The gist of MobX
https://dev.to/rosyshrestha/build-your-first-app-with-mobx-and-react-4896
掘金 - Mobx React 初学者入门指南
stackoverflow - How to get MobX Decorators to work with Create-React-App v2?
5. vite.config.js 配置
在项目根目录新建一个 vite.config.js
文件,内容如下:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
})
修改 base 路径
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [react()]
})
配置别名
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const { resolve } = require('path') //必须要引入resolve
// https://vitejs.dev/config/
export default defineConfig({
base: './',
resolve: {
alias: {
'@components': resolve(__dirname, 'src', 'components'),
'@utils': resolve(__dirname, 'src', 'utils'),
'@config': resolve(__dirname, 'src', 'config'),
},
},
plugins: [react()]
})
jsxRuntime 更改
经过上面的迁移改造,项目已经能够正常启动开发,但是在 npm run build
之后执行 npm run preview
会发现报错:
Uncaught ReferenceError: React is not defined
这是因为使用到了 mobx-react
的 inject
导致,关于报错信息,本人在网上搜索找到类似问题的链接:
Uncaught ReferenceError: React is not defined
in production build · Issue #10 · alloc/vite-react-jsx (github.com)@vitejs/plugin-react - Inconsistent vendor reference · Issue #5608 · vitejs/vite (github.com)
具体配置可以参考 @vitejs/plugin-react 插件的相关描述,因此接下来需要继续调整 vite.config.js
文件中的配置:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const { resolve } = require('path') //必须要引入resolve
// https://vitejs.dev/config/
export default defineConfig({
base: './',
resolve: {
alias: {
'@components': resolve(__dirname, 'src', 'components'),
'@utils': resolve(__dirname, 'src', 'utils'),
'@config': resolve(__dirname, 'src', 'config'),
},
},
plugins: [react({ jsxRuntime: 'classic' })]
})
6. 变量使用
如果使用 process
,则需要更新 process.env
为 import.meta.env
,比如将 NODE_ENV === 'production'
更新为 import.meta.env.PROD
, REACT_APP_XXX
的环境变量,则切换为 VITE_XXX
,假如有一个 .env
文件:
PORT=8001
PUBLIC_URL=/awesome-project/
REACT_APP_NAME=My App
第一步,修改 vite.config.js
里面的配置支持下面两个变量:
- PORT=8001
- PUBLIC_URL=/awesome-project/
export default defineConfig({
base: "/awesome-project/",
server: {
port: 8001,
}, ...
]);
接着修改支持 REACT_APP_
开头的自定义变量:
# 在 .env 文件自定义 env 变量
- REACT_APP_NAME=My App
+ VITE_NAME=My App
# 在 React 组件访问自定义 env 变量
- process.env.REACT_APP_NAME
+ import.meta.env.VITE_NAME
还可以采用 dotenv 来加载 env 变量,并通过Vite 的 define 传递给应用:
import dotenv from "dotenv";
dotenv.config();
export default defineConfig({
define: {
"process.env.VITE_NAME": `"${process.env.VITE_NAME}"`
},
// ...
这种方式不支持 mode-specific .env
比如 .env.development
,因此需要时要自行设置。
经常使用到 process.env.NODE_ENV
变量,比如用来区分 development
和 production
的代码编译,这里还有一种访问变量的方式:
export default ({ mode }) => {
return defineConfig({
define: {
"process.env.NODE_ENV": `"${mode}"`,
},
});
};
7. 参考链接
darekkay.com - Migrating a Create React App project to Vite (推荐)
福禄网络研发团队 - antd+react项目迁移vite的解决方案
掘金 - Vite2 实战: React + TS + Mobx 旧项目迁移
darraghoriordan.com - Migrating a Create React App (CRA) application to Vite
medium.com - Migrate from create-react-app (CRA) to Vite (ViteJS) with TypeScript
segmentfault - 从create-react-app迁移到vite