如果你還沒聽說過 Vite.js
疚沐,那你應(yīng)該去試一試待锈。
Vite 提供了一個(gè)開發(fā)服務(wù)器舶沛,它基于 原生 ES 模塊 提供了 豐富的內(nèi)建功能罩抗,如速度快到驚人的 模塊熱更新(HMR)求摇。
在開始改造之前窍荧,先來看一下本項(xiàng)目用了哪些東西:
- Create React App (CRA) 創(chuàng)建項(xiàng)目
- 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
創(chuàng)建的項(xiàng)目并進(jìn)入命令行蕊退,執(zhí)行命令郊楣,安裝 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%
:
- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+ <link rel="icon" href="/favicon.ico" />
- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+ <link rel="apple-touch-icon" href="/logo192.png" />
- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+ <link rel="manifest" href="/manifest.json" />
并且在 body
標(biāo)簽內(nèi)追加代碼
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
+ <script type="module" src="/src/index.jsx"></script>
如果是 Typescript 則使用 <script type="module" src="/src/index.tsx"></script>
净蚤。
更改 .js 為 .jsx 后綴
在 vite 中需要將 .js
后綴的組件改成 .jsx
或 .tsx
后綴,參考:react支持.js后綴文件 · Issue #1552 · vitejs/vite (github.com)
3. SASS
如果項(xiàng)目使用了 sass输硝,則需要執(zhí)行命令進(jìn)行安裝:
# npm
$ npm install -D sass
# yarn
$ yarn add -D sass
如果 scss 文件里面引入了一些 node_modules 的 css 是使用 ~
符號的今瀑,可以做出調(diào)整:
@import '~antd/dist/antd.css';
@import '~react-perfect-scrollbar/dist/css/styles.css';
調(diào)整為
@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 項(xiàng)目使用了 mobx 的裝飾器 @
,本來期望通過配置 babel 來解決但沒有生效郎逃。
關(guān)于在 CRA 中啟用裝飾器主要是用到兩個(gè)插件: @babel/plugin-proposal-decorators
和 @babel/plugin-proposal-class-properties
哥童,這里列出參考的鏈接:
- vite - decorators not support in js for prebuild
- 知乎 - vite react 項(xiàng)目中使用裝飾器 開啟 decorators-legacy
- vite react 項(xiàng)目中使用裝飾器 開啟 decorators-legacy
- @vitejs/plugin-react
- stackoverflow - How do i enable "@babel/plugin-proposal-decorators" with vite
- bilibili - vite 追加 decorators
經(jīng)過一系列嘗試,最終還是決定移除裝飾器褒翰,需將 Mobx 里面用到的裝飾器改成函數(shù)調(diào)用的形式贮懈,mobx用法改寫過程可以參考鏈接,最新版的 mobx(本機(jī)版本:"mobx": "^6.3.12"
)可以參考官方示例 - React integration · MobX优训,下面列出本人的調(diào)整方式朵你。
Store 的創(chuàng)建方式調(diào)整
項(xiàng)目的 Mobx 版本如下:
"mobx": "^6.3.12",
"mobx-react": "^7.2.1",
現(xiàn)在需要將使用到的裝飾器移除,如下:
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
關(guān)于報(bào)錯(cuò)
MobX: Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed
解決方法:如果一個(gè) action
方法使用到了 async
的話揣非,則需要寫一個(gè) runInAction
在內(nèi)部來修改 observable
屬性抡医,或者調(diào)用另外一個(gè) action
方法修改,或者使用其他方式來實(shí)現(xiàn) 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
})
// 或者使用獨(dú)立方法
this.setUser(authUser)
}
// 這個(gè)方法將被自動封裝成`action`,因?yàn)槭褂昧?`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
.
但由于本人項(xiàng)目從一開始的裝飾器更改過來搁嗓,繼續(xù)采取 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 時(shí)
export default withRouter(inject('userStore')(observer(RouterPage)))
Provider 使用方式
// 定義多個(gè) 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
};
// 默認(rèn)導(dǎo)出接口
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 (
<Provider {...stores}>
<div className="App">
<Router><RouterPage /></Router>
</div>
</Provider>
);
}
export default App;
參考文檔
segmentfault - react-mobx6+使用案例
mobx-react - inject-as-function
https://dev.to/rosyshrestha/build-your-first-app-with-mobx-and-react-4896
stackoverflow - How to get MobX Decorators to work with Create-React-App v2?
5. vite.config.js 配置
在項(xiàng)目根目錄新建一個(gè) vite.config.js
文件,內(nèi)容如下:
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 更改
經(jīng)過上面的遷移改造腺逛,項(xiàng)目已經(jīng)能夠正常啟動開發(fā)荷愕,但是在 npm run build
之后執(zhí)行 npm run preview
會發(fā)現(xiàn)報(bào)錯(cuò):
Uncaught ReferenceError: React is not defined
這是因?yàn)槭褂玫搅?mobx-react
的 inject
導(dǎo)致,關(guān)于報(bào)錯(cuò)信息,本人在網(wǎng)上搜索找到類似問題的鏈接:
具體配置可以參考 @vitejs/plugin-react 插件的相關(guān)描述安疗,因此接下來需要繼續(xù)調(diào)整 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
的環(huán)境變量怖现,則切換為 VITE_XXX
,假如有一個(gè) .env
文件:
PORT=8001
PUBLIC_URL=/awesome-project/
REACT_APP_NAME=My App
第一步玉罐,修改 vite.config.js
里面的配置支持下面兩個(gè)變量:
- 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 傳遞給應(yīng)用:
import dotenv from "dotenv";
dotenv.config();
export default defineConfig({
define: {
"process.env.VITE_NAME": `"${process.env.VITE_NAME}"`
},
// ...
這種方式不支持 mode-specific .env
比如 .env.development
,因此需要時(shí)要自行設(shè)置吊输。
經(jīng)常使用到 process.env.NODE_ENV
變量饶号,比如用來區(qū)分 development
和 production
的代碼編譯,這里還有一種訪問變量的方式:
export default ({ mode }) => {
return defineConfig({
define: {
"process.env.NODE_ENV": `"${mode}"`,
},
});
};
7. 參考鏈接
darekkay.com - Migrating a Create React App project to Vite (推薦)
福祿網(wǎng)絡(luò)研發(fā)團(tuán)隊(duì) - antd+react項(xiàng)目遷移vite的解決方案
掘金 - Vite2 實(shí)戰(zhàn): React + TS + Mobx 舊項(xiàng)目遷移
darraghoriordan.com - Migrating a Create React App (CRA) application to Vite
medium.com - Migrate from create-react-app (CRA) to Vite (ViteJS) with TypeScript