Vite 使用指南

17,637 阅读8分钟

前言

Vite 是一个超快速的前端构建工具,推动着下一代网络应用的发展。

2024 年 11 月 26 日,Vite 6.0 正式发布,标志着 Vite 迈向新阶段的重要里程碑!

提示:关于Vite的更多介绍,请移步至 官网 >>

搭建项目

环境配置:

nodev22.11.0

pnpmv10.3.0

vitev6.1.0

# Vue
$ pnpm create vite <PROJECT-Name> --template vue-ts
# React
$ pnpm create vite <PROJECT-Name> --template react-ts

🔖 提示

  • 你可以使用 . 作为项目名称,在当前目录中创建项目脚手架。

  • 关于如何基于 Vite 创建项目,可以参考 这里 >>

vite.config.js

@See cn.vitejs.dev/config/

首先安装 Node.js 提供的类型定义的包 @types/node,解决 找不到模块 path 或其相对应的类型声明 的问题。

$ pnpm add -D @types/node

基本配置如下:

📖 Vue.js

import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "VITE_");
  return {
    plugins: [vue()],
  };
});

📖 React.js

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

// https://cn.vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd(), "VITE_");
  console.log(env)
  return {
    plugins: [react()],
  };
});

取别名

@See cn.vitejs.dev/config/shar…

vite.config.ts 中配置 resolve.alias ,使用 @ 符号代表 src

vite.config.js

import type { UserConfig, ConfigEnv } from 'vite';
import { defineConfig, loadEnv } from 'vite'
import { resolve } from 'node:path';
import react from '@vitejs/plugin-react'

// https://cn.vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {

  // -- 获取当前工作目录路径
  const root = process.cwd();
  const pathResolve = (path: string) => resolve(root, '.', path);
  // -- 获取环境变量
  const env = loadEnv(mode, root, "VITE_");
  console.log(env);
  return {
    resolve: {
      alias: {
        "@": pathResolve('src'),
      },
    },
    plugins: [react()],
  };
});

tsconfig.app.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
  },
}

提示:您可以根据需求配置,通常来说,配置 @/ 指向 src/* 目录即可。

开发服务器选项 & 代理

@See cn.vitejs.dev/config/serv…

server: {
  host: "0.0.0.0",
  port: 8888,
  strictPort: false,
  open: true,
  cors: true,
  proxy: {},
}

环境变量

@See cn.vitejs.dev/guide/env-a…

①. 在根目录新建 .env.development.env.production.env.test

$ touch .env.dev .env.prod .env.qa

大致内容如下:

[env.dev]
# -- 开发环境
VITE_ENV=dev;
# -- 接口请求地址
VITE_API_HOST=开发环境接口请求地址;
# -- 打包输出目录
VITE_OUT_DIR="dist"
# -- 基础路径,部署二级目录时填写,如:/my-app/
VITE_BASE=""

[.env.prod]
# -- 生产环境
VITE_ENV=prod;
# -- 接口请求地址
VITE_API_HOST=生产环境接口请求地址;
# -- 打包输出目录
VITE_OUT_DIR="dist"
# -- 基础路径,部署二级目录时填写,如:/my-app/
VITE_BASE=""

[.env.qa]
# -- 测试环境
VITE_ENV=qa;
# -- 接口请求地址
VITE_API_HOST=测试环境接口请求地址;
# -- 打包输出目录
VITE_OUT_DIR="dist"
# -- 基础路径,部署二级目录时填写,如:/my-app/
VITE_BASE=""

⚠️ 注意

  1. 环境变量名必须以 VITE_ 开头;
  2. 配置文件的内容请根据项目需要设置;

②. 修改 package.json 指令

为了使命令在 Windows 和 macOS/Linux 下都能正常运行,我们可以使用 cross-env

$ pnpm add -D cross-env

📖 Vue.js

{
  "scripts": {
    "dev": "cross-env VITE_CJS_IGNORE_WARNING=true vite --mode dev",
    "build:qa": "vue-tsc && vite build --mode qa",
    "build:prod": "vue-tsc && vite build --mode prod"
  }
}

📖 React.js

{
  "scripts": {
    "dev": "cross-env VITE_CJS_IGNORE_WARNING=true vite --mode dev",
    "build:qa": "tsc --noEmit && vite build --mode qa",
    "build:prod": "tsc --noEmit && vite build --mode prod"
  }
}

③. 配置TypeScript 智能提示

@/vite-env.d.ts

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

interface ImportMetaEnv {
  readonly VITE_ENV: string;
  readonly VITE_API_HOST: string;
  readonly VITE_OUT_DIR: string;
  readonly VITE_BASE: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

④. 使用环境变量

import.meta.env.VITE_API_HOST

构建相关

@See cn.vitejs.dev/config/buil…

build: {
  // -- 输出目录,默认为 dist
  outDir: 'dist',
  // -- 静态资源的输出目录,默认为 assets
  assetsDir: 'assets',
  // -- 小于此阈值的导入或引用资源将内联为 base64 编码,设置为 0 可禁用此项,单位为字节
  assetsInlineLimit: 4096,
  // -- 启用/禁用 CSS 代码拆分,默认为 true
  cssCodeSplit: true,
  // -- 构建后是否生成 source map 文件,默认为 false
  sourcemap: false,
  // -- 控制构建时是否清空输出目录,默认为 true
  emptyOutDir: true,
  // -- 控制是否压缩构建结果,默认为 true
  minify: "esbuild",
  // -- 设置目标浏览器,默认为 Vite 内部的默认值
  target: 'modules',
  // -- chunk 大小警告的限制(以 kbs 为单位)
  chunkSizeWarningLimit: 2000,
  // -- 启用/禁用 gzip 压缩大小报告
  reportCompressedSize: false,
  ...
}

推荐配置:

build: {
  outDir: env.VITE_OUT_DIR,
  chunkSizeWarningLimit: 2000,
  reportCompressedSize: false
}

gzip压缩打包

安装依赖:

$ pnpm add -D vite-plugin-compression
import { defineConfig } from "vite";
import viteCompression from "vite-plugin-compression";

export default defineConfig({
  plugins: [
    // -- 默认压缩 gzip,生成 .gz 文件
    viteCompression({
      // -- 压缩后是否删除源文件
      deleteOriginFile: false,
    }),
  ],
});

运行 pnpm build 命令,查看 distassets 目录。

提示:为了充分利用 Gzip 压缩的优势,服务器端也需要进行相应的配置,以确保能够正确地服务 Gzip 压缩文件,这一步交给后端来处理即可。

浏览器兼容

@See github.com/vitejs/vite…

$ pnpm add @vitejs/plugin-legacy terser -D

提示:必须安装 terser,因为 legacy 需要使用它来进行压缩

// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default {
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11'],
    }),
  ],
}

打包分析可视化

$ pnpm add -D rollup-plugin-visualizer
import { defineConfig } from "vite";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
  plugins: [
    visualizer({
      // -- build后,是否自动打开分析页面,默认false
      open: true,
      // -- 是否分析 gzip 大小
      gzipSize: true,
      // -- 是否分析 brotli 大小
      brotliSize: true,
      // -- 生成的可视化报告文件名
      filename: 'stats.html',
    })
  ],
});

使用命令 pnpm build 后,分析图 html 文件会在根目录下生成,默认命名为 stats.html

提示:把分析文件加入 .gitignore ,不提交到 git 仓库中

打包配置

@See cn.vitejs.dev/config/shar…

生产环境去除 console.log 、debugger

import { defineConfig } from "vite";
export default defineConfig({
  esbuild: {
    drop: ['debugger'],
    pure: ['console.log']
  }
});

项目配置

开发规范

代码规范检查与修复

代码规范:lint工具(eslint

代码风格 prettier

1)安装

$ pnpm add -D prettier

2) 新建 .prettierrc.json 配置文件,添加配置:

{
 "printWidth": 80,
 "tabWidth": 2,
 "useTabs": true,
 "singleQuote": true,
 "semi": true,
 "trailingComma": "none",
 "bracketSpacing": true
}

③ 将 prettier 集成到 eslint 中,其中:

  • eslint-config-prettier:覆盖 ESLint 本身的规则配置
  • eslint-plugin-prettier:用 Prettier 来接管修复代码即 eslint --fix
$ pnpm add -D eslint-config-prettier eslint-plugin-prettier

④ 为 lint 增加对应的执行脚本:

"lint": "eslint --fix --quiet .",
  • eslint:调用 ESLint 代码检查工具。
  • --fix:自动修复可修复的错误(例如代码格式、简单的语法问题)。
  • --quiet:只报告错误(忽略警告 warnings),减少输出信息。
  • .:表示检查当前目录下的所有文件。

同时为了方便,我们可以安装 prettier eslint 的 VSCode 插件,并在 setting 中设置为保存后自动执行。

Editor: Default Formatter —— Prettier - Code formatter

Editor:Format On Save —— ☑️ 保存时设置文件格式

5️⃣ 测试

commit 规范检查

在 Vite 项目中,可以通过配置 commitlint + husky + lint-staged规范 Git 提交信息自动执行代码质量检查,以及优化 Git Hook 执行效率

1️⃣ 安装依赖

安装必要的依赖:

$ pnpm add -D husky lint-staged @commitlint/{cli,config-conventional}

2️⃣ 初始化 husky

初始化 husky,这会自动创建 .husky 目录并设置 Git Hook:

$ pnpm exec husky init

3️⃣ 配置 pre-commit 钩子

配置 pre-commit 钩子,在提交时运行 lint-staged,只检查暂存区的文件:

$ echo "npx lint-staged" > .husky/pre-commit && chmod +x .husky/pre-commit

package.json 中配置 lint-staged

"lint-staged": {
  "*.{js,jsx,ts,tsx}": [
    "pnpm lint"
  ]
}

这样,当你执行 git commit 时,lint-staged 会自动运行 pnpm lint 来检查暂存区中的文件。

4️⃣ 配置 commit-msg 钩子

配置 commit-msg 钩子,检查提交信息是否符合规范:

$ echo "npx --no-install commitlint --edit \$1" > .husky/commit-msg && chmod +x .husky/commit-msg

创建 .commitlintrc.js 文件来配置 commitlint

export default {
  extends: ["@commitlint/config-conventional"]
};

5️⃣ 完成

现在,当你执行 git commit 时,husky 会自动触发以下钩子:

  • pre-commit 钩子:运行 lint-staged,对暂存区的文件进行代码风格检查。
  • commit-msg 钩子:运行 commitlint,检查提交信息是否符合规范。

这样配置后,你的项目将能够在提交时自动进行代码风格和提交信息的检查,确保代码质量和提交信息的规范性。

6️⃣ 提示

  1. lint-staged 会自动将修改后的文件添加到暂存区,因此不要在 lint-staged 配置中显式调用 git add
  2. 如果 pnpm lint 检查失败,提交会被阻止。请根据错误提示修复代码后重新提交。
  3. 如果需要自定义提交信息规范,可以修改 .commitlintrc.js 文件,添加自定义规则。

样式

less & sass

Vite 内置对 Less 和 Sass 的支持,安装对应的包后即可直接使用,无需额外配置。

# 安装 Less
$ pnpm add -D less 

# 安装 Sass
$ pnpm add -D sass 

tailwindcss

英文官网(推荐):tailwindcss.com/

中文官网:www.tailwindcss.cn/

  1. 安装 Tailwind CSS
$ pnpm add tailwindcss @tailwindcss/vite
  1. 配置 Vite 插件:在 vite.config.ts 配置文件中添加 @tailwindcss/vite 插件
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
  plugins: [
    ...
    tailwindcss(),
  ],
})
  1. 导入 Tailwind CSS:在您的 CSS 文件(通常是 index.css)中导入 Tailwind CSS 的内容
@import "tailwindcss";

🔖 提示:如果你使用 PostCSS,请参考 这里 >>

mockjs

①. 安装依赖:

$ pnpm add -D mockjs vite-plugin-mock

②. 引入插件

vite.config.ts

import { defineConfig } from "vite";
import { viteMockServe } from "vite-plugin-mock";

export default defineConfig(({ mode }) => {
  return {
    ...
    plugins: [viteMockServe()],
  };
});

③. 新建 mock/test.ts 文件

// @ts-ignore
import { mock } from "mockjs";

export default [
  {
    url: "/api/user/:id",
    method: "GET",
    response: (req: any) => {
      return mock({
        code: 200,
        data: {
          id: "@guid",
          name: "@cname",
          avatar: "@Image('30x30','@color')",
        },
        msg: "success",
      });
    },
  },
];

提示:在引入 mockjs 时,我添加了 @ts-ignore,因为在 mockjs^1.1.0 中,导出的是 MockM 是大写字母,实际在调用的时候是小写。

④. 模拟请求

fetch("api/user/1")
  .then((resp) => resp.json())
  .then((info) => {
    console.log(info);
  });

axios

①. 安装相关依赖:

$ pnpm add axios antd @likg/tools

②. 新建目录

$ mkdir -p src/api/{apiConfig,apiServer} && touch src/api/apiConfig/{index.ts,axios-type.d.ts} src/api/apiServer/{index.ts,apiExamples.ts} src/api/typings.d.ts

生成目录如下:

.
├── api
    ├── apiConfig
    │   ├── axios-type.d.ts
    │   └── index.ts
    ├── apiServer 
    │   ├── apiExamples.ts
    │   └── index.ts
    └── typings.d.ts

apiConfig/axios-type.d.ts

import 'axios';

declare module 'axios' {
  export interface AxiosResult<T = unknown> {
    code: number;
    data: T;
    msg: string;
  }
}

apiConfig/index.ts

import Tools from "@likg/tools";
import axios, {
  AxiosError,
  AxiosInstance,
  AxiosResponse,
  AxiosResult,
  AxiosRequestConfig,
  InternalAxiosRequestConfig,
} from "axios";
import { message } from "antd";


// ~~~~~~~~~~~~~~~~~~~~~~~
// 错误信息
// ~~~~~~~~~~~~~~~~~~~~~~~
const errorMessages: Record<number, string> = {
  400: "请求错误(400)",
  401: "未授权,请重新登录(401)",
  403: "拒绝访问(403)",
  404: "请求出错(404)",
  405: "请求方法不支持(405)",
  408: "请求超时(408)",
  500: "服务器异常(500)",
  501: "服务未实现(501)",
  502: "网络错误(502)",
  503: "网络超时(503)",
  504: "网络超时(504)",
  505: "HTTP 版本不受支持(505)",
};

// ~~~~~~~~~~~~~~~~~~~~~~~
// 创建 axios 实例
// ~~~~~~~~~~~~~~~~~~~~~~~
const axiosInstance: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_HOST,
  timeout: 60 * 1000,
  withCredentials: false,
  headers: {
    "Access-Control-Allow-Origin": "*",
    "Content-Type": "application/json",
  },
});


// ~~~~~~~~~~~~~~~~~~~~~~~
// 请求拦截器
// ~~~~~~~~~~~~~~~~~~~~~~~
axiosInstance.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {

    // -- 拼接token
    const token = localStorage.getItem("AUTHORIZATION_TOKEN") ?? "";
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }

    // -- 确认平台
    // -- 如果同时开发支付宝/微信公众号/H5则需要传入来源
    // -- 注意:具体值请与后端配合商议对应平台的source值
    const platforms: Record<string, string> = {
      alipay: "MP_ALIPAY",
      weixin: "MP_WEIXIN",
    };
    const source = platforms[Tools.getEnv()] || "WEB";
    config.headers.source = source;

    // -- 返回配置
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);



// ~~~~~~~~~~~~~~~~~~~~~~~
// 响应拦截器
// ~~~~~~~~~~~~~~~~~~~~~~~
axiosInstance.interceptors.response.use(
  async (response: AxiosResponse) => {
    message.destroy();
    // -- 处理流数据
    if (response.request.responseType === "blob") {
      return { code: 200, data: response.data, msg: "success" };
    }
    // -- 判断code,统一处理异常
    const { code, msg } = response.data as unknown as AxiosResult;
    switch (code) {
      case 200:
        return response.data;
      case 401:
      case 402:
        // history.replace('/login');
        return Promise.reject();
      default:
        message.error(msg || '请求出错啦');
        return Promise.reject(msg);
    }
  },
  (error: AxiosError) => {
    console.log("[request error] > ", error);
    if (error && error.response) {
      const status = error.response?.status;
      const errorMessage = errorMessages[status] || `连接出错(${status})!`;
      error.message = errorMessage;
    } else {
      error.message = "服务链接失败";
    }
    message.error(error.message);
    return Promise.reject(error);
  }
);

// ~~~~~~~~~~~~~~~~~~~~~~~
// 统一的 request 方法
// ~~~~~~~~~~~~~~~~~~~~~~~
const request = <R = unknown>(options: AxiosRequestConfig): Promise<AxiosResult<R>> => {
  return axiosInstance(options);
};
export default request;

typings.d.ts

declare namespace API {
  // ~~~~~~~~~~~~~~~~~~
  // 列表数据
  // ~~~~~~~~~~~~~~~~~~
  interface ListParams {
    page?: number;
    size?: number;
    [__prop__: string]: unknown;
  }
  interface ListResponse<T> {
    items: T[];
    page: number;
    size: number;
    count: number;
  }
}

提示:这个文件主要定义一些接口请求或响应的数据类型。

apiServer/apiExamples.ts

import request from "@/api/apiConfig";

export async function posts() {
  return request<unknown>({
    url: '/posts',
  });
}

提示:这个文件主要定义实际业务需求中的接口,你应该根据不同的业务场景创建不同的文件,文件名义 apiXXX 开头。

apiServer/index.ts

import * as apiExamples from "./apiExamples";
export { apiExamples };

提示:这个文件主要是统一导出接口请求。

调用:

import { apiExamples } from '@/api/apiServer';
import { useEffect } from 'react';

export default function App() {
	useEffect(() => {
		const fetchData = async () => {
			const resp = await apiExamples.posts();
			console.log(resp.data);
		};
		fetchData();
	}, []);
	return <></>;
}

全局类型声明

有时,当我们引入三方SDK时,需要在window上访问,可以定义全局类型声明:

@/typings/index.d.ts

export {};

// -- 全局组件属性定义
import { FiltersProps } from "@/filters";
declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $filters: FiltersProps;
  }
}

declare global {
  // 👉 定义全局属性
  interface Window {
    /** 百度统计 */
    _hmt: any;
    /** 微信S*/
    wx: any;
    /** 百度地图 */
    AMap: any;
    /** 腾讯地图 */
    qq: any;
    /** 支付宝 */
    AlipayJSBridge: any;
    /** iOS回调地址 */
    CONFIG_URL_FOR_IOS: string;
  }
}

动态加载静态资源

在使用 webpack 动态引入静态资源可以使用 require 形式,但是在vite中不可取,会抛出 require is not defined 的错误。

不过我们可以通过 import.meta.url 的形式引入,它是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL,其语法形式如下:

new URL(url, import.meta.url)

使用示例:

const imgs = [
  { imgUrl: new URL('./assets/logo_1.png', import.meta.url), text: "标题1" },
  { imgUrl: new URL('./assets/logo_2.png', import.meta.url), text: "标题2" },
];
// => react
{imgs.map((item) => (
  <img src={item.imgUrl.toString()} key={item.title} />
))}

值得注意的是,在生产环境中会抛出 “URL is not defined xxx” 的错误,这个时我们需要使用一个插件:rollup-plugin-import-meta-url-to-module

使用方式比较简单,首先安装依赖:

$ yarn add rollup-plugin-import-meta-url-to-module

然后再 “vit.config.js” 中配置plugins:

import urlToModule from 'rollup-plugin-import-meta-url-to-module';

export default {
  plugins: [
    urlToModule()
  ]
};

移动端配置(可选)

vconsole

vconsole 是一个轻量、可拓展、针对手机网页的前端开发者调试面板。

$ pnpm add -D vconsole

main.ts

if (import.meta.env.VITE_ENV !== "prod") {
  new vconsole();
}

移动端适配

$ pnpm add -D postcss-pxtorem autoprefixer 

postcss.config.js

export default {
  plugins: {
    autoprefixer: {
      overrideBrowserslist: [
        "Android 4.1",
        "iOS 7.1",
        "Chrome > 31",
        "ff > 31",
        "ie >= 8",
      ],
    },
    "postcss-pxtorem": {
      // -- Vant 官方根字体大小是 37.5
      rootValue: 37.5,
      propList: ["*"],
      // -- 过滤掉.norem-开头的class,不进行rem转换
      selectorBlackList: [".norem"],
    },
  },
};

@/utils/rem.ts

// rem 等比适配配置文件
// 基准大小,注意此值要与 postcss.config.js 文件中的 rootValue保持一致
const baseSize = 37.5;
// 设置 rem 函数
function setRem() {
  // 当前页面宽度相对于375宽的缩放比例,可根据自己需要修改,一般设计稿都是宽750(图方便可以拿到设计图后改过来)。
  const scale = document.documentElement.clientWidth / 375; // 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
  document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + "px";
}
// 初始化
setRem();
// 改变窗口大小时重新设置 rem
window.onresize = function () {
  setRem();
};

mian.ts(x) 引入:

import "@/utils/rem";

如果控制台抛出:【无法在 --isolatedModules 下编译 rem.ts,因为它被视为全局脚本文件。请添加导入、导出或空的 export {} 语句来使它成为模块】此时你应该在 tsconfig.json 文件中将 isolatedModules 字段设置为 false

差异化配置

📖 Vue.js

路由配置

安装依赖:

$ pnpm add vue-router

新建文件:

$ mkdir -p src/router && touch src/router/index.ts src/router/routes.ts

routes.ts

import { RouteRecordRaw } from "vue-router";

import Layouts from "@/layouts/index.vue";

const routes: RouteRecordRaw[] = [
  {
    path: "/",
    name: "layouts",
    redirect: "/index-page",
    component: Layouts,
    children: [
      {
        path: "/index-page",
        name: "index",
        component: () => import("@/pages/Tabs/IndexPage.vue"),
        meta: { title: "首页" },
      },
      {
        path: "/special-sale",
        name: "SpecialSale",
        component: () => import("@/pages/Tabs/SpecialSale.vue"),
        meta: { title: "特卖" },
      },
      {
        path: "/friends",
        name: "Friends",
        component: () => import("@/pages/Tabs/Friends.vue"),
        meta: { title: "好友" },
      },
      {
        path: "/mine",
        name: "Mine",
        component: () => import("@/pages/Tabs/Mine.vue"),
        meta: { title: "我的" },
      },
    ],
  },
  {
    path: "/download",
    name: "Download",
    component: () => import("@/pages/Download/index.vue"),
    meta: {
      title: "下载",
    },
  },
  {
    path: "/not-found",
    name: "NotFound",
    component: () => import("@/pages/NotFound/index.vue"),
  },
  {
    path: "/:pathMatch(.*)",
    redirect: "/not-found",
  },
];

export default routes;

index.ts

import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes";

const router = createRouter({
  // -- 部署二级目录:createWebHistory(base?: string)
  history: createWebHistory(import.meta.env.VITE_BASE),
  // -- 路由
  routes,
  // -- 滚动行为
  scrollBehavior: () => ({
    el: "#app",
    top: 0,
    behavior: "smooth",
  }),
});

// 导航守卫
router.beforeEach(async (to, _) => {});

router.afterEach((to) => {
  // → 设置标题
  if (to.path !== "/favicon.icon") {
    document.title = to.meta.title ? (to.meta.title as string) : "";
  }
  // → 滚动
  window.scrollTo(0, 0);
});

export default router;

提示:这里只是简单演示如何配置路由,实际上你应该根据你的业务需求来配置。关于 vue-router 的使用,可以参考 这里 >>

在main.ts挂载路由

import App from "@/App.vue";
import router from "@/router";

// App配置/挂载相关
// 1. 创建App
const app = createApp(App);
// 2. 注入
app.use(router);
// 3. 挂载
app.mount("#app");

状态管理

$ pnpm add pinia

提示:关于 Pinia 的使用,可以参考 这里 >>

@/stores/index.ts

import { defineStore } from 'pinia';

interface StoreProps {
  count: number;
}

interface ActionProps {
  increment: () => void;
  decrement: () => void;
}

export const useStore = defineStore<string, StoreProps, any, ActionProps>(
  'appStore',
  {
    state: () => ({ count: 0 }),
    actions: {
      async increment() {},
      async decrement() {},
    },
  }
);

挂载:在main.ts挂载数据中心

import { createApp } from "vue";
import { createPinia } from "pinia";
import { useStore } from "@/stores";
import App from "@/App.vue";

// 👉 app
// 1. 创建app
const app = createApp(App);
// 2. 注入依赖
app.use(createPinia());

// 4. 挂在app
app.mount("#app");

// 👉 持久化pinia
const store = useStore();
// 页面进入:合并状态
const localState = localStorage.getItem("PINIA_PERSISTENCE");
if (localState) {
  console.log("[温馨提示]:合并Store...");
  store.$state = JSON.parse(localState);
}
// 页面刷新:存储状态
window.addEventListener("beforeunload", () => {
  console.log("[温馨提示]:缓存Store...");
  localStorage.setItem("PINIA_PERSISTENCE", JSON.stringify(store.$state));
});

Vant

Vant 是一个轻量、可定制的移动端组件库,Vue 首选移动端UI框架,

$ pnpm add vant

提示:配置指南,参考 这里 >>

①. 安装依赖

$ pnpm add @vant/auto-import-resolver unplugin-vue-components -D

② . 配置插件

vite.config.ts

import { defineConfig } from "vite";
import { VantResolver } from "@vant/auto-import-resolver";
import Components from "unplugin-vue-components/vite";

export default defineConfig(({ mode }) => {
  return {
    plugins: [
    	...
      Components({
        resolvers: [VantResolver()],
      }),
    ],
  };
});

提示:有赞团队也提供了React 版本 —— React Vant >>

二级目录部署

方案1:

打包时设置 -base 选项:

"build": "vue-tsc --noEmit && vite build --mode production --base=/二级目录名/",

然后再 router/index.ts 中配置如下:

const router = createRouter({
  // 部署二级目录:createWebHistory(base?: string)
  history: createWebHistory("/二级目录名/"),
  routes,
});

方案2:

vite.config.js 配置项中添加 base 配置,如:

base:"/YOUR_PROJECT_NAME/"

然后再 router/index.ts 中配置如下:

const router = createRouter({
  // 部署二级目录:createWebHistory(base?: string)
  history: createWebHistory("/二级目录名/"),
  routes,
});

📖 React

路由配置

①. 目录结构

.
├── components
│   ├── Fallback # 懒加载loading提示 
│   ├── NotEnv   # 非微信/支付宝环境提示
│	  └── TabBar   # 标签栏
│   │   ├── index.tsx
│   │   └── index.less
├── layouts
│	  └── index.tsx
├── pages
│   ├── Auth
│   ├── IndexPage
│   ├── Integral
│   ├── Mine
│   ├── NotFound
│	  └── PrivilegeBrand
├── router
│   ├── index.tsx
│	  └── routes.ts
└── main.tsx

提示:这里只例举部分目录,仅供参考。

②. 安装依赖

$ pnpm add react-router-dom

提示:目前我使用的版本是 ^6.23.1,关于 v6 版本的路由使用请 参考这里 >>

③. 文件代码

src/layout/index.tsx

import TabBar from '@/components/@lgs-react/TabBar';
import React from 'react';
import { Outlet } from 'react-router-dom';

const Layout: React.FC = () => {
  return (
    <>
      {/* 视图容器,类似于vue中的 router-view */}
      <Outlet />
      {/* 标签栏 */}
      <TabBar />
    </>
  );
};

export default Layout;

src/App.tsx

import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';

import Fallback from '@/components/@lgs-react/Fallback';
import Layout from '@/layout';
import IndexPage from '@/pages/IndexPage';
import Integral from '@/pages/Integral';
import Mine from '@/pages/Mine';
import PrivilegeBrand from '@/pages/PrivilegeBrand';
import Auth from '@/pages/Auth';
import NotFound from './pages/NotFound';
import NotEnv from './components/@lgs-react/NotEnv';

import Tools from 'lg-tools';

const Details = React.lazy(() => import('@/pages/Details'));
const Download = React.lazy(() => import('@/pages/Download'));
const Test = React.lazy(() => import('@/pages/Test'));

/**
 * 环境判断
 * 如果 VITE_APP_SOURCE === 'mp',即表示公众号/生活号
 * 那么在浏览器环境将提示 “请在微信或支付宝客户端打开链接”
 * @param param0
 * @returns
 */
export const GuardEnv: React.FC = ({ children }) => {
  return import.meta.env.VITE_APP_SOURCE === 'mp' &&
    ['weixin', 'alipay'].indexOf(Tools.getEnv()) === -1 ? (
    <NotEnv />
  ) : (
    <>{children}</>
  );
};

/**
 * appRouter
 * @returns
 */
export const AppRouter: React.FC = ({ children }) => {
  return (
    <Suspense fallback={Fallback}>
      <Router basename={import.meta.env.VITE_APP_BASE}>{children}</Router>
    </Suspense>
  );
};

/**
 * appRoutes - 渲染路由
 * @returns
 */
export const AppRoutes: React.FC = () => {
  return (
    <Routes>
      <Route path='/' element={<Layout />}>
        <Route index element={<IndexPage />} />
        <Route path='privilege-brand' element={<PrivilegeBrand />} />
        <Route path='integral' element={<Integral />} />
        <Route path='mine' element={<Mine />} />
      </Route>
      <Route path='/details' element={<Details />} />
      <Route path='/download' element={<Download />} />
      <Route path='/test' element={<Test />} />
      <Route path='/auth/:type' element={<Auth />} />
      <Route path='*' element={<NotFound />} />
    </Routes>
  );
};

src/main.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import ErrorBoundary from '@/components/@lgs-react/ErrorBoundary';

import '@/utils/rem';
import '@/index.css';
import { AppRouter, AppRoutes, GuardEnv } from './App';
import Schemes from 'lg-schemes';

// 1. 开发环境 & 测试环境 启用vconsole --- Tips:目前启用vconsole打包会出现异常
import vconsole from 'vconsole';
if (import.meta.env.VITE_APP_ENV !== 'pro') {
  new vconsole();
}

// 2. 渲染视图
ReactDOM.render(
  <React.StrictMode>
    <ErrorBoundary>
      <GuardEnv>
        <AppRouter>
          <AppRoutes />
        </AppRouter>
      </GuardEnv>
    </ErrorBoundary>
  </React.StrictMode>,
  document.getElementById('root')
);

组件库

Ant Design

Ant Design 是一款基于 React 的企业级 UI 组件库,由阿里巴巴 Ant Group 开发,主要用于构建现代化、高效、优雅的中后台管理系统。它提供了丰富的 UI 组件、强大的主题定制能力,以及良好的国际化支持,是 React 生态中最受欢迎的 UI 库之一。

1)安装依赖

$ pnpm add antd

2)使用:修改 src/App.js,引入 antd 的按钮组件。

import { Button } from 'antd';

export default function App() {
	return (
		<div>
			<Button type="primary">Button</Button>
		</div>
	);
}

❗️注意:

截止到发文, Ant 系列暂时还未完全适配 React 19,在使用过程中会有警告甚至错误,如下所示:

Warning: [antd: compatible] antd v5 support React is 16 ~ 18. see u.ant.design/v5-for-19 for compatible.

官方已经推出了兼容方案(参考 这里 >>),就是安装兼容包 @ant-design/v5-patch-for-react-19

3)设置组件默认语言为中文

import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN'; // 引入中文语言包

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <ConfigProvider locale={zhCN}>
      <App />
    </ConfigProvider>
  </React.StrictMode>
);Ï
Ant Design Mobile

Ant Design Mobile 是 Ant Design 的移动端版本,它继承了 Ant Design 优秀的设计理念,拥有丰富的高质量移动端组件,提供良好的交互体验和视觉效果,支持 React 和 Vue 等多种技术栈,方便集成到现有项目中,并且注重性能优化与兼容性,适合用于构建企业级移动端应用,能让开发保持统一设计风格且高效。

$ pnpm add antd-mobile

💡提示:配置指南,参考 这里 >>

二级目录部署

①. 在 package.json 文件中配置指令时时设置 --base=/二级目录名/

②. 在各环境配置文件中根据需要设置 VITE_APP_BASE 字段值

②. 在配置路由时设置 basename 属性,如下所示:

/**
 * src/router/index.tsx
 * appRouter
 * @returns
 */
export const AppRouter: React.FC = ({ children }) => {
  return (
    <Suspense fallback={Fallback}>
      <Router basename={import.meta.env.VITE_APP_BASE}>{children}</Router>
    </Suspense>
  );
};

模板地址

闲暇之余,结合 Vite 封装了两个模板,大家可以参考:

提示:本文中的代码片段均摘自上述模板文件。