Vite|Vite + React + TypeScript 构建标准化react应用

背景 之前公司项目采用的是umi脚手架一体化构建工具,得益于对webpack与各框架的集成和封装,使得快速上手的能力大大加强,但是随着项目的不断迭代与功能增加,依赖的库也是越来越多,目前最明显的感受就是每次启动与打包构建的时长,往往是好几分钟~,热更新有时也要耗费数秒,对于开发效率与体验影响很大。。。

之前尤大发布vite1.0时也了解了一点,最明显感受就是一个字“快”~,不过一直没仔细研究过,只知道是基于esbuildrollup,目前vite2.0已经发布,完全作为一个独立的构建工具,对react等其他非vue框架有着很好的支持。最近也算忙里偷闲,算是稍微研究了一下基本知识。本篇文章记录我以vite构建react的过程及细节,后续会继续深入研究输出vite相关系列文章,敬请期待~
目标 我对构建项目的要求如下:

  • 支持Typescript
  • 支持ReactJSX语法
  • 支持ES6语法
  • 支持Less module
  • 支持EslintPrettierPre-commit hook
  • 支持HMR快速热更新
  • 支持Antd按需引入与主题样式覆盖
  • 支持Proxy代理、alias别名
  • 兼容传统浏览器
  • 开发启动速度要够快,以秒计算
  • 支持懒加载和chunk分割
介绍 前置条件之一
浏览器原生支持 ES 模块。
特点
  • 基于原生 ES 模块,即 ,做到快速加载
  • 使用 Esbuild 预构建依赖 (本地开发环境)
  • 使用 Rollup 打包代码(线上生产环境)
  • HMR 是在原生 ESM 上执行的
  • 利用对 HTTP 头信息的控制,优化缓存与重加载,高效率利用浏览器能力。
  • 开箱即用,内置多种支持,如:Typescript支持、JSX支持、CommonJSUMD兼容、css预处理器与css modules
vite对模块的分类
  • 依赖:
    • 在开发时不会变动的纯 JavaScript。(如第三方依赖antdlodash等)
    • 在该场景采用具有优势的 Esbuild 处理。(大量模块的组件库、CommonJS格式的文件等)
  • 源码:
    • 通常包含一些并非直接是 JavaScript 的文件,需要转换,时常会被编辑。(JSXCSSVue/React组件等)
    • 会根据路由拆分代码按需加载模块
    • Vite 以 原生 ESM 方式提供源码
概念
预构建:将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。简单来说就是尽量合并与减少请求。
例如:当我们引入的一个第三方模块依赖了大量其他模块时,在不合并请求的情况下,会请求上百次不等,造成网络拥塞影响性能,而通过预构建合并后只需要一个请求即可。(lodash-es 有超过个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!通过预构建 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!)
相较于传统的 webpack 构建工具,先打包构建所有的依赖和项目代码,然后再启动开发服务器。Vite 则利用浏览器对 ESM 的支持,先启动开发服务器,然后再根据代码执行按需加载剩下所需的对应模块。
因为缓存,在我们第二次启动时几乎可以做到秒开!非常的可怕~
官网图
官网图很清晰的描绘了区别:
Vite|Vite + React + TypeScript 构建标准化react应用
文章图片

Vite|Vite + React + TypeScript 构建标准化react应用
文章图片

步骤 项目初始化
官方支持React模板预设有:reactreact-ts,因为我需要Typescript,所以直接用这个模板,省事了~
# npm 6.x npm init @vitejs/app my-react-app --template react-ts# npm 7+, 需要额外的双横线: npm init @vitejs/app my-react-app -- --template react-ts# yarn yarn create @vitejs/app my-react-app --template react-ts

引入react三件套
这里有兴趣的可以尝试下 pnpm 包管理工具,安装速度很快,不了解的可以查看pnpm官方文档,相较于传统的npmyarn工具都有很好的性能提升与使用体验,这里不做过多介绍,放张图大家体会下~
注:就目前我的使用情况来看大部分场景几乎都没问题的,不过还是存在一小部分问题。如:安装precommit后Git hooks不生效等。
Vite|Vite + React + TypeScript 构建标准化react应用
文章图片

安装依赖
# pnpm pnpm add react react-dom react-router-dom # or npm npm i react react-dom react-router-dom

创建页面 在src目录下创建pages目录放置页面组件模块,然后我们简单写两个页面测试下:
// pages/Home/index.tsx import React from 'react'; const Home: React.FC = () =>Home ; export default Home; // pages/About/index.tsx import React from 'react'; const About: React.FC = () =>About ; export default About;

修改文件App.tsx
// App.tsx import React, { Suspense } from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import Home from './pages/Home' import About from './pages/About'const App = () => { return ( loading}> ); }; export default App;

Vite|Vite + React + TypeScript 构建标准化react应用
文章图片

Vite|Vite + React + TypeScript 构建标准化react应用
文章图片

配置路由/界面
新建layouts组件,主要用于区别渲染登录注册页面与布局界面:
layouts/BasicLayout.tsxlayouts/UserLayout.tsx
这里就不一一做展示了,详细代码见仓库,地址贴在下面了。
新建路由配置文件router/index.ts:
import React from 'react'; const Page404 = React.lazy(() => import('../pages/404')); const Home = React.lazy(() => import('../pages/Home')); const Login = React.lazy(() => import('../pages/User/Login')); const Register = React.lazy(() => import('../pages/User/Register')); const routes: IRoute[] = [ { path: '/user', component: React.lazy(() => import('../layouts/UserLayout')), meta: { title: '用户路由', }, redirect: '/user/login', children: [], }, { path: '/', component: React.lazy(() => import('../layouts/BasicLayout')), meta: { title: '系统路由', }, redirect: '/home', children: [ { path: '/home', meta: { title: '首页', icon: 'home', }, component: , }, { path: '/about', meta: { title: '关于', icon: 'about', }, component: , }, ], }, ]export default routes;

创建store状态管理文件
react hooks诞生后,大部分场景使用hooksprops进行状态管理基本可以满足多数需求,少部分全局应用信息与用户信息等需要全局状态管理的,这里我觉得也不需要完整引入一个reduxmobx等这种库。当然还要结合具体场景和公司技术栈确定,较大型和复杂的项目等视情况而定~
我这里使用了zustand做了简单配置,使用起来也是比较的简单,详情参见官方文档
// 创建store import create from 'zustand'const useStore = create(set => ({ bears: 0, increasePopulation: () => set(state => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }) }))

// 组件绑定 function BearCounter() { const bears = useStore(state => state.bears) return {bears} around here ... }function Controls() { const increasePopulation = useStore(state => state.increasePopulation) return }

引入Antd组件库并配置按需加载
这里就不废话了,直接展示如何在vite中配置antd的按需加载,首先我们安装一个插件:
pnpm add vite-plugin-imp -D
然后在vite.config.ts文件的plugins中添加配置vitePluginImp
这里我们顺势再引入一个less-vars-to-js包,less-vars-to-js可以将less文件转化为json键值对的形式,当然你也可以直接在modifyVars属性后写json键值对。这样做的好处是可以把全局配置统一放到config文件进行管理,方便维护。
自定义覆盖主题色
config/variables.less // @primary-color: '#ff7875';
import reactRefresh from '@vitejs/plugin-react-refresh'; import lessToJS from 'less-vars-to-js'; import path from 'path'; import { defineConfig } from 'vite'; // vite-plugin-imp 该插件按需加载存在部分样式丢失的情况 // import vitePluginImp from 'vite-plugin-imp'; // 由于 vite 本身已按需导入了组件库,因此仅样式不是按需导入的,因此只需按需导入样式即可。 import styleImport from 'vite-plugin-style-import'; const themeVariables = lessToJS( fs.readFileSync(path.resolve(__dirname, './config/variables.less'), 'utf8'), ); export default defineConfig({ base, plugins: [ reactRefresh(), // 配置按需引入antd // vitePluginImp({ //libList: [ //{ //libName: 'antd', //style: (name) => `antd/es/${name}/style/index.less`, //}, //], // }), styleImport({ libs: [ { libraryName: 'antd', esModule: true, resolveStyle: (name) => { return `antd/es/${name}/style/index`; }, }, ], }), ], css: { preprocessorOptions: { less: { // 支持内联 JavaScript,支持 less 内联 JS javascriptEnabled: true, // 重写 less 变量,定制样式 modifyVars: themeVariables, }, }, } })

Vite|Vite + React + TypeScript 构建标准化react应用
文章图片

环境变量
方案一 通过 --mode 注入配置参数以匹配测试/开发环境等。
我们修改下package.json文件:
scripts: { "build:beta": "vite build --mode beta", "build:release": "vite build --mode release", "build:legacy ": "vite build --mode legacy ", }

node环境下直接process.argv即可获取到,我们可以在vite.config.ts中打印信息查看
// vite.config.ts import {defineConfig} from 'vite'const env = process.argv[process.argv.length - 1]; console.log('env:', env); export default defineConfig({})

方案二 使用函数式写法配置动态获取环境变量等参数
首先在根目录下创建.env文件
# port VITE_PORT = 3100# HTTP API VITE_HTTP_API = http://127.0.0.1:8000# title VITE_APP_TITLE = Vite React App

然后我们调整下 vite.config.ts 文件
// 函数式配置 import { loadEnv } from 'vite'; import type { ConfigEnv, UserConfig } from 'vite'; export default ({ command, mode }: ConfigEnv): UserConfig => { const root = process.cwd(); const env = loadEnv(mode, root); console.log('env', env); console.log('command', command); console.log('mode', mode); }

组件内可通过import.meta.env获取,我们可以在Home/index.tsx中打印信息查看
// Home/index.tsx import React from 'react' import { Button } from 'antd'const Home: React.FC = () => { console.log('import.meta.env', import.meta.env) return } export default Home

alias 别名设置
export default defineConfig({ ... resolve: { alias: [ { find: /^~/, replacement: path.resolve(__dirname, './') }, { find: '@', replacement: path.resolve(__dirname, 'src') }, ], // alias: { //'~': path.resolve(__dirname, './'), // 根路径 //'@': path.resolve(__dirname, 'src') // src 路径 // } } ... })

proxy代理配置
export default defineConfig({ ... server: { port: 8080, // 开发环境启动的端口 proxy: { '/api': { // 当遇到 /api 路径时,将其转换成 target 的值 target: 'http://127.0.0.1:8080/api/', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), // 将 /api 重写为空 }, } } })

兼容传统浏览器
vite默认只支持现代浏览器,即ES module,对于IE等老版本浏览器,可使用@vitejs/plugin-legacy插件在build时进行polyfill
yarn add @vitejs/plugin-legacy -D
// vite.config.js import legacy from '@vitejs/plugin-legacy'export default { plugins: [ legacy({ targets: ['ie >= 11'], additionalLegacyPolyfills: ['regenerator-runtime/runtime'] }) ] }

Eslint、Prettier、Stylelint 代码检查与格式化
使用eslint这种代码检查工具是为了更好的规范代码和写法,列如:禁用var,建议使用constlet,以及配合TS使用时对各种类型规范等,对于代码优化与后期维护都很方便。
而格式化对于多人协同开发时,统一代码风格很有用。
VSCode 集成 插件规则遵循就近原则,会优先启用本项目下的配置文件,当前配置会覆盖全局配置,如果没有就采用全局配置。
source.fixAll.eslint 开启后可使编辑器按照eslint规则 auto fix
vscode 中安装 EslintPrettier 插件,并开启功能,推荐在全局配置(或当前工作目录下)中添加:
// file: vscode setting.json // onSave "editor.formatOnSave": true, //每次保存的时候自动格式化 // eslint "eslint.alwaysShowStatus": true,// 总是在 VSCode 显示 ESLint 的状态 "eslint.quiet": true,// 忽略 warning 的错误 "editor.codeActionsOnSave": {// 保存时使用 ESLint 修复可修复错误 "source.fixAll": true, "source.fixAll.eslint": true }, // prettier "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }

为了方便配置,可以使用 VsCode插件 setting sync(该插件通过GitHub Gist ID 绑定,实现云同步上传下载),通过GitHub Gist ID云同步配置和插件。
git commit 集成
git commit原理:
git在执行的过程会提供相关钩子函数,如commitpush时,在项目目录下的.git目录下的hooks文件夹下存在相关git生命周期的钩子函数配置,我们可以手动自己配置,只需删除后缀.sample即可,不过因为这是本地文件,对于团队协同开发同步配置不太友好,所以还是建议采用第三方库统一管理。
hooks文件夹./.git/hooks/
Vite|Vite + React + TypeScript 构建标准化react应用
文章图片

  1. 我们先在项目根目录下创建.husky文件夹
  2. 然后添加文件pre-commit
    #!/bin/sh . "$(dirname "$0")/_/husky.sh" npx --no-install lint-staged

  3. package.json 中添加如下配置:
    script 配置 postinstall是为了确保在安装依赖完成后,npm可以执行postinstall钩子,使husky安装配置文件到.husky文件下,npm在安装执行的过程提供了一些生命周期钩子,postinstall、prepare等。
    { "script": { "postinstall": "husky install", "lint:fix": "eslint --cache --ext .js,.jsx,.ts,.tsx --no-error-on-unmatched-pattern --quiet --fix ./src", "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", }, "lint-staged": { "**/*.{js,jsx,tsx,ts,json}": [ "npm run lint:fix", "git add --force" ], "**/*.{less}": [ "npm run lint:style", "git add --force" ] }, }

  4. 再安装 huskylint-staged 依赖库,
在提交代码时,lint git 暂存区的代码,若 lint 不通过则中断提交,保证问题代码不进入代码仓库。
目录结构
├── dist// 默认的 build 输出目录 ├── config// 全局配置文件 └── src// 源码目录 ├── assets// 公共的文件(如image、css、font等) ├── components// 项目组件 ├── constants// 常量/接口地址等 ├── layout// 全局布局 ├── routes// 路由 ├── store// 状态管理器 ├── utils// 工具库 ├── pages// 页面模块 ├── Home// Home模块,建议组件统一大写开头 ├── ... ├── App.tsx// react顶层文件 ├── main.ts// 项目入口文件 ├── typing.d.ts// ts类型文件 ├── .editorconfig// IDE格式规范 ├── .env// 环境变量 ├── .eslintignore// eslint忽略 ├── .eslintrc// eslint配置文件 ├── .gitignore// git忽略 ├── .npmrc// npm配置文件 ├── .prettierignore// prettierc忽略 ├── .prettierrc// prettierc配置文件 ├── .stylelintignore// stylelint忽略 ├── .stylelintrc// stylelint配置文件 ├── index.html// 项目入口文件 ├── LICENSE.md// LICENSE ├── package.json// package ├── pnpm-lock.yaml// pnpm-lock ├── postcss.config.js// postcss ├── README.md// README ├── tsconfig.json// typescript配置文件 └── vite.config.ts// vite

项目地址 项目Github地址
本文掘金地址
参考
vitejs
Vite 2.0 + React + Ant Design 4.0 搭建开发环境
zustand
如何为你的 Vue 项目添加配置 Stylelint
从零配置 Eslint + Prettier + husky + lint-staged 构建前端代码工作流
ESLint 使用指南
【Vite|Vite + React + TypeScript 构建标准化react应用】深入浅出eslint——关于我学习eslint的心得
在 pre-commit 的钩子中运行 npm script
@umijs/fabric

    推荐阅读