记录改造一个2018年的react老项目到vite4.3全过程

lxf2023-12-21 04:40:02

前言:

每个项目的改造成本是不一样的,您遇到的问题,可能恰巧我没有遇到,so,不喜勿喷~

本文忽略:

  1. eslint, prettier, 因为影响不大,改造成本相对较小。
  2. test & mock环节, 自己配很简单 (推荐使用vitest, msw)

老项目核心配置

  • react@16.8.x
  • antd@3.26.x
  • webpack@4.28.x
  • typescript@3.9.x

改造过程

vite有个好处:

  1. 基本上xxx-loader不需要自己install,比如less-loader, ts-loader, sass-loader, 只需要装less, typescript, sass即可
  2. postcss/postcss-loader也不需要安装

用vite初始化一个空react-ts项目

此时配置很简洁,就一个react插件的配置

npm create vite@latest proj-to-vite -- --template react-ts

cd proj-to-vite

index.html改造

将老项目的index.html和vite的index.html进行合并, 并保留以下脚本

<!-- react是index.tsx -->
<script type="module" src="/src/index.tsx"></script>

同时后期运行项目,我遇到了:global not defined的问题

解决办法:在index.html中添加

+ <script>window.global = window;</script>
<script type="module" src="/src/index.tsx"></script>

postcss配置

主要作用是css前缀添加/flexbox bugs修复等(online demo)...

npm i postcss-flexbugs-fixes postcss-preset-env -D

举例:比如我们想要给filter, animation属性添加-webkit前缀 (此时设定范围需要包含chrome52)

.browserslistrc

>0.2%
Chrome >= 52

修改postcss.config.js(或者直接在vite.config.ts中配置)

module.exports = {
  plugins: [
    // https://github.com/luisrudge/postcss-flexbugs-fixes#readme
    require('postcss-flexbugs-fixes'), 
    require('postcss-preset-env')({
      // postcss-preset-env依赖了autoprefixer, 所以不需要单独安装
      autoprefixer: {
        // 或者在这里覆盖.browserslistrc
        // overrideBrowserslist: ['Chrome >= 52'],
        grid: true,
      },
    }),
  ],
};

路由异步加载

为了提升开发和用户体验,请异步加载路由,我使用的是:

@loadable/component

vite 配置修改

请仔细阅读以下每一步修改:

base路径修改

有些项目并不是部署在服务器的根目录下, 有可能在二级目录下,base修改为 ./

/// <reference types="vite/client" />

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

export default defineConfig(({ command, mode }) => {
  return {
    base: './',
    plugins: [react()],
  };
});

变量替换

慎用__DEV__这个名称, 因为有些第三方包源码中存在__DEV__判断,vite默认会匹配并执行所有替换,项目跑起来报错,如下:

记录改造一个2018年的react老项目到vite4.3全过程

换成不一样的名称即可,比如__ENV_DEV__

/// <reference types="vite/client" />

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

export default defineConfig(({ command, mode }) => {
  const isBuild = command !== 'serve';
  // load .env vars
  const env = loadEnv(mode, process.cwd(), '');

  return {
    ...,
    define: {
      __ENV_DEV__: !isBuild,
      // __DEV__: !isBuild, // 慎用
      
      // === other vars ===
      
      // MOCK_DEV定义在.env文件中
      __MOCK_IN_PRODUCTION__: env.MOCK_DEV === '1',
    },
  };
});

alias路径映射

路径简写,和webpack中的alias一样

/// <reference types="vite/client" />

import path from 'path';
import react from '@vitejs/plugin-react';
import { defineConfig, loadEnv } from 'vite';

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    resolve: {
      alias: [
        ...(
          Object.entries({
            '@': path.resolve('src'),
            '@config': path.resolve('src/config'),
            '@assets': path.resolve('src/assets'),
            '@components': path.resolve('src/components'),
          }).map(([key, val]) => ({ find: key, replacement: val }))
        ),
      ],
    },
  };
});

classnames/lodash改造

vite通过预处理,将多个模块进行合并,以减少开发环境模块请求数量, 从而提升开发体验

npm i classnames-es-ts lodash-es -S

# 代码还是正常 import { xx } from 'lodash';
/// <reference types="vite/client" />

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

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    resolve: {
      alias: [
        ...(
          Object.entries({
            ...,
            'classnames': 'classnames-es-ts',
            'lodash': 'lodash-es',
          }).map(([key, val]) => ({ find: key, replacement: val }))
        ),
      ],
    },
  };
});

build配置修改

/// <reference types="vite/client" />

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

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    build: {
      outDir: 'build', // 我们的项目输出文件夹要求是build
      assetsInlineLimit: 4096 * 2, // 转换base64的临界点(kb)
      chunkSizeWarningLimit: 3000, // 对于PC,500kb上限有点小
      sourcemap: !isBuild ? true : env.SOURCE_MAP === '1', // SOURCE_MAP定义在.env文件中
      manifest: true, // for PWA
    },
  };
});

react插件配置/html-plugin

项目使用了decorators, class-properties, reflect-metadata

切记:src/index.tsx入口处需要import 'reflect-metadata'

同时

# 因为使用了`decorators`, `class-properties`, `reflect-metadata`
# 请记得install:
# @babel/plugin-proposal-decorators
# @babel/plugin-proposal-class-properties
# babel-plugin-transform-typescript-metadata

npm i babel-plugin-transform-typescript-metadata @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
/// <reference types="vite/client" />

import react from '@vitejs/plugin-react';
import { defineConfig, loadEnv } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    plugins: [
      createHtmlPlugin(),
      // 我测试了很多遍,babel无法读取.babelrc,需要写在插件里面
      // TIPS: 这里不在需要presets配置,否则你会很难受!
      react({
        babel: {
          plugins: [
            'babel-plugin-transform-typescript-metadata',
            ['@babel/plugin-proposal-decorators', { legacy: true }],
            ['@babel/plugin-proposal-class-properties', { loose: true }],
          ],
        },
      }),
    ]
  };
});

antd按需加载

/// <reference types="vite/client" />

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

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    css: {
      preprocessorOptions: { // antd 的基本配置
        less: {
          javascriptEnabled: true,
          modifyVars: {
            '@primary-color': 'blue',
            // ...
          },
        },
        scss: {
          // 解决postcss插件产生的warning
          charset: false,
        },
      },
    },
    plugins: [
      ...,
      createStyleImportPlugin({
        resolves: [
          AntdResolve(),
        ],
        libs: [
          {
            libraryName: 'antd' ,
            resolveStyle: (name) => {
              return `antd/es/${name}/style`;
            },
          },
        ],
      }),
    ]
  };
});

让vite读取.browserslistrc配置

当然你可以直接使用vite-plugin-legacy,but, 它的打包速度非常的慢, 我们的项目加上它,打包时间从2分钟直接变成了6分钟,坑爹啊,果断放弃, 转到browserslist-to-esbuild

TIPS: vite默认只处理语法转换,比如async await/??/?./??=,函数(例如padStart)需要自行添加polyfill方案

以下设定可以转换 “async await/??/?./??=”语法, online demo

> 0.2%
Chrome >= 52
...
import browserslistToEsbuild from 'browserslist-to-esbuild'

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    build: {
      ...
      // 可读取.browserslistrc文件
      target: browserslistToEsbuild(),
    },
  };
});

目前测试.browserslistrc配置可以影响css的输出 (js方面,默认只会处理语法转换,比如async await/??/?./??=,函数(例如padStart)需要自行添加polyfill方案):

css方面,我的配置是chrome >= 52,因为我的浏览器范围设定很大,包含低版本,所以如下样式会被转换:

background-color: #0006;

/** to **/

background-color: rgba(0, 0, 0, 0.4);

注意:若css需要添加前缀 / flexbugs修复,请配置postcss.config.js

详细请转到:本文“postcss配置”部分

第三方包 require not defined 问题

添加transformMixedEsModules配置即可

...
import browserslistToEsbuild from 'browserslist-to-esbuild'

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    build: {
      ...
      commonjsOptions: {
        transformMixedEsModules: true
      },
    },
  };
});

typescript静态类型实时检测

建议先不开启它,等改造成功后再开启(因为会产生一些ts相关的类型error, 但不影响程序运行,需要我们手动fix

...
import TsChecker from 'vite-plugin-checker';

export default defineConfig(({ command, mode }) => {
  ...

  return {
    ...,
    plugins: [
      ...,
      // 里面也有eslint的配置
      TsChecker({ typescript: true }),
    ]
  };
});

本地开发环境启用https

安装步骤请戳