mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-05-20 21:45:46 +08:00
依赖: - pnpm update --latest(vite、vue、eslint、typescript 等 14 个包到最新版) - 显式加入 vue-eslint-parser ^10.4.0 / workbox-build ^7.4.1,消除 peer 警告 代码质量: - 修复 ESLint vue parser 配置(用 vue-eslint-parser 作主 parser),lint 错误 17 → 0 - tsconfig 启用 noUncheckedIndexedAccess,修复 16 个潜在 undefined 访问 bug - 删除未使用的 src/utils/icons.ts(723 行死代码) 打包体积: - PWA precache 10.5 MB / 424 项 → 740 KB / 24 项(字体改为运行时缓存) - vendor CSS gzip 201 KB → 141 KB(禁用字体 base64 内联,保留 unicode-range 子集策略) - Artalk CSS 跟随 CommentsModal 异步加载 - 字体精简:移除未使用的 300 字重,补上用到的 600 - 删除僵尸的 fancybox manualChunks 配置 健壮性: - SSE 搜索新增 AbortController + 60s 超时,新搜索取消旧请求,组件卸载取消进行中 - settings 持久化加 300ms 防抖 + QuotaExceededError 处理 + beforeunload 强制落盘 组件拆分: - SearchHeader.vue 1330 → 1061 行,抽出 SearchErrorCard 子组件 - SettingsModal.vue 1289 → 1171 行,抽出 AdvancedApiSettings 子组件 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
226 lines
6.5 KiB
TypeScript
226 lines
6.5 KiB
TypeScript
import { defineConfig } from "vite";
|
||
import vue from "@vitejs/plugin-vue";
|
||
import tailwindcss from "@tailwindcss/vite";
|
||
import { fileURLToPath, URL } from 'node:url';
|
||
import { VitePWA } from 'vite-plugin-pwa';
|
||
|
||
export default defineConfig({
|
||
server: {
|
||
host: "localhost",
|
||
port: 5500,
|
||
// 预热常用文件
|
||
warmup: {
|
||
clientFiles: [
|
||
'./src/App.vue',
|
||
'./src/components/*.vue',
|
||
'./src/stores/*.ts',
|
||
],
|
||
},
|
||
},
|
||
|
||
plugins: [
|
||
{
|
||
name: 'prismjs-esm-fix',
|
||
transform(code, id) {
|
||
if (id.includes('prismjs/components/') && !id.includes('prism-core')) {
|
||
return {
|
||
code: `import Prism from 'prismjs/components/prism-core';\n${code}`,
|
||
map: null,
|
||
}
|
||
}
|
||
},
|
||
},
|
||
vue(),
|
||
tailwindcss(),
|
||
// PWA 配置
|
||
VitePWA({
|
||
registerType: 'autoUpdate', // 收到新版本立即更新
|
||
includeAssets: ['logo.svg', 'robots.txt'],
|
||
manifest: {
|
||
name: 'SearchGal - Galgame 聚合搜索',
|
||
short_name: 'SearchGal',
|
||
description: '多平台 Galgame 资源聚合搜索引擎',
|
||
start_url: '/',
|
||
display: 'standalone',
|
||
background_color: '#fff5fa',
|
||
theme_color: '#ff1493',
|
||
orientation: 'portrait-primary',
|
||
scope: '/',
|
||
lang: 'zh-CN',
|
||
dir: 'ltr',
|
||
icons: [
|
||
{
|
||
src: '/logo.svg',
|
||
sizes: 'any',
|
||
type: 'image/svg+xml',
|
||
purpose: 'any maskable',
|
||
},
|
||
],
|
||
categories: ['entertainment', 'games', 'utilities'],
|
||
shortcuts: [
|
||
{
|
||
name: '游戏模式搜索',
|
||
short_name: '游戏搜索',
|
||
description: '快速搜索游戏资源',
|
||
url: '/?mode=game',
|
||
icons: [{ src: '/logo.svg', sizes: 'any' }],
|
||
},
|
||
{
|
||
name: '补丁模式搜索',
|
||
short_name: '补丁搜索',
|
||
description: '搜索游戏补丁资源',
|
||
url: '/?mode=patch',
|
||
icons: [{ src: '/logo.svg', sizes: 'any' }],
|
||
},
|
||
],
|
||
},
|
||
workbox: {
|
||
// 预缓存核心构建产物(首屏必需,不包含字体——字体按 unicode-range 子集太多,改为运行时缓存)
|
||
globPatterns: ['**/*.{js,css,html,svg,ico}'],
|
||
// 单文件最大预缓存 3 MB,避免大依赖膨胀 precache
|
||
maximumFileSizeToCacheInBytes: 3 * 1024 * 1024,
|
||
// 排除按需加载的资源(评论、编辑器在用户触发时再缓存)
|
||
globIgnores: [
|
||
'**/artalk-*.{js,css}',
|
||
'**/editor-*.{js,css}',
|
||
'**/CommentsModal-*.{js,css}',
|
||
'**/SettingsModal-*.{js,css}',
|
||
],
|
||
// 运行时缓存策略
|
||
runtimeCaching: [
|
||
{
|
||
// 本地字体子集 - 缓存优先(用户用到哪个子集才缓存哪个)
|
||
urlPattern: /\/fonts\/.*\.(woff2?|ttf|otf)$/i,
|
||
handler: 'CacheFirst',
|
||
options: {
|
||
cacheName: 'local-fonts',
|
||
expiration: {
|
||
maxEntries: 60,
|
||
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 年
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// 字体文件 - 缓存优先
|
||
urlPattern: /^https:\/\/fonts\.(googleapis|gstatic)\.com\/.*/i,
|
||
handler: 'CacheFirst',
|
||
options: {
|
||
cacheName: 'google-fonts',
|
||
expiration: {
|
||
maxEntries: 10,
|
||
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 年
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// 图片 API - 网络优先
|
||
urlPattern: /^https:\/\/api\.illlights\.com\/.*/i,
|
||
handler: 'NetworkFirst',
|
||
options: {
|
||
cacheName: 'background-images',
|
||
expiration: {
|
||
maxEntries: 20,
|
||
maxAgeSeconds: 60 * 60 * 24, // 1 天
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// VNDB API - 网络优先
|
||
urlPattern: /^https:\/\/api\.vndb\.org\/.*/i,
|
||
handler: 'NetworkFirst',
|
||
options: {
|
||
cacheName: 'vndb-api',
|
||
expiration: {
|
||
maxEntries: 50,
|
||
maxAgeSeconds: 60 * 30, // 30 分钟
|
||
},
|
||
},
|
||
},
|
||
],
|
||
// 离线时的导航回退
|
||
navigateFallback: null, // 不使用默认回退,由 offlineFallback 处理
|
||
},
|
||
// 开发环境启用 SW(便于测试)
|
||
devOptions: {
|
||
enabled: false, // 开发时禁用,避免干扰
|
||
},
|
||
}),
|
||
],
|
||
|
||
resolve: {
|
||
alias: {
|
||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||
}
|
||
},
|
||
|
||
// 构建优化
|
||
build: {
|
||
target: 'esnext',
|
||
cssCodeSplit: true,
|
||
sourcemap: false,
|
||
chunkSizeWarningLimit: 600,
|
||
// 禁止字体 base64 内联:保留 fontsource 的 unicode-range 子集策略,
|
||
// 浏览器按需下载用到的子集而不是把全部字体 base64 进 CSS
|
||
assetsInlineLimit: (filePath) => {
|
||
if (/\.(woff2?|eot|ttf|otf)$/i.test(filePath)) {
|
||
return false
|
||
}
|
||
return undefined
|
||
},
|
||
rolldownOptions: {
|
||
output: {
|
||
assetFileNames: (assetInfo) => {
|
||
const name = assetInfo.name || '';
|
||
if (/\.(woff2?|eot|ttf|otf)$/i.test(name)) {
|
||
return 'fonts/[name]-[hash][extname]';
|
||
}
|
||
if (/\.(png|jpe?g|gif|svg|webp|ico)$/i.test(name)) {
|
||
return 'images/[name]-[hash][extname]';
|
||
}
|
||
if (/\.css$/i.test(name)) {
|
||
return 'css/[name]-[hash][extname]';
|
||
}
|
||
return 'assets/[name]-[hash][extname]';
|
||
},
|
||
entryFileNames: 'js/[name]-[hash].js',
|
||
chunkFileNames: 'js/[name]-[hash].js',
|
||
manualChunks: (id) => {
|
||
if (id.includes('node_modules')) {
|
||
if (id.includes('/vue/') || id.includes('/@vue/')) {
|
||
return 'vue-core';
|
||
}
|
||
if (id.includes('/pinia/')) {
|
||
return 'pinia';
|
||
}
|
||
if (id.includes('/lucide-vue-next/')) {
|
||
return 'ui-libs';
|
||
}
|
||
if (id.includes('/artalk/')) {
|
||
return 'artalk';
|
||
}
|
||
if (id.includes('/prismjs/') || id.includes('/vue-prism-editor/')) {
|
||
return 'editor';
|
||
}
|
||
return 'vendor';
|
||
}
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
// 依赖优化
|
||
optimizeDeps: {
|
||
include: [
|
||
'vue',
|
||
'pinia',
|
||
'lucide-vue-next',
|
||
],
|
||
exclude: ['artalk'],
|
||
},
|
||
|
||
// CSS 配置
|
||
css: {
|
||
devSourcemap: true,
|
||
},
|
||
});
|