Skip to content

vite 配置文件

这是配置的重灾区。这里的配置很复杂。

导入依赖

导入的依赖大多数是插件

依赖
ts
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 环境下的全局常量。

ts
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
function pathResolve(dir: string) {
	const resPath = resolve(__dirname, ".", dir);
	return resPath;
}

创建类型声明文件时用的公共工具

生成特殊规则的文件名称。主要是实现大小写转换、约定导出文件目录、导入模板。

工具
ts
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 文件,加载的时候用户很容易出现长时间的白屏。

打包时手动分包的特殊处理
ts
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/.*"),
	},
};

路径别名

对常见的文件夹做路径别名设置,避免导入文件时过于冗长:

路径别名
ts
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
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 插件配置是麻烦且复杂的。上限很高,完全取决于你自己对此的投入。

类型化路由插件

基于目录结构,生成自动化路由。这可以让我们不再需要写路由了。但是对大家有另外一套全新的文件命名规范,要求很高。

这个插件会颠覆大家写页面的组织形式。但是该工具事实上过于激进,目前没有全面使用。

VueRouter
ts
/**
 * 类型化路由插件
 * @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
ts
/**
 * 打包体积分析插件
 */
visualizer({
	filename: "./dist/visualizer/index.html",
	title: "visualizer打包分析报告",
	template: "network",
});

vue 语言插件

vite 不是默认支持 vue 的,要安装插件,vite 才认识 vue 组件,才能提供基础的语言服务。

自动生成类型声明文件插件

让你绝大多数的组件变绿,可以让你不用导入组件,直接根据路径名来使用组件。

对大家的目录组织要求很高。而且几乎没多少人能够习惯自动导入的组件。

autoImport
ts
/**
 * 自动生成类型声明文件插件
 */
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
ts
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
ts
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
ts
Icons({
	autoInstall: true,
});

开发调试插件

爆炸级别的插件。牛逼到爆炸的插件。在你开发时,提供一揽子的开发工具。

比如:

  • 路由
  • 页面
  • 全局存储
  • 编译预览
  • 页面组件分析
  • 直接跳转到 vscode 对应的 vue 组件
  • 如果你用 cloudflare D1 数据库,还可以直接看数据库表
vueDevTools
ts
/**
 * 开发调试插件
 * @description
 * vueDevTools 必须在 createHtmlPlugin 的前面导入
 *
 * @see https://github.com/vuejs/devtools-next/issues/278#issuecomment-2021745201
 */
vueDevTools();

html 注入插件

依赖注入插件。比如全局控制 html 文件内嵌的内容。

这个插件还有一种用法,就是自定义特定目录下的 index.html。

createHtmlPlugin
ts
createHtmlPlugin({
	inject: {
		data: {
			title: getViteEnv(mode, "VITE_APP_TITLE"),
		},
	},
});

贡献者

The avatar of contributor named as ruan-cat ruan-cat

页面历史