React 从 CRA 到 Vite 迁移笔记

如果你还没听说过 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

下面是迁移步骤:

  1. 安装 Vite
  2. 更改文件
  3. SASS
  4. Mobx 改造
  5. vite.config.js 配置
  6. 变量使用

1 安装 Vite

打开通过 CRA 创建的项目并进入命令行,执行命令,安装 vite@vitejs/plugin-reactdevDependencies

# 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 by React.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-reactinject 导致,关于报错信息,本人在网上搜索找到类似问题的链接:

  • 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.envimport.meta.env ,比如将 NODE_ENV === 'production' 更新为 import.meta.env.PRODREACT_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 变量,比如用来区分 developmentproduction 的代码编译,这里还有一种访问变量的方式:

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

你可能感兴趣的:(React 从 CRA 到 Vite 迁移笔记)