optimizaiton
This commit is contained in:
102
frontend/src/icons/lazy.ts
Normal file
102
frontend/src/icons/lazy.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Lazy Icon Loader
|
||||
*
|
||||
* This module provides lazy-loaded icons to reduce initial bundle size.
|
||||
* Icons are only loaded when actually used, not on initial page load.
|
||||
*
|
||||
* Usage:
|
||||
* import { lazyIcon } from '@/icons/lazy';
|
||||
* const PlusIcon = lazyIcon('plus');
|
||||
*/
|
||||
|
||||
import { lazy, ComponentType } from 'react';
|
||||
|
||||
// Icon name to component mapping
|
||||
const iconMap: Record<string, () => Promise<{ default: ComponentType<any> }>> = {
|
||||
'plus': () => import('./plus.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'close': () => import('./close.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'box': () => import('./box.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'check-circle': () => import('./check-circle.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'alert': () => import('./alert.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'info': () => import('./info.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'error': () => import('./info-error.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'bolt': () => import('./bolt.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'arrow-up': () => import('./arrow-up.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'arrow-down': () => import('./arrow-down.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'folder': () => import('./folder.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'videos': () => import('./videos.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'audio': () => import('./audio.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'grid': () => import('./grid.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'file': () => import('./file.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'download': () => import('./download.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'arrow-right': () => import('./arrow-right.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'group': () => import('./group.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'box-line': () => import('./box-line.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'shooting-star': () => import('./shooting-star.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'dollar-line': () => import('./dollar-line.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'trash': () => import('./trash.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'angle-up': () => import('./angle-up.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'angle-down': () => import('./angle-down.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'angle-left': () => import('./angle-left.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'angle-right': () => import('./angle-right.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'pencil': () => import('./pencil.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'check-line': () => import('./check-line.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'close-line': () => import('./close-line.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'chevron-down': () => import('./chevron-down.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'chevron-up': () => import('./chevron-up.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'paper-plane': () => import('./paper-plane.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'lock': () => import('./lock.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'envelope': () => import('./envelope.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'user-line': () => import('./user-line.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'calender-line': () => import('./calender-line.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'eye': () => import('./eye.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'eye-close': () => import('./eye-close.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'time': () => import('./time.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'copy': () => import('./copy.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'chevron-left': () => import('./chevron-left.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'user-circle': () => import('./user-circle.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'task-icon': () => import('./task-icon.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'list': () => import('./list.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'table': () => import('./table.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'page': () => import('./page.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'pie-chart': () => import('./pie-chart.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'box-cube': () => import('./box-cube.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'plug-in': () => import('./plug-in.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'docs': () => import('./docs.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'mail-line': () => import('./mail-line.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'horizontal-dots': () => import('./horizontal-dots.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'chat': () => import('./chat.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'moredot': () => import('./moredot.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'alert-hexa': () => import('./alert-hexa.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
'info-hexa': () => import('./info-hexa.svg?react').then(m => ({ default: m.ReactComponent })),
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a lazy-loaded icon component
|
||||
* @param iconName - Name of the icon (without .svg extension)
|
||||
* @returns Lazy-loaded React component
|
||||
*/
|
||||
export function lazyIcon(iconName: string): ComponentType<any> {
|
||||
const loader = iconMap[iconName];
|
||||
if (!loader) {
|
||||
console.warn(`Icon "${iconName}" not found. Available icons: ${Object.keys(iconMap).join(', ')}`);
|
||||
// Return a placeholder component
|
||||
return () => <span>?</span>;
|
||||
}
|
||||
return lazy(loader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload commonly used icons (optional optimization)
|
||||
* Call this early in your app to preload icons that are used on every page
|
||||
*/
|
||||
export function preloadCommonIcons() {
|
||||
const commonIcons = ['plus', 'close', 'chevron-down', 'chevron-up', 'chevron-left', 'chevron-right'];
|
||||
commonIcons.forEach(iconName => {
|
||||
const loader = iconMap[iconName];
|
||||
if (loader) {
|
||||
loader(); // Start loading but don't await
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,46 +21,94 @@ export default defineConfig(({ mode }) => {
|
||||
// Optimize dependency pre-bundling
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'react-apexcharts',
|
||||
'apexcharts',
|
||||
// Only pre-bundle frequently used, small dependencies
|
||||
'clsx',
|
||||
'tailwind-merge',
|
||||
'zustand',
|
||||
],
|
||||
// Exclude heavy dependencies that are only used in specific pages
|
||||
// They will be lazy-loaded when needed
|
||||
exclude: [
|
||||
'@fullcalendar/core',
|
||||
'@fullcalendar/daygrid',
|
||||
'@fullcalendar/interaction',
|
||||
'@fullcalendar/list',
|
||||
'@fullcalendar/react',
|
||||
'@fullcalendar/timegrid',
|
||||
'apexcharts',
|
||||
'react-apexcharts',
|
||||
'@react-jvectormap/core',
|
||||
'@react-jvectormap/world',
|
||||
'react-dnd',
|
||||
'react-dnd-html5-backend',
|
||||
'swiper',
|
||||
],
|
||||
exclude: [], // Don't exclude anything
|
||||
esbuildOptions: {
|
||||
// Increase timeout for large dependencies
|
||||
target: 'es2020',
|
||||
},
|
||||
},
|
||||
// Build configuration for code splitting
|
||||
build: {
|
||||
// Enable minification (esbuild is faster than terser)
|
||||
minify: 'esbuild',
|
||||
// Enable CSS code splitting
|
||||
cssCodeSplit: true,
|
||||
// Optimize chunk size
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// Manual chunk splitting for better code splitting
|
||||
manualChunks: (id) => {
|
||||
// Vendor chunks - separate large dependencies
|
||||
// Vendor chunks - separate large dependencies for better caching
|
||||
if (id.includes('node_modules')) {
|
||||
// React and React DOM together
|
||||
if (id.includes('react') || id.includes('react-dom') || id.includes('react-router')) {
|
||||
return 'vendor-react';
|
||||
// React core (most critical, always needed)
|
||||
if (id.includes('react/') || id.includes('react-dom/')) {
|
||||
return 'vendor-react-core';
|
||||
}
|
||||
// Chart libraries
|
||||
if (id.includes('apexcharts') || id.includes('recharts')) {
|
||||
// React Router (separate chunk - only loads on navigation)
|
||||
if (id.includes('react-router')) {
|
||||
return 'vendor-react-router';
|
||||
}
|
||||
// Heavy chart libraries (only used in specific pages)
|
||||
if (id.includes('apexcharts') || id.includes('react-apexcharts')) {
|
||||
return 'vendor-charts';
|
||||
}
|
||||
// UI libraries
|
||||
// Calendar libraries (only used in Calendar page)
|
||||
if (id.includes('@fullcalendar')) {
|
||||
return 'vendor-calendar';
|
||||
}
|
||||
// Map libraries (only used in specific pages)
|
||||
if (id.includes('@react-jvectormap')) {
|
||||
return 'vendor-maps';
|
||||
}
|
||||
// Drag and drop (only used in specific pages)
|
||||
if (id.includes('react-dnd')) {
|
||||
return 'vendor-dnd';
|
||||
}
|
||||
// Swiper (only used in specific pages)
|
||||
if (id.includes('swiper')) {
|
||||
return 'vendor-swiper';
|
||||
}
|
||||
// UI libraries (Radix UI, etc.)
|
||||
if (id.includes('@radix-ui') || id.includes('framer-motion')) {
|
||||
return 'vendor-ui';
|
||||
}
|
||||
// Other large vendors
|
||||
return 'vendor';
|
||||
// Zustand (state management - used everywhere)
|
||||
if (id.includes('zustand')) {
|
||||
return 'vendor-state';
|
||||
}
|
||||
// React Helmet (SEO)
|
||||
if (id.includes('react-helmet')) {
|
||||
return 'vendor-helmet';
|
||||
}
|
||||
// Other vendors (smaller libraries)
|
||||
return 'vendor-other';
|
||||
}
|
||||
// Page chunks - each page gets its own chunk
|
||||
if (id.includes('/pages/')) {
|
||||
const match = id.match(/\/pages\/([^/]+)/);
|
||||
if (match) {
|
||||
const pageName = match[1];
|
||||
// Group by module
|
||||
// Group by module for better caching
|
||||
if (pageName === 'Planner' || id.includes('/Planner/')) {
|
||||
return 'pages-planner';
|
||||
}
|
||||
@@ -80,6 +128,10 @@ export default defineConfig(({ mode }) => {
|
||||
return `page-${pageName.toLowerCase()}`;
|
||||
}
|
||||
}
|
||||
// Split icons into separate chunk (lazy load)
|
||||
if (id.includes('/icons/') && id.endsWith('.svg')) {
|
||||
return 'icons';
|
||||
}
|
||||
},
|
||||
// Optimize chunk file names
|
||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||
@@ -88,9 +140,11 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
// Increase chunk size warning limit (default is 500kb)
|
||||
chunkSizeWarningLimit: 1000,
|
||||
chunkSizeWarningLimit: 600,
|
||||
// Enable source maps in dev only
|
||||
sourcemap: isDev,
|
||||
// Report compressed sizes
|
||||
reportCompressedSize: true,
|
||||
},
|
||||
// Only configure server and HMR in development mode
|
||||
...(isDev && {
|
||||
|
||||
Reference in New Issue
Block a user