likes
comments
collection
share

把一个webpack + React项目改造成Vite,再加点ESLint+Prettier+Husky

作者站长头像
站长
· 阅读数 18

把一个webpack + React项目改造成Vite,再加点ESLint+Prettier+Husky

前言

目前的老项目是一个 Menorepo 项目, 使用 CRA 搭建和 Craco 配置的 React + TS 项目,因业务改造,需要抽离其中的一个 repo 独立成一个项目进行维护,如果只是抽离那自然是比较简单的,直接删除多余代码,把耦合部分抽出来合入独立项目即可。

老项目缺乏维护,热更新无效每次都要刷新页面,且启动 dev 和 build 的时间都很长。正好借着这次抽离,利用 Vite 重新搭建,再配合上 ESLint + Prettier + Husky + lint-staged 建设一下代码规范。

话不多说,开始~

Vite 安装配置与使用

vite 安装

# 我使用的包管理器是pnpm,yarn和npm切换成对应的命令就可以了
# 首先, 安装vite, 我这边使用的是5.3.3版本vite,node是 18.16.0
pnpm install -D vite
# 正常情况下,安装vite后使用vite构建项目会自动为你添加
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react-swc": "^3.7.0",
如果没有的话,自己-D安装一下

vite 创建项目

如下所示,进入你想创建项目的文件夹,运行 pnpm create vite, 会提示让你输入项目名,然后选择 React,然后选择 TS + SWC 即可,

SWC 是“Speedy Web Compiler”的缩写,它利用 Rust 语言的高性能特性,实现快速的代码转换和打包

项目就已经创建好了,你想看看演示 demo 的话,依次执行下方的三条命令就行了。

把一个webpack + React项目改造成Vite,再加点ESLint+Prettier+Husky

代码规范引入

你需要安装 ESLint 和 Prettier 的 VScode 插件,并且在 setting 里搜索 formatter 配置成Prettier。

如果你更关注怎么从 webpack 迁移到 vite,你可以直接看下一步的踩坑指南。

ESlint 配置

输入下面的命令,进行 Eslint 初始化,然后按下图进行配置选择

pnpm create @eslint/config

把一个webpack + React项目改造成Vite,再加点ESLint+Prettier+Husky

一般来说,安装好了是这样,当然 react 版本你可以自己切换哈。

把一个webpack + React项目改造成Vite,再加点ESLint+Prettier+Husky

我们应该关注项目根目录的三个文件,如果没有你可以自己创建:

.eslintrc.cjs

module.exports = {
	root: true,
	env: { browser: true, es2020: true },
	extends: [
		'eslint:recommended',
		'plugin:@typescript-eslint/recommended',
		'plugin:react-hooks/recommended',
		'plugin:prettier/recommended',
	],
	ignorePatterns: ['dist', '.eslintrc.cjs'],
	parser: '@typescript-eslint/parser',
	plugins: ['react-refresh', 'prettier', '@typescript-eslint', 'react-hooks'],
	rules: {  // 示例配置,你可以自己根据项目配置规则
		"prettier/prettier": "error",
		"arrow-body-style": "off",
		"prefer-arrow-callback": "off"
	},
}

eslint.config.js

import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js';

export default [
	{ files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] },
	{ languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
	{ languageOptions: { globals: globals.browser } },
	pluginJs.configs.recommended,
	...tseslint.configs.recommended,
	pluginReactConfig,
];

.eslintignore

# Ignore the packaged file
# 根据你的实际情况配置不需要ESlint校验的内容
public/*
node_modules/*
src/pages/app_portrait/*
# history
.history

Prettier 配置

输入下面的命令,安装Prettier

pnpm install prettier -D

然后在根目录创建 .prettierrc.cjs.prettierignore 配置文件

.prettierrc.cjs

module.exports = {
    printWidth: 120,
    tabWidth: 4,
    useTabs: false,
    semi: true,
    singleQuote: true,
    quoteProps: 'as-needed',
    jsxSingleQuote: false,
    trailingComma: 'all',
    bracketSpacing: true,
    bracketSameLine: false,
    arrowParens: 'always',
    proseWrap: 'preserve',
    htmlWhitespaceSensitivity: 'css',
    endOfLine: 'lf',
    embeddedLanguageFormatting: 'off',
    singleAttributePerLine: false,
    overrides: [
        {
            files: ['*.json'],
            options: {
                tabWidth: 2,
            },
        },
    ],
};

.prettierignore

package.json
dist/
build/
.editorconfig
.prettierignore
.prettierrc.cjs
.eslint.config.js

ESLint + Prettier 联合配置

先安装依赖

pnpm install eslint-config-prettier eslint-plugin-prettier -D

然后修改 .eslintrc.cjs 配置文件,其实上面我已经加入了这部分配置,现在我指出来是哪些配置:

extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
+   'plugin:prettier/recommended',
  ],
  
+ plugins: ['react-refresh', 'prettier', '@typescript-eslint', 'react-hooks'],
  
+    "rules": {
+       "prettier/prettier": "error",
+       "arrow-body-style": "off",
+       "prefer-arrow-callback": "off"
    }
}

如果你想要测试一下,你可以手动执行下面的命令,也可以添加在 package.json 的执行脚本中,执行 pnpm lint 即可

" script":{"lint":"eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"}

Husky 和 lint-staged 引入

引入 husky 和 lint-staged 可以在你 commit 的时候进行代码规范并且规范你的提交记录,便于规范和管理

还是首先安装依赖,然后执行命令

pnpm install husky -D
pnpm install lint-staged -D

执行如下命令会在根目录生成 .husky文件夹
husky install
然后给husky添加一个钩子
npx husky add .husky/pre-commit "npm run lint-staged"
然后你还需要在package.json 增加一个脚本
"scripts": {
    "dev": "vite",
    "build": "node --max_old_space_size=8192 node_modules/vite/bin/vite.js build --mode production",
    "lint": "eslint . --ext ts,tsx,js,jsx --report-unused-disable-directives --quiet",
    "lint-fix": "eslint . --fix --ext ts,tsx,js,jsx --report-unused-disable-directives --quiet",
    "preview": "vite preview",
    "prepare": "husky install",
+    "lint-staged": "lint-staged"
  },
然后在根目录添加一个 .lintstagedrc 文件
目录根据你的实际情况配置哈
{
    "src/**/*.{js,jsx,ts,tsx}": ["prettier --w", "eslint --fix --quiet", "eslint --quiet"],
    "common/**/*.{js,jsx,ts,tsx}": ["prettier --w", "eslint --fix --quiet", "eslint --quiet"]
}
这样每次提交代码前都会进行 Lint 检查。使用lint-staged是为了只对对改动的文件进行lint,而不是全量lint

迁移踩坑指南

在这里涉及到的文件内容等,我都尽量放上全量的配置,后续的坑如果有涉及的,我会指出是修改了什么配置。

vite.config.ts 配置和迁移

import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
import * as fs from 'fs';
import * as dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import vitePluginImp from 'vite-plugin-imp';
// import legacy from '@vitejs/plugin-legacy';

const __dirname = dirname(fileURLToPath(import.meta.url));
export default ({ mode }: any) => {
    const env = dotenv.parse(fs.readFileSync(`.env.${mode}`));
    return defineConfig({
        base: '/appname',
        plugins: [
            react(),
            vitePluginImp({
                libList: [
                    {
                        libName: 'antd',
                        style: (name: any) => `antd/es/${name}/style`,
                    },
                ],
            }),
            // legacy({
            //     targets:
            //         '>0.3%, edge>=79, firefox>=67, chrome>=52, safari>=12, iOS>=12, samsung>=20, opera>=90, op_mini all',
            //     polyfills: true,
            //     additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
            // }),
        ],
        resolve: {
            alias: [
                { find: '@', replacement: resolve(__dirname, './src') },
                { find: '@common', replacement: resolve(__dirname, './common') },
                { find: 'utils', replacement: resolve(__dirname, './src/utils') },
                { find: 'pages', replacement: resolve(__dirname, './src/pages') },
                { find: /^~/, replacement: '' },
            ],
        },
        css: {
            preprocessorOptions: {
                less: {
                    // 支持内联 JavaScript
                    javascriptEnabled: true,
                },
            },
        },
        build: {
            chunkSizeWarningLimit: 1500,
            target: ['es2015'],
            cssTarget: ['es2015', 'chrome58', 'edge16', 'firefox57'],
            minify: 'terser',
            terserOptions: {
                compress: {
                    //  生产环境时移除console
                    drop_console: true,
                    drop_debugger: true,
                },
            },
        },
        server: {
            host: 'localhost',
            port: 3000,
            open: false,
            cors: true,
            proxy: {
                '/appname/api': {
                    secure: false,
                    changeOrigin: true,
                    target: env.VITE_GATEWAY_URL,
                },
            },
        },
    });
};

可以看到一共有 base plugins resolve css build server 等几个配置,如果目前有一些配置你不明白,你可以继续往下看,因为我相信你会遇到和我一样的问题,到时候回来看他们解决了什么就行了。

base: app 的根路径,你没有特殊需求可以不配置,就是

plugins: 插件配置,引入插件,react 和 antd 按需加载

resolve: alias 配置别名映射路径

css: 样式配置

build:vite 打包的配置

server: dev 环境下代理配置等

项目入口修改

vite 的入口 index.html 是在根目录中的,我们把在 CRA 项目中位于 public/index.html 的文件移出来,当然你需要进行修改。

主要关注两点:

静态文件引入部分,你放在 public 文件夹下,vite 会自己寻找,需要去掉 %PUBLIC_URL%

另外一个需要注意的就是 <script type="module" src="/src/index.tsx"></script> ,vite 采用 esModule 引入,我们需要指定文件入口。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="你的软件名称"
    />
    <title>网页title</title>
  </head>
  <body>
    <div id="root"></div>
    <script>
      if (typeof window.global === 'undefined') {
        window.global = window;
      }
    </script>
    <script type="module" src="/src/index.tsx"></script>
  </body>
</html>

Antd 引入报错,(Internal server error: '~antd/lib/style/themes/default.less' wasn't found.)

当我们在 vite 中引入 antd,并在 vite.config.ts 配置按需引入,你可能会遇到Internal server error: '~antd/lib/style/themes/default.less' wasn't found 或者 XX.less 无法找到的问题,关键在于 vite 没有成功解析 ~antd/,当然这是针对 antdV4 版本会有这个问题,V5 已经是 CSSinJS 了。你需要在 vite.config.ts 进行如下的配置:

首先 安装依赖 pnpm install vite-plugin-imp -D,然后分别在 plungins、css、resolve 中进行如下配置,

可以查看 github 这一个 issue GITHUB ISSUE

plugins: [
  react(),
  vitePluginImp({
    libList: [
      {
        libName: 'antd',
        style: (name: any) => `antd/es/${name}/style`,
      },
    ],
  }),
css: {
    preprocessorOptions: {
      less: {
        // 支持内联 JavaScript
        javascriptEnabled: true,
      },
    },
},  
resolve: {
      alias: [
        { find: /^~/, replacement: '' },
      ],
},    

项目中别名配置错误,(Failed to resolve import "@/common" from "src/index.tsx". Does the file exist?)

报错 :Failed to resolve import "@/common" from "src/index.tsx". Does the file exist

这个问题通常是因为你在代码中使用了类似 import xxx from '@common/utils' 或者 import xxx from '@/pages'这样的引入方式,可能是因为以前是 menorepo 项目,也可能是以前 webpack 就已经为你配置好了,所以你需要在 vite 中根据你的文件夹结构配置一下别名:

类似的我进行了如下配置:

resolve: {
  alias: [
    { find: '@', replacement: resolve(__dirname, './src') },
    { find: '@common', replacement: resolve(__dirname, './common') },
    { find: 'utils', replacement: resolve(__dirname, './src/utils') },
    { find: 'pages', replacement: resolve(__dirname, './src/pages') },
    { find: /^~/, replacement: '' },
  ],
},

报错 global 无法识别(ReferenceError: global is not defined.)

如果不出意外,你可能会遇到报错 ReferenceError: global is not defined。告诉你 global 没有定义,通常 global 是 node 环境下的,window 下找不到,我们可以添加一段 polyfill 来解决,在你的 index.html 中插入

<script>
  if (typeof window.global === 'undefined') {
    window.global = window;
  }
</script>

Vite 老版本浏览器兼容问题 解决白屏问题

如果你用的老版本的浏览器,你应该会遇到兼容性问题:

默认情况下,Vite 的目标是能够 支持原生 ESM script 标签、

支持原生 ESM 动态导入 和 import.meta 的浏览器: Chrome >=87 Firefox >=78 Safari >=14 Edge >=88 你也可以通过 build.target 配置项 指定构建目标,最低支持 es2015。

这是 vite 的技术基础,我们可以通过配置来解决,但需要注意的是 这部分的配置只是在 build 的时候生效,dev 开发环境你仍然需要使用 vite 支持的浏览器。

兼容性问题分为两种,你使用了更高级的语法,浏览器不支持,这个时候你需要把他翻译成浏览器支持的语法就行,那么你只需要在 build 中进行如下配置,打开你的 vite.config.ts,将 target 设置为 es2015,这厮 vite 最低支持的版本了,当然你也可能需要设置 cssTarget 的值,不然你可能会遇到某些 antd 组件很诡异,例如 switch 看不到开关,modal 点击后弹不出来,其实都是 css 样式不支持,例如 modal 里设置了 inset: 0,其实就等价于 top:0;right:0;bottom:0,left:0,但是 老浏览器是不支持的。

你还可以配置 terser 来压缩你的代码,记得要先装依赖哈。

build: {
    chunkSizeWarningLimit: 1500,
    target: ['es2015'],
    cssTarget: ['es2015', 'chrome58', 'edge16', 'firefox57'],
    minify: 'terser',
    terserOptions: {
    compress: {
      //  生产环境时移除console
        drop_console: true,
        drop_debugger: true,
      },
    },
},

如果到这里,你还没有解决问题,那可能是你的项目中使用了老浏览器不支持的 API,比如 Promise.allSettled 这个时候就只能使用 polyfill 或者 babe 了,vite 官方为我们提供了一个插件,可以通过配置这个插件来支持老旧的浏览器。

首先安装依赖 pnpm install @vitejs/plugin-legacy -D,然后配置 legacy 即可,一般情况下,你使用默认配置的 legacy()就可以解决问题,他会加载默认配置进行打包,如果还没解决,你就需要根据你需要适配的浏览器版本进行一个设置,这里如果你配置了 .browserslistrc, 那 vite 会去读这个配置,未设置则是默认值,当然那你也可以显示在这里指定。

import legacy from '@vitejs/plugin-legacy';

plugins: [
  react(),
  vitePluginImp({
    libList: [
      {
        libName: 'antd',
        style: (name: any) => `antd/es/${name}/style`,
      },
    ],
  }),
  legacy({
       targets:
           '>0.3%, edge>=79, firefox>=67, chrome>=52, safari>=12, iOS>=12, samsung>=20, opera>=90, op_mini all',
       polyfills: true,
       additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
  }),
],
转载自:https://juejin.cn/post/7390416395148722185
评论
请登录