import { quasar } from "@quasar/vite-plugin";
import vue from "@vitejs/plugin-vue";
import { defineConfig, type Plugin } from "vite";
import { copyFile, readFile, writeFile } from "node:fs/promises";
import { resolve } from "node:path";
// __dirname 是当前文件(vite.config.ts)所在目录,即 ClientApp 目录。
// resolve() 把相对路径拼成绝对路径。以下 4 个路径是本项目自己约定的:
const outputRoot = resolve(__dirname, "../wwwroot"); // wwwroot 根目录
const viteManifestPath = resolve(outputRoot, "auth-assets/.vite/manifest.json"); // vite 自动生成的产物映射表(见 manifest: true)
const targetPath = resolve(outputRoot, "auth-assets-manifest.json"); // 给 ASP.NET(Razor)用的正式清单
const localManifestPath = resolve(__dirname, "auth-assets-manifest.json"); // 复制到 ClientApp 的副本,方便本地查看
// 自定义 Vite 插件:在构建结束后读取 vite 生成的 manifest.json,
// 把它改造成只含 4 个入口的简化清单,供 ASP.NET 视图查找带内容哈希的文件名。
function authManifestPlugin(): Plugin {
return {
name: "dpz-auth-manifest", // 插件名称(调试/报错时显示)
apply: "build", // 只在 vite build 时生效,dev/watch 模式下跳过
async closeBundle() {
// closeBundle:Rollup 钩子,所有产物文件写出完成后触发。
// viteManifest 结构类似:
// { "src/entries/public.ts": { file: "assets/public-a1b2c3d4.js", css: [...], imports: [...] } }
const viteManifest = JSON.parse(await readFile(viteManifestPath, "utf8"));
// 简化后的目标结构:
// { "public.js": "auth-assets/assets/public-a1b2c3d4.js", "public.css": ["auth-assets/assets/xxx.css"] }
const manifest: Record<string, string | string[]> = {};
// 递归收集某个入口自身及其所有间接依赖 chunk 的 CSS 文件(用 Set 去重)。
// 因为入口的 CSS 可能被 vite 拆到它 import 的其他 chunk 里,必须顺着 imports 链找全。
function collectCss(entry: { css?: string[]; imports?: string[] }): Set<string> {
const cssFiles = new Set<string>();
for (const importKey of entry.imports ?? []) {
const imported = viteManifest[importKey];
if (!imported) {
continue;
}
for (const cssFile of collectCss(imported)) {
cssFiles.add(cssFile);
}
}
for (const cssFile of entry.css ?? []) {
cssFiles.add(cssFile);
}
return cssFiles;
}
// 只关心 4 个入口(src/entries/*.ts),其余 chunk 全部忽略
for (const [key, value] of Object.entries(viteManifest)) {
if (!key.startsWith("src/entries/")) {
continue;
}
// "src/entries/public.ts" -> "entries/public",作为清单里的键名
const entry = key.replace("src/", "").replace(".ts", "");
const bundle = value as { file: string; css?: string[]; imports?: string[] };
// JS 记录为:"entries/public.js" -> "auth-assets/assets/public-a1b2c3d4.js"
manifest[`${entry}.js`] = `auth-assets/${bundle.file}`;
// CSS 记录为该入口收集到的全部 CSS 文件列表
manifest[`${entry}.css`] = [...collectCss(bundle)].map(
(cssFile) => `auth-assets/${cssFile}`,
);
}
// 写入正式清单到 wwwroot,并复制一份到 ClientApp 目录便于本地查看
await writeFile(targetPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
await copyFile(targetPath, localManifestPath);
},
};
}
export default defineConfig({
// 插件:vue() 编译 .vue 单文件组件;quasar() 注入 Quasar UI 框架;authManifestPlugin() 是上面自定义的清单插件
plugins: [vue(), quasar(), authManifestPlugin()],
// 构建产物的公开 URL 前缀,页面里所有 JS/CSS 引用都会写成 /auth-assets/xxx
base: "/auth-assets/",
build: {
// 产物输出目录(对应 clean-build.mjs 里先删除的那个目录)
outDir: "../wwwroot/auth-assets",
emptyOutDir: true, // 构建前自动清空输出目录
manifest: true, // 额外生成 .vite/manifest.json,记录"源文件 -> hash 文件名"的映射(供上面的插件读取)
modulePreload: false, // 不自动插入 <link rel="modulepreload"> 预加载标签,加载顺序交给 ASP.NET 端控制
rollupOptions: {
// 多入口构建:4 个页面分组各有独立入口(用户访问不同页面时只加载对应 bundle)
input: {
"entries/public": resolve(__dirname, "src/entries/public.ts"),
"entries/identity": resolve(__dirname, "src/entries/identity.ts"),
"entries/user": resolve(__dirname, "src/entries/user.ts"),
"entries/admin": resolve(__dirname, "src/entries/admin.ts"),
},
output: {
// manualChunks:手动分包。id 是模块的绝对路径,按依赖的库归类成独立 chunk。
// 好处:大库(milkdown/codemirror 等)单独成包,改业务代码时这些 chunk 的文件名
// (内容哈希)不会变,浏览器可以继续用缓存。
manualChunks(id) {
// vite 内部的运行时辅助代码单独一个包
if (id.includes("vite/preload-helper")) {
return "vite-runtime";
}
// 项目自己的源码(不在 node_modules)不手动分包,交给 rollup 默认策略
if (!id.includes("node_modules")) {
return;
}
if (id.includes("@milkdown")) {
return "editor-milkdown";
}
if (id.includes("@codemirror") || id.includes("@lezer") || id.includes("codemirror")) {
return "editor-codemirror";
}
if (id.includes("photoswipe")) {
return "gallery-photoswipe";
}
if (id.includes("mermaid")) {
return "editor-mermaid";
}
if (id.includes("vue") || id.includes("quasar") || id.includes("@quasar")) {
return "vendor-vue-quasar";
}
},
},
},
},
});
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
Vite 配置文件解析
这是一个用于 ASP.NET + Vue 混合项目的 Vite 构建配置文件。核心特点是为 4 个不同的页面入口分别构建独立的 JS/CSS bundle,并生成一个简化的资源清单供 ASP.NET Razor 视图使用。
主要功能模块
1. 路径定义
const outputRoot = resolve(__dirname, "../wwwroot");
const viteManifestPath = resolve(outputRoot, "auth-assets/.vite/manifest.json");
const targetPath = resolve(outputRoot, "auth-assets-manifest.json");
const localManifestPath = resolve(__dirname, "auth-assets-manifest.json");
定义了构建产物的输出路径和清单文件位置:
outputRoot: ASP.NET 的静态资源根目录wwwrootviteManifestPath: Vite 自动生成的原始清单(包含所有 chunk 信息)targetPath: 简化后的清单,供 Razor 视图读取localManifestPath: 本地副本,方便开发时查看
2. 自定义插件 authManifestPlugin
核心逻辑
在构建完成后(closeBundle 钩子),将 Vite 生成的复杂 manifest.json 转换为简化版本:
原始格式(Vite 生成):
{
"src/entries/public.ts": {
"file": "assets/public-a1b2c3d4.js",
"css": ["assets/public-x1y2z3.css"],
"imports": ["src/components/Foo.vue"]
}
}
简化格式(插件生成):
{
"entries/public.js": "auth-assets/assets/public-a1b2c3d4.js",
"entries/public.css": ["auth-assets/assets/public-x1y2z3.css"]
}
关键函数 collectCss
function collectCss(entry): Set<string>
递归收集入口及其所有依赖模块的 CSS 文件(去重)。这是因为:
- Vite 会将 CSS 拆分到不同的 chunk 中
- 需要顺着
imports依赖链找到所有相关 CSS
处理流程
- 只处理
src/entries/*.ts的 4 个入口文件 - 提取键名:
src/entries/public.ts→entries/public - 记录 JS:
entries/public.js→ 带 hash 的完整路径 - 记录 CSS:递归收集所有 CSS 并添加
auth-assets/前缀
3. Vite 构建配置
插件
plugins: [vue(), quasar(), authManifestPlugin()]
vue(): 处理.vue单文件组件quasar(): 集成 Quasar UI 框架authManifestPlugin(): 上述自定义清单插件
基础配置
base: "/auth-assets/"
所有资源引用都会带上此前缀(如 /auth-assets/assets/xxx.js)
构建选项
build: {
outDir: "../wwwroot/auth-assets",
emptyOutDir: true,
manifest: true,
modulePreload: false,
rollupOptions: { ... }
}
manifest: true: 生成原始清单文件modulePreload: false: 不自动插入预加载标签(由 ASP.NET 控制加载顺序)
4. 多入口配置
input: {
"entries/public": "src/entries/public.ts",
"entries/identity": "src/entries/identity.ts",
"entries/user": "src/entries/user.ts",
"entries/admin": "src/entries/admin.ts"
}
为 公开页、身份验证页、用户页、管理页 各自构建独立的 bundle,实现按需加载。
5. 手动分包策略 manualChunks
将第三方库拆分为独立 chunk,优化缓存效率:
| 库名称 | Chunk 名称 | 目的 |
|---|---|---|
@milkdown | editor-milkdown | Markdown 编辑器核心 |
@codemirror/@lezer | editor-codemirror | 代码编辑器 |
photoswipe | gallery-photoswipe | 图片查看器 |
mermaid | editor-mermaid | 图表渲染 |
vue/quasar | vendor-vue-quasar | 框架代码 |
vite/preload-helper | vite-runtime | Vite 运行时 |
优势:修改业务代码时,这些库的 chunk hash 不变,浏览器可继续使用缓存。
工作流程总结
graph LR
A[构建开始] --> B[编译 4 个入口]
B --> C[生成原始 manifest.json]
C --> D[authManifestPlugin 触发]
D --> E[收集 CSS 依赖]
E --> F[生成简化清单]
F --> G[写入 wwwroot]
G --> H[复制到 ClientApp]
最终 ASP.NET Razor 视图可以读取简化清单,根据页面类型加载对应的带 hash 的 JS/CSS 文件。
AI 正在分析代码…
评论加载中...