feat(project-view): optimize component loading and enhance configuration
- Introduced lazy loading for project view components and chart components to reduce initial bundle size. - Centralized Ant Design imports in a new `antd-imports.ts` file for better tree-shaking and maintainability. - Updated project view header and task list components to utilize centralized imports, improving consistency and performance. - Enhanced project view constants to streamline component rendering and improve user experience.
This commit is contained in:
25
worklenz-frontend/src/components/charts/chart-loader.tsx
Normal file
25
worklenz-frontend/src/components/charts/chart-loader.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { Spin } from 'antd';
|
||||
|
||||
// Lazy load chart components to reduce initial bundle size
|
||||
const LazyBar = React.lazy(() => import('react-chartjs-2').then(module => ({ default: module.Bar })));
|
||||
const LazyDoughnut = React.lazy(() => import('react-chartjs-2').then(module => ({ default: module.Doughnut })));
|
||||
|
||||
interface ChartLoaderProps {
|
||||
type: 'bar' | 'doughnut';
|
||||
data: any;
|
||||
options?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const ChartLoader: React.FC<ChartLoaderProps> = ({ type, ...props }) => {
|
||||
const ChartComponent = type === 'bar' ? LazyBar : LazyDoughnut;
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Spin size="large" />}>
|
||||
<ChartComponent {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartLoader;
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import ProjectViewInsights from '@/pages/projects/projectView/insights/project-view-insights';
|
||||
import ProjectViewFiles from '@/pages/projects/projectView/files/project-view-files';
|
||||
import ProjectViewMembers from '@/pages/projects/projectView/members/project-view-members';
|
||||
import ProjectViewUpdates from '@/pages/projects/project-view-1/updates/project-view-updates';
|
||||
import ProjectViewTaskList from '@/pages/projects/projectView/taskList/project-view-task-list';
|
||||
import ProjectViewBoard from '@/pages/projects/projectView/board/project-view-board';
|
||||
import ProjectViewEnhancedTasks from '@/pages/projects/projectView/enhancedTasks/project-view-enhanced-tasks';
|
||||
import ProjectViewEnhancedBoard from '@/pages/projects/projectView/enhancedBoard/project-view-enhanced-board';
|
||||
|
||||
// Lazy load all project view components to reduce initial bundle size
|
||||
const ProjectViewInsights = React.lazy(() => import('@/pages/projects/projectView/insights/project-view-insights'));
|
||||
const ProjectViewFiles = React.lazy(() => import('@/pages/projects/projectView/files/project-view-files'));
|
||||
const ProjectViewMembers = React.lazy(() => import('@/pages/projects/projectView/members/project-view-members'));
|
||||
const ProjectViewUpdates = React.lazy(() => import('@/pages/projects/project-view-1/updates/project-view-updates'));
|
||||
const ProjectViewEnhancedTasks = React.lazy(() => import('@/pages/projects/projectView/enhancedTasks/project-view-enhanced-tasks'));
|
||||
const ProjectViewEnhancedBoard = React.lazy(() => import('@/pages/projects/projectView/enhancedBoard/project-view-enhanced-board'));
|
||||
|
||||
// type of a tab items
|
||||
type TabItems = {
|
||||
@@ -28,57 +28,31 @@ export const tabItems: TabItems[] = [
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
key: 'task-list-v1',
|
||||
label: 'Task List v1',
|
||||
isPinned: true,
|
||||
element: React.createElement(ProjectViewTaskList),
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
key: 'board',
|
||||
label: 'Board',
|
||||
isPinned: true,
|
||||
element: React.createElement(ProjectViewEnhancedBoard),
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
key: 'board-v1',
|
||||
label: 'Board v1',
|
||||
isPinned: true,
|
||||
element: React.createElement(ProjectViewBoard),
|
||||
},
|
||||
// {
|
||||
// index: 3,
|
||||
// key: 'workload',
|
||||
// label: 'Workload',
|
||||
// element: React.createElement(ProjectViewWorkload),
|
||||
// },
|
||||
// {
|
||||
// index: 4,
|
||||
// key: 'roadmap',
|
||||
// label: 'Roadmap',
|
||||
// element: React.createElement(ProjectViewRoadmap),
|
||||
// },
|
||||
{
|
||||
index: 5,
|
||||
index: 2,
|
||||
key: 'project-insights-member-overview',
|
||||
label: 'Insights',
|
||||
element: React.createElement(ProjectViewInsights),
|
||||
},
|
||||
{
|
||||
index: 6,
|
||||
index: 3,
|
||||
key: 'all-attachments',
|
||||
label: 'Files',
|
||||
element: React.createElement(ProjectViewFiles),
|
||||
},
|
||||
{
|
||||
index: 7,
|
||||
index: 4,
|
||||
key: 'members',
|
||||
label: 'Members',
|
||||
element: React.createElement(ProjectViewMembers),
|
||||
},
|
||||
{
|
||||
index: 8,
|
||||
index: 5,
|
||||
key: 'updates',
|
||||
label: 'Updates',
|
||||
element: React.createElement(ProjectViewUpdates),
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import {
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Flex,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography,
|
||||
ArrowLeftOutlined,
|
||||
BellFilled,
|
||||
BellOutlined,
|
||||
@@ -9,10 +15,9 @@ import {
|
||||
SaveOutlined,
|
||||
SettingOutlined,
|
||||
SyncOutlined,
|
||||
UsergroupAddOutlined,
|
||||
} from '@ant-design/icons';
|
||||
UsergroupAddOutlined
|
||||
} from '@/shared/antd-imports';
|
||||
import { PageHeader } from '@ant-design/pro-components';
|
||||
import { Button, Dropdown, Flex, Tag, Tooltip, Typography } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { PushpinFilled, PushpinOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { Badge, Button, ConfigProvider, Flex, Tabs, TabsProps, Tooltip } from 'antd';
|
||||
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
// Centralized Ant Design imports
|
||||
import {
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Flex,
|
||||
Tooltip,
|
||||
Badge,
|
||||
Tabs,
|
||||
PushpinFilled,
|
||||
PushpinOutlined,
|
||||
type TabsProps
|
||||
} from '@/shared/antd-imports';
|
||||
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||
import { getProject, setProjectId, setProjectView } from '@/features/project/project.slice';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState, useMemo, lazy, Suspense } from 'react';
|
||||
import { Empty } from '@/shared/antd-imports';
|
||||
import Flex from 'antd/es/flex';
|
||||
import Skeleton from 'antd/es/skeleton';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -10,7 +11,6 @@ import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import { fetchTaskGroups, fetchTaskListColumns } from '@/features/tasks/tasks.slice';
|
||||
import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice';
|
||||
import { fetchPhasesByProjectId } from '@/features/projects/singleProject/phase/phases.slice';
|
||||
import { Empty } from 'antd';
|
||||
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
||||
|
||||
const ProjectViewTaskList = () => {
|
||||
|
||||
198
worklenz-frontend/src/shared/antd-imports.ts
Normal file
198
worklenz-frontend/src/shared/antd-imports.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Optimized Ant Design imports for maximum tree-shaking
|
||||
*
|
||||
* This file provides:
|
||||
* - Granular imports from antd/es for better tree-shaking
|
||||
* - Only commonly used components to reduce bundle size
|
||||
* - Separate icon imports to avoid loading entire icon set
|
||||
*/
|
||||
|
||||
// Core Components - Import as default exports for better tree-shaking
|
||||
import Button from 'antd/es/button';
|
||||
import Input from 'antd/es/input';
|
||||
import Select from 'antd/es/select';
|
||||
import Typography from 'antd/es/typography';
|
||||
import Card from 'antd/es/card';
|
||||
import Spin from 'antd/es/spin';
|
||||
import Empty from 'antd/es/empty';
|
||||
import Space from 'antd/es/space';
|
||||
import Tooltip from 'antd/es/tooltip';
|
||||
import Badge from 'antd/es/badge';
|
||||
import Popconfirm from 'antd/es/popconfirm';
|
||||
import Checkbox from 'antd/es/checkbox';
|
||||
import Dropdown from 'antd/es/dropdown';
|
||||
import Menu from 'antd/es/menu';
|
||||
import Modal from 'antd/es/modal';
|
||||
import Tag from 'antd/es/tag';
|
||||
import Avatar from 'antd/es/avatar';
|
||||
import List from 'antd/es/list';
|
||||
import Table from 'antd/es/table';
|
||||
import Flex from 'antd/es/flex';
|
||||
import Divider from 'antd/es/divider';
|
||||
import Progress from 'antd/es/progress';
|
||||
import Result from 'antd/es/result';
|
||||
import Skeleton from 'antd/es/skeleton';
|
||||
import Alert from 'antd/es/alert';
|
||||
import Tabs from 'antd/es/tabs';
|
||||
import ConfigProvider from 'antd/es/config-provider';
|
||||
|
||||
// Date & Time Components
|
||||
import DatePicker from 'antd/es/date-picker';
|
||||
import TimePicker from 'antd/es/time-picker';
|
||||
|
||||
// Form Components
|
||||
import Form from 'antd/es/form';
|
||||
import InputNumber from 'antd/es/input-number';
|
||||
|
||||
// Layout Components
|
||||
import Row from 'antd/es/row';
|
||||
import Col from 'antd/es/col';
|
||||
import Layout from 'antd/es/layout';
|
||||
import Drawer from 'antd/es/drawer';
|
||||
|
||||
// Message and Notification - Import separately
|
||||
import message from 'antd/es/message';
|
||||
import notification from 'antd/es/notification';
|
||||
|
||||
// Theme
|
||||
import theme from 'antd/es/theme';
|
||||
|
||||
// Re-export all components
|
||||
export {
|
||||
Button,
|
||||
Input,
|
||||
Select,
|
||||
Typography,
|
||||
Card,
|
||||
Spin,
|
||||
Empty,
|
||||
Space,
|
||||
Tooltip,
|
||||
Badge,
|
||||
Popconfirm,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Modal,
|
||||
Tag,
|
||||
Avatar,
|
||||
List,
|
||||
Table,
|
||||
Flex,
|
||||
Divider,
|
||||
Progress,
|
||||
Result,
|
||||
Skeleton,
|
||||
Alert,
|
||||
Tabs,
|
||||
ConfigProvider,
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
Form,
|
||||
InputNumber,
|
||||
Row,
|
||||
Col,
|
||||
Layout,
|
||||
Drawer,
|
||||
message,
|
||||
notification,
|
||||
theme
|
||||
};
|
||||
|
||||
// Icons - Import only commonly used ones
|
||||
export {
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
MoreOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
CalendarOutlined,
|
||||
ClockCircleOutlined,
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
TagOutlined,
|
||||
BarsOutlined,
|
||||
AppstoreOutlined,
|
||||
FilterOutlined,
|
||||
SearchOutlined,
|
||||
SettingOutlined,
|
||||
DownOutlined,
|
||||
RightOutlined,
|
||||
LeftOutlined,
|
||||
UpOutlined,
|
||||
ArrowLeftOutlined,
|
||||
BellFilled,
|
||||
BellOutlined,
|
||||
SaveOutlined,
|
||||
SyncOutlined,
|
||||
PushpinFilled,
|
||||
PushpinOutlined,
|
||||
UsergroupAddOutlined,
|
||||
ImportOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
// TypeScript Types - Import only commonly used ones
|
||||
export type {
|
||||
ButtonProps,
|
||||
InputProps,
|
||||
InputRef,
|
||||
SelectProps,
|
||||
TypographyProps,
|
||||
CardProps,
|
||||
TooltipProps,
|
||||
DropdownProps,
|
||||
MenuProps,
|
||||
DatePickerProps,
|
||||
FormProps,
|
||||
FormInstance,
|
||||
FlexProps,
|
||||
TabsProps,
|
||||
TableProps,
|
||||
TableColumnsType
|
||||
} from 'antd';
|
||||
|
||||
// Dayjs
|
||||
export { default as dayjs } from 'dayjs';
|
||||
export type { Dayjs } from 'dayjs';
|
||||
|
||||
// Optimized message utilities
|
||||
export const appMessage = {
|
||||
success: (content: string) => message.success(content),
|
||||
error: (content: string) => message.error(content),
|
||||
warning: (content: string) => message.warning(content),
|
||||
info: (content: string) => message.info(content),
|
||||
loading: (content: string) => message.loading(content),
|
||||
};
|
||||
|
||||
export const appNotification = {
|
||||
success: (config: any) => notification.success(config),
|
||||
error: (config: any) => notification.error(config),
|
||||
warning: (config: any) => notification.warning(config),
|
||||
info: (config: any) => notification.info(config),
|
||||
};
|
||||
|
||||
// Default configurations
|
||||
export const antdConfig = {
|
||||
datePickerDefaults: {
|
||||
format: 'MMM DD, YYYY',
|
||||
placeholder: 'Set Date',
|
||||
size: 'small' as const,
|
||||
},
|
||||
buttonDefaults: {
|
||||
size: 'small' as const,
|
||||
},
|
||||
inputDefaults: {
|
||||
size: 'small' as const,
|
||||
},
|
||||
selectDefaults: {
|
||||
size: 'small' as const,
|
||||
showSearch: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
config: antdConfig,
|
||||
message: appMessage,
|
||||
notification: appNotification,
|
||||
};
|
||||
@@ -20,16 +20,34 @@ export default defineConfig(({ command, mode }) => {
|
||||
{ find: '@features', replacement: path.resolve(__dirname, './src/features') },
|
||||
{ find: '@assets', replacement: path.resolve(__dirname, './src/assets') },
|
||||
{ find: '@utils', replacement: path.resolve(__dirname, './src/utils') },
|
||||
{ find: '@services', replacement: path.resolve(__dirname, './src/services') },
|
||||
{ find: '@hooks', replacement: path.resolve(__dirname, './src/hooks') },
|
||||
{ find: '@pages', replacement: path.resolve(__dirname, './src/pages') },
|
||||
{ find: '@api', replacement: path.resolve(__dirname, './src/api') },
|
||||
{ find: '@types', replacement: path.resolve(__dirname, './src/types') },
|
||||
{ find: '@shared', replacement: path.resolve(__dirname, './src/shared') },
|
||||
{ find: '@layouts', replacement: path.resolve(__dirname, './src/layouts') },
|
||||
{ find: '@services', replacement: path.resolve(__dirname, './src/services') },
|
||||
],
|
||||
// **Ensure single React instance**
|
||||
dedupe: ['react', 'react-dom'],
|
||||
},
|
||||
|
||||
// **CSS Configuration**
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
modifyVars: {
|
||||
'@primary-color': '#1890ff',
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// **Development Server**
|
||||
server: {
|
||||
port: 5173,
|
||||
host: true,
|
||||
open: true,
|
||||
hmr: {
|
||||
overlay: false,
|
||||
@@ -47,43 +65,114 @@ export default defineConfig(({ command, mode }) => {
|
||||
cssCodeSplit: true,
|
||||
|
||||
// **Sourcemaps**
|
||||
sourcemap: !isProduction ? 'inline' : false, // Disable sourcemaps in production for smaller bundles
|
||||
sourcemap: false,
|
||||
|
||||
// **Minification**
|
||||
minify: isProduction ? 'terser' : false,
|
||||
terserOptions: isProduction ? {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
pure_funcs: ['console.log', 'console.info', 'console.debug'],
|
||||
passes: 2, // Multiple passes for better compression
|
||||
drop_console: isProduction,
|
||||
drop_debugger: isProduction,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
},
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
} : undefined,
|
||||
},
|
||||
|
||||
// **Chunk Size Warnings**
|
||||
chunkSizeWarningLimit: 1000,
|
||||
|
||||
// **Rollup Options**
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// **Simplified Chunking Strategy to avoid React context issues**
|
||||
manualChunks: {
|
||||
// Keep React and all React-dependent libraries together
|
||||
'react-vendor': ['react', 'react-dom', 'react/jsx-runtime'],
|
||||
// **Rollup Options**
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// **Manual Chunking Strategy**
|
||||
manualChunks: (id) => {
|
||||
// Vendor libraries
|
||||
if (id.includes('node_modules')) {
|
||||
// Ant Design - Split into smaller chunks
|
||||
if (id.includes('antd/es')) {
|
||||
// Split antd by component types for better caching
|
||||
if (id.includes('date-picker') || id.includes('time-picker') || id.includes('calendar')) {
|
||||
return 'antd-date';
|
||||
}
|
||||
if (id.includes('table') || id.includes('list') || id.includes('tree')) {
|
||||
return 'antd-data';
|
||||
}
|
||||
if (id.includes('form') || id.includes('input') || id.includes('select') || id.includes('checkbox')) {
|
||||
return 'antd-form';
|
||||
}
|
||||
if (id.includes('button') || id.includes('tooltip') || id.includes('dropdown') || id.includes('menu')) {
|
||||
return 'antd-basic';
|
||||
}
|
||||
if (id.includes('modal') || id.includes('drawer') || id.includes('popconfirm')) {
|
||||
return 'antd-overlay';
|
||||
}
|
||||
// Catch remaining antd components
|
||||
return 'antd-misc';
|
||||
}
|
||||
|
||||
// Separate chunk for router
|
||||
'react-router': ['react-router-dom'],
|
||||
// Icons
|
||||
if (id.includes('@ant-design/icons')) {
|
||||
return 'antd-icons';
|
||||
}
|
||||
|
||||
// Keep Ant Design separate but ensure React is available
|
||||
'antd': ['antd', '@ant-design/icons'],
|
||||
},
|
||||
|
||||
// Chart.js and related
|
||||
if (id.includes('chart.js') || id.includes('react-chartjs-2')) {
|
||||
return 'charts';
|
||||
}
|
||||
|
||||
// Redux and related state management
|
||||
if (id.includes('@reduxjs/toolkit') || id.includes('react-redux')) {
|
||||
return 'redux';
|
||||
}
|
||||
|
||||
// React Router
|
||||
if (id.includes('react-router')) {
|
||||
return 'router';
|
||||
}
|
||||
|
||||
// i18n
|
||||
if (id.includes('react-i18next') || id.includes('i18next')) {
|
||||
return 'i18n';
|
||||
}
|
||||
|
||||
// Other large libraries
|
||||
if (id.includes('lodash')) {
|
||||
return 'lodash';
|
||||
}
|
||||
|
||||
if (id.includes('dayjs')) {
|
||||
return 'dayjs';
|
||||
}
|
||||
|
||||
// Remaining vendor code
|
||||
return 'vendor';
|
||||
}
|
||||
|
||||
// Application code chunking
|
||||
// Project view components
|
||||
if (id.includes('src/pages/projects/projectView')) {
|
||||
return 'project-view';
|
||||
}
|
||||
|
||||
// Other project components
|
||||
if (id.includes('src/pages/projects')) {
|
||||
return 'projects';
|
||||
}
|
||||
|
||||
// Task management components
|
||||
if (id.includes('src/components/task-') || id.includes('src/features/tasks')) {
|
||||
return 'tasks';
|
||||
}
|
||||
|
||||
// Settings and admin components
|
||||
if (id.includes('src/pages/settings') || id.includes('src/components/admin')) {
|
||||
return 'admin';
|
||||
}
|
||||
|
||||
// Common components
|
||||
if (id.includes('src/components/common') || id.includes('src/shared')) {
|
||||
return 'common';
|
||||
}
|
||||
},
|
||||
|
||||
// **File Naming Strategies**
|
||||
chunkFileNames: (chunkInfo) => {
|
||||
const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/').pop() : 'chunk';
|
||||
@@ -103,6 +192,12 @@ export default defineConfig(({ command, mode }) => {
|
||||
},
|
||||
},
|
||||
|
||||
// **Tree shaking optimization**
|
||||
treeshake: {
|
||||
moduleSideEffects: false,
|
||||
unknownGlobalSideEffects: false,
|
||||
},
|
||||
|
||||
// **External dependencies (if any should be externalized)**
|
||||
external: [],
|
||||
|
||||
@@ -122,15 +217,17 @@ export default defineConfig(({ command, mode }) => {
|
||||
include: [
|
||||
'react',
|
||||
'react-dom',
|
||||
'react/jsx-runtime',
|
||||
'react-router-dom',
|
||||
'@reduxjs/toolkit',
|
||||
'react-redux',
|
||||
'react-i18next',
|
||||
'dayjs',
|
||||
],
|
||||
exclude: [
|
||||
// Exclude antd from pre-bundling to allow better tree-shaking
|
||||
'antd',
|
||||
'@ant-design/icons',
|
||||
],
|
||||
exclude: [
|
||||
// Add any packages that should not be pre-bundled
|
||||
],
|
||||
// Force pre-bundling to avoid runtime issues
|
||||
force: true,
|
||||
},
|
||||
|
||||
// **Define global constants**
|
||||
|
||||
Reference in New Issue
Block a user