如何将大型react项目开发效率大幅提升(vite)

117 阅读5分钟

一,背景

随着项目迭代,项目的体积会越拉越大,启动本地项目以及修改代码编译时间较长,大大影响了开发效率。新技术vite能很好的解决上述问题。本地开发时,慢的原因主要在webpack的编译阶段,即使开发一个单独小页面,webpack也会将整个项目全部打包。vite本质是用现代浏览器自带的esm来取代编译打包的过程。

vite介绍:juejin.cn/post/695018…

二,数据提升(个人项目数据提升)

(1)项目启动154s -> 1s以内

(2)修改代码反应时间,浮动较大,大约5-40s -> 1s以内

三,整体思路

引入初期考虑webpack和vite并行存在在项目中,开发态使用vite,编译打包上线使用webpack,主要考虑到:

(1)当前webpack配置项较多,贸然迁移上线打包流程容易出现错误,并且也没有收益(主要收益还是在开发态)。

(2)切换方便, vite作为一个新技术,生态并没有特别强大,后续的联邦模块之类的优化不一定可以很好的支持,之后如果有什么问题,方便快速切换回webpack,只需要删除一些vite配置文件即可。

(3)保留webpack原有能力作为兜底,确保稳定性。

四,引入步骤

(1)修改package.json,引入vite,并添加dev


"devDependencies": {
   ...
   "vite": "^3.1.0",
   "@vitejs/plugin-react": "^2.1.0"
}

"scripts": {
    ...
    "dev": "vite"
 }

(2)根目录下创建vite.cnofig.ts配置文件(默认配置)

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react(),
  ]
})

(3)添加alias

export default defineConfig({
  ...
  resolve:{
  alias: {
    "@": path.resolve(__dirname, "src"),
    "view": path.resolve(__dirname, "src/view"),
    "components": path.resolve(__dirname, "src/components"),
    "assets": path.resolve(__dirname, "src/assets"),
    "store": path.resolve(__dirname, "src/store"),
    "mixins": path.resolve(__dirname, "src/mixins"),
  },
},
})

(4)添加环境变量

vite默认的环境变量文件为.env.development和.env.production,在使用的时候通过 META.xx。这里和我们当前使用的webpack有所不同

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

const preFix = '/xxx'

export default defineConfig({
  plugins: [
    react(),
  ],
  // 全局可以使用process
  define : { 
  'process.env'  : { 
      'PROJECT_PREFIX'  : preFix,  
  } 
 }, 
})

(5)添加本地server,port保持和当前项目一致

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

const preFix = '/xxx'

export default defineConfig({
  plugins: [
    react(),
  ],
  define: {
    'process.env': {
      'PROJECT_PREFIX': preFix
    }
  },
  server : { 
 host :  "0.0.0.0"  , 
 port : 3100 , 
 }, 
})

(6)添加css预处理配置项

scss,这里基本和webpack一致,主要是添加scss的全局变量

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

const preFix = '/xx'


export default defineConfig({
  plugins: [
    react(),
  ],
  define: {
    'process.env': {
      'PROJECT_PREFIX': preFix,
    }
  },
  server: {
    host: "0.0.0.0",
    port: 3100,
  },
  css: {
    preprocessorOptions: {
      //define global scss variable
      scss : { 
         additionalData :  ` 
         @use '@/styles/var.scss'; 
         @use '@/styles/mixin.scss'; 
      `  , 
 }, 
    },
    modules: {
      localsConvention: 'camelCaseOnly', // 使用驼峰形式
    },
  },
})

less

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const { adapter, Flags } = require('@okee-fe/theme-adapter');

const preFix = '/xx'



export default defineConfig({
  plugins: [
    react(),
  ],
  define: {
    'process.env': {
      'PROJECT_PREFIX': preFix,
    }
  },
  server: {
    host: "0.0.0.0",
    port: 3100,
  },
  css: {
    preprocessorOptions: {
      //define global scss variable
      scss: {
        additionalData: `
        @use '@/styles/var.scss';
        @use '@/styles/mixin.scss';
        `,
      },
      less : { 
         modifyVars : xx, 
     } 
    },
    modules: {
      localsConvention: 'camelCaseOnly', // 我们使用驼峰形式
    },
  },
})

(7)添加html入口文件

我们当前项目基本都是mpa的方式,这种方式在打包后会有多个html文件。使用html-webpack-plugin,对所有mpa使用统一模版。当前vite也有相应的解决方案,如vite-plugin-html,但是个人使用后发现问题比较多。所以这里需要在每个pages下添加单独的html文件。这里先放在pages目录下,后续需要自己编写rewrite plugins。

--src
  --pages
    --home-page
      index.tsx
     +index. html

模版内容

<!DOCTYPE html>
<html lang="en" style="height: 100%; background: #f6f6f6;">
  <head>
    <meta charset="utf-8" />
<!--    <link rel="shortcut icon" href="%PROJECT_PREFIX%/favicon.png" />-->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <title>xxe</title>
  </head>
  <body style="height: 100%;">
    <noscript>You need to enable JavaScript to run this app.</noscript>
<!--    <div style="display: none"><%= htmlWebpackPlugin.options.context.commitId %></div>-->
    <div id="root" style="height: 100%;"></div>
    <script src="/xx/index.tsx" type="module"></script>

    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

(8)添加runtime publicPath和配置文件根路径

import path from 'path'
...
export default defineConfig({
  ...
  root: path.resolve(__dirname, 'src/pages'),
  base: preFix,
})

(9)重写第三方错误代码

因为react-virtualized第三方包代码有问题,并且vite没有经过treeshaking删除多余代码,此时会报错

查阅相关三方的issue,发现尤雨溪也提出了这个问题

下面也给出了解决方案,使用plugins,重写wrong_code

(10)编写plugins,支持mpa

vite官网说是通过配置rollup来支持mpa,但是实际使用发现完全没有效果,应该没有支持dev模式,而是只支持了preview模式。没有找到较好的解决方案,自己编写rewrite plugins解决

这里主要参考webpack常用的connect-history-api-fallback来变更devServer的逻辑,没有直接使用该组件是因为这里vite并没有经过打包,返回的url要附上/nested/的格式。

webpack配置:

这里如果发现前端请求的是html文件。会对其进行处理,如分割参数,添加'/'后缀,重定向等。

import type { PluginOption } from 'vite';

function acceptsHtml(header: string) {
  const options = ['text/html'];
  for (let i = 0; i < options.length; i++) {
    if (header.indexOf(options[i]) !== -1) {
      return true;
    }
  }
  return false;
}

export default (rewrites: {
  from: RegExp
  to: string
}[]): PluginOption => {
  return {
    name: 'vite-history-api-fallback',
    configureServer(server) {

      server.middlewares.use((req: {
        headers: {
          accept: string
        };
        method: string;
        url: string;
      }, res, next) => {
        const headers = req.headers;
        const { url } = req;
        if (req.method !== 'GET' && req.method !== 'HEAD') {

          return next();
        } else if (!headers || typeof headers.accept !== 'string') {

          return next();
        } else if (headers.accept.indexOf('application/json') === 0) {

          return next();
        } else if (acceptsHtml (headers. accept ) ) {
          // html重定向
          const paramIndex = url.indexOf('?');
          // 有参数
          let preUrl = url;
          let params = '';
          if (paramIndex !== -1) {

            preUrl = url.substring(0, paramIndex);
            params = url.substring(paramIndex);
          }
          for(let i = 0; i < rewrites.length; i++) {
            const {from, to} = rewrites[i];
            if(from.test(preUrl)) {
              let urlTo = `${to}`;
              if(!urlTo.endsWith('/')) {
                urlTo = `${urlTo}/`
              }
              req.url = `${urlTo}${params}`

              return next();
            }
          }
        }
        return next();
      });
    }
  };
};

修改vite.config.ts,传入mpa重定向参数,使用该plugin

...
import devServerRouter from './config/vite-history-api-fallback'

const rewrites = [
  {from: /^/ xx/ xx1/ ?/, to: '/xx/xx1/'},
  {from: /^/ xx/ login/ ?/, to: '/xx/login/'},
  {from: /^/ xx/ xx2/ ?/, to: '/xx/xx2/'},
  {from: /^/ xx/ ?/, to: '/xx/'},
]


export default defineConfig({
  plugins: [
    ...
    devServerRouter(rewrites),
  ],
  ...

})

(11)成功启动项目,ms启动,ms修改

五,一些问题

(1)html统一模版,当前vite的html模版组件都不太好用,所以每添加一个mpa就需要在当前页面下添加一个index.html。后续考虑开发单独plugins统一模版。

(2)当前vite和webpack有些重复配置项,webpack打包时候必须使用cjs,而vite运行态必须使用esm,两者存在冲突。
(3)webpack对于yalc支持不足,如果引入yalc开发,需要考虑觉解方案。