React接入vite作为开发环境实施方案,接入架手架介绍

2,070 阅读6分钟
如果你的React项目现在已经比较庞大,每次启动的时候都需要几十秒,热更新也是非常的慢,你想要缓解这种现象,提高开发效率的时候,可能首先就会想到vite,今天聊一聊React接入vite的方案。

一、方案介绍

新项目如何使用vite?

1.最直接也是简单的,使用vite中文官网提供的脚手架,初始化项目,不过,官网的脚手架,只提供了最基本的react项目搭建,关于路由、api、mock、redux等常用功能,需要自己封装。

2.使用阿里旗下的飞冰框架,最新版本支持一键配置vite,这个封装的功能更多一点,但是封装多意味着我们被框架限制也更多,很多东西需要按照框架要求,不过也能满足我们日常大部分开发需求。

3.umijs想想大部分人也很熟悉,umi4beta版本,也将要支持vite开发环境+webpack生产环境,但是这个版本什么时候会正式上线,目前未知。

老项目如何接入vite?

其实我们更多的时候是想要缓解,已有的项目,启动慢,热更新缓慢的问题,我们进入正题。

如题,本次文章的主题react接入vite的开发环境,之所以只接入开发环境,是因为一下几个方面的考量。

  1. 目前vite的生态相比webpack还不是那么完善,有一定风险
  2. vite兼容性较差
  3. 如果完全把项目的webpack替换成vite,可能需要做全量的测试,有出线上问题的风险

开发环境接入方案介绍

我们可以先看一下,使用vite官网脚手架搭建的项目目录

image.png

目录结构也很简单,其中红圈的三个文件是构成一个vite项目的核心文件。

index.html: vite服务的入口文件,用script标签引入了main.tsx

main.tsx: react的入口文件

vite.config.ts: vite配置文件。

我们在在老项目中使用vite作为开发环境的思路,就是在在项目中,生成一个这样的vite环境,然后在main中,我们把原有项目中的页面、路由配置、redux配置、mock配置等引入进来,这样就算完成了,听起来好像也不是很复杂。

我们现在开始试验一下

找一个项目在原有项目,可以直接在原有项目用官网的脚手架生成一个vite环境的文件夹

1.首先我们开始改造main.tsx 代码如下,我们在main当中增加动态懒加载路由部分,路由部分大家可以根据原有项目情况,使用v5或者v6版本,v5到v6版本变化还有有点大的。 由于我们原有项目使用umi框架,开启了dva配置,我们也需要加入dva的部分

import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import 'antd/dist/antd.css';
import { umiConfig, viteConfig } from '../viteConfig';
import dva from 'dva';
import { createBrowserHistory } from 'history';
import { HashRouter, Route, BrowserRouter, Switch } from 'react-router-dom';
import loadable from '@loadable/component'


// 引入所有model  
const allModel = import.meta.globEager('../src/models/*.ts');

// 不使用不会引入,导致动态导入失败
const loada = loadable(() => { })
let routerConfig = [];
// 渲染路由方法,递归渲染子路由
const renderRoute = (router: any) => {
  if (!router) return
  return router.map((item: any) => {
    const child = renderRoute(item?.routes)
    let Element = item.component
    return <Route
      key={item.path}
      path={`${item.path.replace('./', '/*').replace('/', '/*')}`}
      component={() => (
        <Element>{child}</Element>
      )}

    ></Route>
  });
};

// 默认使用dva,可以根据自己项目需求自行改造
// 创建 dva 实例
const app = dva({
  history: createBrowserHistory(),
});
// 注册 model
Object.keys(allModel).forEach((key) => {
  allModel[key]?.default && app.model(allModel[key].default);
});

const router = (
  <BrowserRouter>
    <Switch>
      {renderRoute(routerConfig)}
    </Switch>
  </BrowserRouter>
);
app.router(() => router);

// 渲染
ReactDOM.render(
  <div>
    {
      <Provider store={app.start()().props.store}>
        {/* {<Main></Main>} */}
        {router}
      </Provider>
    }
  </div>,
  document.getElementById('root'),
);

2.第二步,开始配置vite配置文件

代码如下,我们在配置中,尤其是插件中,做了很多的东西。

  • 首先利用插件,配置了动态路由,因为直接在main里面写,vite不支持动态路由里面写动态字符串。

  • 因为我们的vite项目中直接引用的redux上下文,和原有项目引用的umi的redux上下文,不是一个环境,需要利用插件更换为原生的引用。

  • 原生Vite 构建模式在开发阶段基于浏览器加载 ESM 模块,不支持 require 语法的导入,需要引入一个vite-plugin-require的插件。

  • 然后还利用vite-plugin-mock 插件实现了mock数据的处理

  • 因为原有umi项目中有一些全局变量,我们在vite中define.process 增加了一些全局变量,解决报错

  • 利用css.preprocessorOptions配置全局less变量

  • 利用resolve.alias配置实现全局引用路径配置,比如【@】这种

  • 还需要注意的一个是,原生vite的cssmodule只支持.module.less文件,我们又不能去修改原有项目的所有less文件,这里配合修改vite源码中compileCSS方法,识别a.less?module=true,这种结尾的less文件,然后再利用插件,修改所有文件中/import [a-z]+ from ".+.less" 引入的less文件,使得支持cssmodule。

  • 更多配置,大家可以参考官方文档。

  • vite修改源码部分如下:

image.png

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { viteMockServe } from 'vite-plugin-mock';
import vitePluginRequire from 'vite-plugin-require';
import { umiConfig, viteConfig } from '../viteConfig';

let config = { ...umiConfig, ...viteConfig };


// 生成router 字符串 因为动态路由vite直接写会报错!!
let routerConfig = config.routes;
const initRoute = (routers) => {
  routers.forEach(item => {
    if (item.component) {
      let atr = item.component.replace('@', '../src')
      item.component = `loadable(() => import('${atr}'))`
    }
    item?.routes?.length && initRoute(item?.routes)
  });
}
initRoute(routerConfig)
let routerStr = JSON.stringify(routerConfig)

routerStr = routerStr.replace(/"loadable/g, "loadable")
routerStr = routerStr.replace(/\)\)"/g, "))")

routerStr = `
let routerConfig =  ${routerStr}
`
// 生成router 字符串结束


const localEnabled = config.hasOwnProperty('mock') ? false : true;

const prodEnabled = !!process.env.USE_CHUNK_MOCK || false;

export default defineConfig({
  plugins: [
    {
      name: 'originjs:commonjs',
      apply: 'serve',
      transform(code, id) {
        let result = code;
        // 增加cssmodule  
        if (code.match(/import [a-z]+ from ".+\.less"/)) {
          result = code.replace(/(import [a-z]+ from ".+\.less)(")/, ($1, $2, $3) => {
            return `${$2}?module=true${$3}`;
          });
        }
        // 修改umi的useDispatch 没有上下文环境,这段代码可以根据自己项目情况,判断是否需要
        if (code.indexOf('useDispatch()') > -1) {
          result = `import { useDispatch as viteUseDispatch, useSelector as viteUseSelector } from 'react-redux' \n` + result;
          // 全局替换
          result = result.replace(/useSelector\(/g, "viteUseSelector(")
          result = result.replace(/useDispatch\(\)/g, "viteUseDispatch()")
        }
        // 修改umi的useDispatch 没有上下文环境,这段代码可以根据自己项目情况,判断是否需要
        // if (code.indexOf(`connect(`) > -1 && id.indexOf('.tsx') > -1) {
        //   result = `import { connect as viteconnect } from 'react-redux' \n` + result;
        //   // 全局替换
        //   result = result.replace(/connect\(/g, "viteconnect(")
        // }

        // 在main.tsx 中增加路由相关
        if (id.indexOf(`/vite-project/main.tsx`) > -1) {
          result = result.replace('let routerConfig = [];', routerStr);
        }
        return {
          code: result,
          map: null,
          warnings: null,
        };
      },
    },

    react(),
    // require插件
    vitePluginRequire({
      // @fileRegex RegExp
      // optional:default file processing rules are as follows
      // fileRegex:/(.jsx?|.tsx?|.vue)$/
    }),
    // mock数据插件
    viteMockServe({
      mockPath: './mock',
      localEnabled: localEnabled, // 开发打包开关
      prodEnabled: prodEnabled, // 生产打包开关
      // 这样可以控制关闭mock的时候不让mock打包到最终代码内
      injectCode: `
        import { setupProdMockServer } from './mockProdServer';
        setupProdMockServer();
      `,
      logger: false, //是否在控制台显示请求日志
      supportTs: true, //打开后,可以读取 ts 文件模块。 请注意,打开后将无法监视.js 文件
    }),
  ],
  define: {
    process: {
      env: {
        __IS_SERVER: null,
        isVite: true,
      },
    },
  },
  css: {
    // 解决cssmodule
    // @ts-ignore
    isModule(id) {
      if (id.indexOf('module=true') !== -1) {
        return true;
      }
    },
    preprocessorOptions: {
      less: {
        modifyVars: {
          'primary-color': '#ff8420',
          'link-color': '#2DB7F5',
          'border-radius-base': '4px',
          'text-color': '#666',
          'font-size-base': '14px',
        },
        javascriptEnabled: true,
      },
    },
  },
  resolve: {
    preserveSymlinks: true,
    alias: {
      '@': path.resolve(__dirname, '../src'),
      '@@': path.resolve(__dirname, '../src/.umi'),
    },
  },
  // server:{
  //   hmr:{
  //     overlay: false
  //   }
  // }
});

进行到这里,对整个项目的改造大部分已经完成了,剩下就是处理一些报错,因为毕竟原来的项目有各种各样的逻辑,和配置,下面我罗列了我遇到的一些错误和解决办法,大家可以参考。

问题列表

1.首先需要在主项目安装less、和react-router,因为大家的项目可能有一些已经安装过了,就没在脚手架自动安装,react-router可以自行选择是v5版本还是v6版本

2.icon.png

这个问题是antd3.x导致的问题,4.x版本没有这个问题,一直没有找到好的解决办法,只能去antd的源码里加一个安全判断。如果大家有什么更好的解决办法,可以评论区告诉我。 image.png

3.require.png 这个问题可能是由于require插件导致的,这个插件目前可能不太完善,有的项目报错,有的不报错。把require插件注释掉。 但是。。。如果不使用这个插件,就得手动把项目里的require都替换成import。 image.png

大致的问题就这些,如果在接入过程中,遇到其他问题,可以仿照自定义插件,对项目做一些修改,这里只会修改vite生成的文件,对原有项目没有任何影响。

二、接入脚手架介绍

这里开发了一个脚手架方便大家使用,

  1. 全局安装脚手架:npm i add-vite-cli -g 或者 yarn安装。需要内网环境,git目前是在内网(文末有git链接)。
  2. 脚手架目前只支持react接入,后续考虑加入Vue,其实Vue和react接入的的思路都是一样的,后面会讲到。
  3. 进入项目根目录 addVite
  4. cd ./vite-project

现在做一些最基本的修改

  • mock数据的修改,这里需要引入之前项目的mock配置文件,下面注释内容是vite需要的mock格式,可能需要一个转化的过程,我下面自带了umi mock数据转化的方法。 image.png

  • 路由文件配置,目前配置文件也只支持路由的配置。routers写在哪个对象都行,因为我们的vite环境是开放的,很多东西都可以直接在vite目录修改,这里目前没有多做配置,之所以把路由提出来,也是为了和旧项目的兼容,防止出现两套路由配置的情况。 image.png

因为毕竟项目会有各种各样的架构,架手架也没办法考虑到所有的情况,甚至这里也只是考虑到一小部分,需要根据不同的项目,做出不同的配置和修改

git地址

脚手架地址:igit.58corp.com/weijie03/ad…

项目模板地址:igit.58corp.com/weijie03/re…

参考资料: zhuanlan.zhihu.com/p/399998544