Files
MoviePilot-Frontend/vite.config.ts
jxxghp dbeea6afcc perf: reduce frontend memory pressure and startup cost
Limit long-lived page and component retention while virtualizing large card views to keep runtime memory lower. Defer heavy editor, chart, workflow, calendar, and icon code so the app loads less JavaScript up front.
2026-05-09 08:32:14 +08:00

271 lines
7.5 KiB
TypeScript

import { fileURLToPath } from 'node:url'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vite'
import vuetify from 'vite-plugin-vuetify'
import { VitePWA } from 'vite-plugin-pwa'
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
import { resolve } from 'node:path'
import federation from '@originjs/vite-plugin-federation'
import topLevelAwait from 'vite-plugin-top-level-await'
import { readFileSync } from 'node:fs'
// 读取 package.json 获取版本号
const packageJson = JSON.parse(readFileSync('./package.json', 'utf-8'))
const buildTime = new Date().getTime().toString()
function getManualChunk(id: string) {
if (id.includes('ace-builds') || id.includes('vue3-ace-editor')) {
return 'vendor-ace'
}
if (id.includes('apexcharts') || id.includes('vue3-apexcharts')) {
return 'vendor-charts'
}
if (id.includes('@fullcalendar')) {
return 'vendor-calendar'
}
if (id.includes('@vue-flow')) {
return 'vendor-workflow'
}
if (id.includes('@vue-js-cron')) {
return 'vendor-cron'
}
return undefined
}
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [
vue(),
vueJsx(),
vuetify({
styles: {
configFile: 'src/styles/variables/_vuetify.scss',
},
}),
Components({
dirs: ['src/@core/components'],
dts: true,
}),
AutoImport({
imports: ['vue', 'vue-router', '@vueuse/core', '@vueuse/math', 'pinia', 'vue-i18n'],
vueTemplate: true,
}),
VueI18n({
include: [resolve(__dirname, 'src/locales/*.ts')],
}),
federation({
name: 'MoviePilot',
filename: 'remoteEntry.js',
// @ts-ignore
remotes: {
// 动态remotes将在运行时注入
dummy: {
external: '',
format: 'var',
},
},
shared: ['vue', 'vuetify'],
}),
VitePWA({
injectRegister: 'script',
registerType: 'autoUpdate',
strategies: 'injectManifest',
srcDir: 'src',
filename: 'service-worker.ts',
injectManifest: {
rollupFormat: 'iife',
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
globPatterns: ['**/*.{js,css,html,ico,png,svg,jpg,jpeg,webp,woff,woff2,ttf,otf,eot}'],
},
devOptions: {
enabled: true,
type: 'module',
},
manifest: {
'name': 'MoviePilot',
'short_name': 'MoviePilot',
'description': 'MoviePilot - 智能影视媒体库管理工具',
'start_url': './',
'scope': './',
'display': 'standalone',
'display_override': ['window-controls-overlay', 'standalone'],
'orientation': 'portrait-primary',
'lang': 'zh-CN',
'dir': 'ltr',
'categories': ['entertainment', 'multimedia', 'utilities'],
'icons': [
{
'src': './android-chrome-192x192.png',
'sizes': '192x192',
'type': 'image/png',
'purpose': 'any',
},
{
'src': './android-chrome-192x192_maskable.png',
'sizes': '192x192',
'type': 'image/png',
'purpose': 'maskable',
},
{
'src': './android-chrome-512x512.png',
'sizes': '512x512',
'type': 'image/png',
'purpose': 'any',
},
{
'src': './android-chrome-512x512_maskable.png',
'sizes': '512x512',
'type': 'image/png',
'purpose': 'maskable',
},
],
'theme_color': '#0E1116',
'background_color': '#0E1116',
'edge_side_panel': {
'preferred_width': 320,
},
'launch_handler': {
'client_mode': 'navigate-existing',
},
'handle_links': 'preferred',
'id': 'moviepilot-app',
'shortcuts': [
{
'name': '推荐',
'short_name': '推荐',
'description': '查看推荐内容',
'url': './recommend',
'icons': [
{
'src': './sparkles-icon-192x192.png',
'sizes': '192x192',
'type': 'image/png',
},
],
},
{
'name': '探索',
'short_name': '探索',
'description': '探索新内容',
'url': './discover',
'icons': [
{
'src': './clock-icon-192x192.png',
'sizes': '192x192',
'type': 'image/png',
},
],
},
{
'name': '更多',
'short_name': '更多',
'description': '更多功能',
'url': './apps',
'icons': [
{
'src': './cog-icon-192x192.png',
'sizes': '192x192',
'type': 'image/png',
},
],
},
],
'screenshots': [
{
'src': './android-chrome-512x512.png',
'sizes': '512x512',
'type': 'image/png',
'form_factor': 'wide',
'label': 'MoviePilot 主界面',
},
{
'src': './android-chrome-192x192.png',
'sizes': '192x192',
'type': 'image/png',
'form_factor': 'narrow',
'label': 'MoviePilot 移动端',
},
],
'protocol_handlers': [
{
'protocol': 'web+moviepilot',
'url': './?handler=%s',
},
],
'prefer_related_applications': false,
'related_applications': [],
},
}),
topLevelAwait({
// The export name of top-level await promise for each chunk module
promiseExportName: '__mp_tla',
// The function to generate import names of top-level await promise in each chunk module
promiseImportName: i => `__mp_tla_${i}`,
}),
],
define: {
'process.env': {},
'__APP_VERSION__': JSON.stringify(`v${packageJson.version}`),
'__BUILD_TIME__': JSON.stringify(buildTime),
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@core': fileURLToPath(new URL('./src/@core', import.meta.url)),
'@layouts': fileURLToPath(new URL('./src/@layouts', import.meta.url)),
'@images': fileURLToPath(new URL('./src/assets/images/', import.meta.url)),
'@styles': fileURLToPath(new URL('./src/styles/', import.meta.url)),
'@configured-variables': fileURLToPath(new URL('./src/styles/variables/_template.scss', import.meta.url)),
'apexcharts': fileURLToPath(new URL('node_modules/apexcharts', import.meta.url)),
},
},
build: {
target: 'esnext',
minify: 'terser',
rollupOptions: {
output: {
manualChunks: getManualChunk,
},
},
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
chunkSizeWarningLimit: 5000,
cssCodeSplit: false,
},
optimizeDeps: {
exclude: ['vuetify'],
entries: ['./src/**/*.vue'],
},
server: {
proxy: {
'/api/v1': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
cookieDomainRewrite: 'localhost',
},
},
},
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler',
quietDeps: true,
},
},
},
})