vite 配置文件
这是配置的重灾区。这里的配置很复杂。
导入依赖
导入的依赖大多数是插件
依赖
import { dirname, resolve } from "node:path";
import { fileURLToPath, URL } from "node:url";
import * as fs from "node:fs";
import { upperFirst } from "lodash-es";
import { type UserConfig, type ConfigEnv, defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import { createHtmlPlugin } from "vite-plugin-html";
import vueDevTools from "vite-plugin-vue-devtools";
import { visualizer } from "rollup-plugin-visualizer";
import VueRouter from "unplugin-vue-router/vite";
import { VueRouterAutoImports } from "unplugin-vue-router";
import { createPlugin, getName } from "vite-plugin-autogeneration-import-file";
import { getRouteName } from "./src/plugins/unplugin-vue-router";
import { ImportMetaEnv } from "./types/env.shim.d";
文件路径
要做文件操作,基本上需要得到路径的。
在 esm 环境下,要变通地得到 node 环境下的全局常量。
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
function pathResolve(dir: string) {
const resPath = resolve(__dirname, ".", dir);
return resPath;
}
创建类型声明文件时用的公共工具
生成特殊规则的文件名称。主要是实现大小写转换、约定导出文件目录、导入模板。
工具
const { autoImport, resolver } = createPlugin();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
function pathResolve(dir: string) {
const resPath = resolve(__dirname, ".", dir);
return resPath;
}
type DirOptions = Parameters<typeof autoImport>["0"];
type DirOption = DirOptions[number];
type _DirOptionName = DirOption["name"];
type _DirOptionNameNotString = Exclude<_DirOptionName, string>;
type DirOptionName = NonNullable<_DirOptionNameNotString>;
/**
* 创建名称生成函数
* @description
* 用于诸如特定的名称前缀 便于实现模块注册
*/
function createDirOptionNameFunction(prefix: string = "") {
/**
* 组件名命名规则支持字符串模板和函数
* @description
* 设置首字母为大写
*/
const dirOptionName: DirOptionName = function name(fileName) {
const resFileName = getName(fileName);
const resFileNameWithPrefix = <const>`${upperFirst(prefix)}${upperFirst(resFileName)}`;
return resFileNameWithPrefix;
};
return dirOptionName;
}
const autoImportTemplatePath = <const>"./template/components.template.d.ts";
/** 文件生成模板 */
function createAutoImportTemplate() {
return fs.readFileSync(pathResolve(autoImportTemplatePath), "utf-8");
}
const autoImportTemplate = createAutoImportTemplate();
构建配置
我们的构建事实上是有 bug 的。不同的包管理器,其依赖的存储格式不同。如果不做处理,打包会生成一个体积巨大的 js 文件,加载的时候用户很容易出现长时间的白屏。
打包时手动分包的特殊处理
const build = {
assetsDir: "static",
chunkSizeWarningLimit: 1000,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules")) {
const regex = /.pnpm\/(.*?)\//;
// const res = id.toString().split("node_modules/")[1].split("/")[0].toString();
const ids = id.toString().split("node_modules/");
const targetId = ids[1];
const chunkNames = targetId.split("/");
// 如果等于pnpm,说明是pnpm的包,需要取第二个
if (chunkNames[0] === ".pnpm") {
// console.log("in chunkNames[0]", chunkNames[0]);
return chunkNames[1];
} else {
return chunkNames[0];
}
}
},
},
external: new RegExp("views/sample/.*"),
},
};
路径别名
对常见的文件夹做路径别名设置,避免导入文件时过于冗长:
路径别名
const resolve = {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
components: fileURLToPath(new URL("./src/components", import.meta.url)),
types: fileURLToPath(new URL("./src/types", import.meta.url)),
views: fileURLToPath(new URL("./src/views", import.meta.url)),
api: fileURLToPath(new URL("./src/apis", import.meta.url)),
stores: fileURLToPath(new URL("./src/stores", import.meta.url)),
router: fileURLToPath(new URL("./src/router", import.meta.url)),
utils: fileURLToPath(new URL("./src/utils", import.meta.url)),
models: fileURLToPath(new URL("./src/models", import.meta.url)),
},
};
与此同时,tsconfig.json 也要同步地做出改动:
在我们项目内,负责客户端的是 tsconfig.app.json 文件。
tsconfig.app.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"types": [
/** https://juejin.cn/post/7262322846252613693 */
"element-plus/global",
"vite/client",
"unplugin-auto-import",
/** https://uvr.esm.is/introduction.html#setup */
"unplugin-vue-router/client"
],
"allowJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"components/*": ["src/components/*"],
"types/*": ["src/types/*"],
"views/*": ["src/views/*"],
"api/*": ["src/apis/*"],
"stores/*": ["src/stores/*"],
"routers/*": ["src/routers/*"],
"utils/*": ["src/utils/*"],
"models/*": ["src/models/*"]
}
},
"include": [
"src",
// 导入全部的类型文件包括:
/**
auto-imports.d.ts
components.d.ts
typed-router.d.ts
*/
"types",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
// 测试文件集
"tests/**/*.ts"
],
"exclude": ["node_modules", "dist", "public", "src/assets"]
}
插件
vite 插件配置是麻烦且复杂的。上限很高,完全取决于你自己对此的投入。
类型化路由插件
基于目录结构,生成自动化路由。这可以让我们不再需要写路由了。但是对大家有另外一套全新的文件命名规范,要求很高。
这个插件会颠覆大家写页面的组织形式。但是该工具事实上过于激进,目前没有全面使用。
- https://nuxt.com/docs/guide/directory-structure/pages
- https://uvr.esm.is/guide/file-based-routing.html
VueRouter
/**
* 类型化路由插件
* @description
* 其定义位置必须在 `@vitejs/plugin-vue` 插件之前。
*
* @see https://uvr.esm.is/introduction.html#installation
*/
VueRouter({
dts: "./types/typed-router.d.ts",
routesFolder: [
{
/**
* 在我们项目中,页面放在 views 文件夹下。
*
* 文档建议是写在pages内
* src: "src/pages",
*/
src: "src/views",
// 下面的配置暂时不使用
// override globals
// exclude: (excluded) => excluded,
// filePatterns: (filePatterns) => filePatterns,
// extensions: (extensions) => extensions,
},
],
getRouteName,
});
打包体积分析插件
常规的打包体积分析插件。
你现在就可以看我们前端项目的打包体积分析报告:
visualizer
/**
* 打包体积分析插件
*/
visualizer({
filename: "./dist/visualizer/index.html",
title: "visualizer打包分析报告",
template: "network",
});
vue 语言插件
vite 不是默认支持 vue 的,要安装插件,vite 才认识 vue 组件,才能提供基础的语言服务。
自动生成类型声明文件插件
让你绝大多数的组件变绿,可以让你不用导入组件,直接根据路径名来使用组件。
对大家的目录组织要求很高。而且几乎没多少人能够习惯自动导入的组件。
autoImport
/**
* 自动生成类型声明文件插件
*/
autoImport([
// components 目录
{
// 文件命名规则
name: createDirOptionNameFunction("ComponentIn"),
// 匹配规则 匹配全部的vue组件
pattern: ["**/*.vue"],
// 监听的文件夹
dir: pathResolve("./src/components"),
// 生成的文件
// FIXME: 当不包含文件路径时,就出现错误 如果没有预先准备好文件夹,就会生成失败。
toFile: pathResolve("./types/components-in-components-path.d.ts"),
// 文件生成模板
template: autoImportTemplate,
codeTemplates: [
{
key: "//typeCode",
template: 'type ComponentIn{{name}}Instance = InstanceType<typeof import("{{path}}")["default"]>;\n ',
},
{
key: "//code",
template: '{{name}}: typeof import("{{path}}")["default"];\n ',
},
],
},
// views 目录
{
name: createDirOptionNameFunction("Page"),
pattern: ["**/*.vue"],
dir: pathResolve("./src/views"),
toFile: pathResolve("./types/components-in-views-path.d.ts"),
template: autoImportTemplate,
codeTemplates: [
{
key: "//typeCode",
template: 'type Page{{name}}Instance = InstanceType<typeof import("{{path}}")["default"]>;\n ',
},
{
key: "//code",
template: '{{name}}: typeof import("{{path}}")["default"];\n ',
},
],
},
]);
自动导入插件
常规的自动导入。你用什么,就导入什么。
本插件不会全量地提供类型声明的,你用了什么,他才会自动生成。
AutoImport
AutoImport({
imports: [
VueRouterAutoImports,
"@vueuse/core",
"vue",
{
"@ruan-cat/utils": ["isConditionsEvery", "isConditionsSome"],
},
],
ignore: ["vue-router"],
dirs: ["src/**/*"],
dts: "./types/auto-imports.d.ts",
resolvers: [ElementPlusResolver()],
});
针对 vue 的自动导入插件
常规的插件。不做解释。
Components
Components({
version: 3,
include: [],
dirs: [
// 不生成 不负责。目前此文件夹下面的组件,交给其他的插件实现生成,生成特定的命名规则前缀
// "src/components",
// 也不负责具体的路由页面
// "src/views",
],
dts: "./types/components.d.ts",
directoryAsNamespace: true,
resolvers: [
ElementPlusResolver(),
resolver([0, 1]),
IconsResolver({
enabledCollections: ["icon-park"],
}),
],
});
针对 icon 组件的自动导入插件
常规插件。为 icon 提供自动导入。
Icons
Icons({
autoInstall: true,
});
开发调试插件
爆炸级别的插件。牛逼到爆炸的插件。在你开发时,提供一揽子的开发工具。
比如:
- 路由
- 页面
- 全局存储
- 编译预览
- 页面组件分析
- 直接跳转到 vscode 对应的 vue 组件
- 如果你用 cloudflare D1 数据库,还可以直接看数据库表
vueDevTools
/**
* 开发调试插件
* @description
* vueDevTools 必须在 createHtmlPlugin 的前面导入
*
* @see https://github.com/vuejs/devtools-next/issues/278#issuecomment-2021745201
*/
vueDevTools();
html 注入插件
依赖注入插件。比如全局控制 html 文件内嵌的内容。
这个插件还有一种用法,就是自定义特定目录下的 index.html。
createHtmlPlugin
createHtmlPlugin({
inject: {
data: {
title: getViteEnv(mode, "VITE_APP_TITLE"),
},
},
});