
This commit includes several improvements and new features: - Updated the WebSocket connection logic in `websocket.py` to include connection status messages and periodic pings for maintaining the connection. - Introduced new skeleton components (`Skeleton.vue`, `SkeletonDashboard.vue`, `SkeletonList.vue`) for improved loading states in the UI, enhancing user experience during data fetching. - Refactored the Vite configuration to support advanced code splitting and caching strategies, optimizing the build process. - Enhanced ESLint configuration for better compatibility with project structure. These changes aim to improve real-time communication, user interface responsiveness, and overall application performance.
240 lines
7.2 KiB
TypeScript
240 lines
7.2 KiB
TypeScript
import { defineConfig, loadEnv } from 'vite';
|
|
import vue from '@vitejs/plugin-vue';
|
|
import { VitePWA, VitePWAOptions } from 'vite-plugin-pwa';
|
|
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
|
import { fileURLToPath, URL } from 'node:url';
|
|
import path from 'node:path';
|
|
|
|
const pwaOptions: Partial<VitePWAOptions> = {
|
|
registerType: 'autoUpdate',
|
|
strategies: 'injectManifest',
|
|
srcDir: 'src',
|
|
filename: 'sw.ts',
|
|
devOptions: {
|
|
enabled: true,
|
|
type: 'module',
|
|
navigateFallback: 'index.html',
|
|
suppressWarnings: true,
|
|
},
|
|
manifest: {
|
|
name: 'mitlist',
|
|
short_name: 'mitlist',
|
|
description: 'mitlist pwa',
|
|
theme_color: '#fff8f0',
|
|
background_color: '#f3f3f3',
|
|
display: 'standalone',
|
|
orientation: 'portrait',
|
|
icons: [
|
|
{ src: 'icons/icon-128x128.png', sizes: '128x128', type: 'image/png' },
|
|
{ src: 'icons/icon-192x192.png', sizes: '192x192', type: 'image/png' },
|
|
{ src: 'icons/icon-256x256.png', sizes: '256x256', type: 'image/png' },
|
|
{ src: 'icons/icon-384x384.png', sizes: '384x384', type: 'image/png' },
|
|
{ src: 'icons/icon-512x512.png', sizes: '512x512', type: 'image/png' },
|
|
],
|
|
},
|
|
injectManifest: {
|
|
globPatterns: [
|
|
'**/*.{js,css,html,ico,png,svg,woff2}',
|
|
'offline.html',
|
|
],
|
|
globIgnores: [
|
|
'**/node_modules/**',
|
|
'**/dist/**',
|
|
'sw.js',
|
|
'dev-sw.js',
|
|
'index.html',
|
|
],
|
|
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024, // 15MB
|
|
},
|
|
workbox: {
|
|
cleanupOutdatedCaches: true,
|
|
sourcemap: true,
|
|
},
|
|
};
|
|
|
|
export default ({ mode }: { mode: string }) => {
|
|
const env = loadEnv(mode, process.cwd(), '');
|
|
const isProduction = mode === 'production';
|
|
|
|
return defineConfig({
|
|
plugins: [
|
|
vue(),
|
|
VitePWA(pwaOptions),
|
|
VueI18nPlugin({
|
|
include: [path.resolve(path.dirname(fileURLToPath(import.meta.url)), './src/i18n/**.json')],
|
|
strictMessage: false,
|
|
runtimeOnly: false,
|
|
compositionOnly: false,
|
|
}),
|
|
],
|
|
resolve: {
|
|
alias: {
|
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
},
|
|
},
|
|
define: {
|
|
__PWA_FALLBACK_HTML__: JSON.stringify('/index.html'),
|
|
__PWA_SERVICE_WORKER_REGEX__: JSON.stringify('^(sw|workbox)-.*\\.js$'),
|
|
'process.env.MODE': JSON.stringify(process.env.NODE_ENV),
|
|
'process.env.PROD': JSON.stringify(process.env.NODE_ENV === 'production'),
|
|
},
|
|
build: {
|
|
// Advanced code splitting configuration
|
|
rollupOptions: {
|
|
output: {
|
|
// Vendor chunk splitting for better caching
|
|
manualChunks: {
|
|
// Core Vue ecosystem
|
|
'vue-vendor': ['vue', 'vue-router', 'pinia'],
|
|
|
|
// UI Framework chunks
|
|
'headlessui': ['@headlessui/vue'],
|
|
'icons': ['@heroicons/vue'],
|
|
|
|
// Large utility libraries
|
|
'date-utils': ['date-fns'],
|
|
'http-client': ['axios'],
|
|
|
|
// Chart/visualization libraries (if used)
|
|
'charts': ['chart.js', 'vue-chartjs'].filter(dep => {
|
|
try {
|
|
require.resolve(dep);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}),
|
|
|
|
// Development tools (only in dev)
|
|
...(isProduction ? {} : {
|
|
'dev-tools': ['@vue/devtools-api']
|
|
})
|
|
},
|
|
|
|
// Optimize chunk file names for caching
|
|
chunkFileNames: (chunkInfo) => {
|
|
const facadeModuleId = chunkInfo.facadeModuleId
|
|
? chunkInfo.facadeModuleId.split('/').pop()?.replace(/\.\w+$/, '') || 'chunk'
|
|
: 'chunk';
|
|
|
|
// Route-based chunks get descriptive names
|
|
if (chunkInfo.facadeModuleId?.includes('/pages/')) {
|
|
return `pages/[name]-[hash].js`;
|
|
}
|
|
if (chunkInfo.facadeModuleId?.includes('/components/')) {
|
|
return `components/[name]-[hash].js`;
|
|
}
|
|
if (chunkInfo.facadeModuleId?.includes('/layouts/')) {
|
|
return `layouts/[name]-[hash].js`;
|
|
}
|
|
|
|
return `chunks/${facadeModuleId}-[hash].js`;
|
|
},
|
|
|
|
// Optimize entry and asset naming
|
|
entryFileNames: `assets/[name]-[hash].js`,
|
|
assetFileNames: (assetInfo) => {
|
|
const info = assetInfo.name?.split('.') || [];
|
|
const _ext = info[info.length - 1];
|
|
|
|
// Organize assets by type for better caching strategies
|
|
if (/\.(png|jpe?g|gif|svg|webp|avif)$/i.test(assetInfo.name || '')) {
|
|
return `images/[name]-[hash][extname]`;
|
|
}
|
|
if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name || '')) {
|
|
return `fonts/[name]-[hash][extname]`;
|
|
}
|
|
if (/\.css$/i.test(assetInfo.name || '')) {
|
|
return `styles/[name]-[hash][extname]`;
|
|
}
|
|
|
|
return `assets/[name]-[hash][extname]`;
|
|
}
|
|
},
|
|
|
|
// External dependencies that should not be bundled
|
|
external: isProduction ? [] : [
|
|
// In development, externalize heavy dev dependencies
|
|
]
|
|
},
|
|
|
|
// Performance optimizations
|
|
target: 'esnext',
|
|
minify: isProduction ? 'terser' : false,
|
|
|
|
// Terser options for production
|
|
...(isProduction && {
|
|
terserOptions: {
|
|
compress: {
|
|
drop_console: true, // Remove console.log in production
|
|
drop_debugger: true,
|
|
pure_funcs: ['console.log', 'console.info', 'console.debug'] // Remove specific console methods
|
|
},
|
|
mangle: {
|
|
safari10: true // Ensure Safari 10+ compatibility
|
|
},
|
|
format: {
|
|
comments: false // Remove comments
|
|
}
|
|
}
|
|
}),
|
|
|
|
// Source map configuration
|
|
sourcemap: isProduction ? false : 'inline',
|
|
|
|
// CSS code splitting
|
|
cssCodeSplit: true,
|
|
|
|
// Asset size warnings
|
|
chunkSizeWarningLimit: 1000, // 1MB warning threshold
|
|
|
|
// Enable/disable asset inlining
|
|
assetsInlineLimit: 4096 // 4KB threshold for base64 inlining
|
|
},
|
|
|
|
// Performance optimizations for development
|
|
optimizeDeps: {
|
|
include: [
|
|
'vue',
|
|
'vue-router',
|
|
'pinia',
|
|
'@headlessui/vue',
|
|
'@heroicons/vue/24/outline',
|
|
'@heroicons/vue/24/solid',
|
|
'date-fns'
|
|
],
|
|
exclude: [
|
|
// Exclude heavy dependencies that should be loaded lazily
|
|
]
|
|
},
|
|
|
|
// Development server configuration
|
|
server: {
|
|
open: true,
|
|
proxy: {
|
|
'/api': {
|
|
target: env.VITE_API_BASE_URL,
|
|
changeOrigin: true,
|
|
// Add caching headers for API responses in development
|
|
configure: (proxy, _options) => {
|
|
proxy.on('proxyRes', (proxyRes, req, _res) => {
|
|
// Add cache headers for static API responses
|
|
if (req.url?.includes('/categories') || req.url?.includes('/users/me')) {
|
|
proxyRes.headers['Cache-Control'] = 'max-age=300'; // 5 minutes
|
|
}
|
|
});
|
|
}
|
|
},
|
|
},
|
|
// Enable gzip compression in dev
|
|
middlewareMode: false,
|
|
},
|
|
|
|
// Preview server configuration (for production builds)
|
|
preview: {
|
|
port: 4173,
|
|
strictPort: true,
|
|
open: true
|
|
}
|
|
});
|
|
}; |