mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-14 23:31:00 +00:00
perf(frontend): lazy-load modals + split heavy vendor chunks (#4501)
* perf(frontend): lazy-load modals on inbounds / clients / index pages Modals on the three list pages were imported statically, so the JS + CSS for every form, info, qr, log, backup, metrics, system-history, version, and config-text modal sat in the initial bundle even though they're only needed after a click. Converted those imports to React.lazy() and gated each modal with a new LazyMount helper that mounts on first open and keeps the component mounted thereafter so AntD close animations still play. Build now emits a dedicated chunk per modal — InboundFormModal at 66 kB (13 kB gzipped) and InboundInfoModal at 23 kB (4 kB gzipped) are the largest, totalling roughly 150 kB of code that no longer parses on first paint. Profiler measured the inbounds-page React render tree drop from ~444 ms to ~254 ms on a prod build. * perf(frontend): split codemirror / jalali / otpauth into lazy vendor chunks Heavy libs (codemirror, persian-calendar-suite, otpauth) and antd's rc-/cssinjs transitive deps used to fall into the catch-all `vendor` chunk and load with every entry point. Give them their own manualChunks groups so they only load with the lazy modal/page that needs them. Initial vendor (catch-all) drops from 1293 kB / 408 kB gzip to 76 kB / 27 kB gzip; codemirror (408 kB / 131 kB gzip) is now on the JsonEditor lazy path instead of the inbounds/clients/index initial load.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { lazy, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Button,
|
||||
@@ -40,18 +40,19 @@ import { useStatus } from '@/hooks/useStatus';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import AppSidebar from '@/components/AppSidebar';
|
||||
import CustomStatistic from '@/components/CustomStatistic';
|
||||
import JsonEditor from '@/components/JsonEditor';
|
||||
import LazyMount from '@/components/LazyMount';
|
||||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
import StatusCard from './StatusCard';
|
||||
import XrayStatusCard from './XrayStatusCard';
|
||||
import PanelUpdateModal from './PanelUpdateModal';
|
||||
import type { PanelUpdateInfo } from './PanelUpdateModal';
|
||||
import LogModal from './LogModal';
|
||||
import BackupModal from './BackupModal';
|
||||
import SystemHistoryModal from './SystemHistoryModal';
|
||||
import XrayMetricsModal from './XrayMetricsModal';
|
||||
import XrayLogModal from './XrayLogModal';
|
||||
import VersionModal from './VersionModal';
|
||||
const JsonEditor = lazy(() => import('@/components/JsonEditor'));
|
||||
const PanelUpdateModal = lazy(() => import('./PanelUpdateModal'));
|
||||
const LogModal = lazy(() => import('./LogModal'));
|
||||
const BackupModal = lazy(() => import('./BackupModal'));
|
||||
const SystemHistoryModal = lazy(() => import('./SystemHistoryModal'));
|
||||
const XrayMetricsModal = lazy(() => import('./XrayMetricsModal'));
|
||||
const XrayLogModal = lazy(() => import('./XrayLogModal'));
|
||||
const VersionModal = lazy(() => import('./VersionModal'));
|
||||
import '@/styles/page-cards.css';
|
||||
import './IndexPage.css';
|
||||
|
||||
@@ -435,67 +436,83 @@ export default function IndexPage() {
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
|
||||
<PanelUpdateModal
|
||||
open={panelUpdateOpen}
|
||||
info={panelUpdateInfo}
|
||||
onClose={() => setPanelUpdateOpen(false)}
|
||||
onBusy={setBusy}
|
||||
/>
|
||||
<LogModal open={logsOpen} onClose={() => setLogsOpen(false)} />
|
||||
<BackupModal
|
||||
open={backupOpen}
|
||||
basePath={basePath}
|
||||
onClose={() => setBackupOpen(false)}
|
||||
onBusy={setBusy}
|
||||
/>
|
||||
<SystemHistoryModal
|
||||
open={sysHistoryOpen}
|
||||
status={status}
|
||||
onClose={() => setSysHistoryOpen(false)}
|
||||
/>
|
||||
<XrayMetricsModal open={xrayMetricsOpen} onClose={() => setXrayMetricsOpen(false)} />
|
||||
<XrayLogModal open={xrayLogsOpen} onClose={() => setXrayLogsOpen(false)} />
|
||||
<VersionModal
|
||||
open={versionOpen}
|
||||
status={status}
|
||||
onClose={() => setVersionOpen(false)}
|
||||
onBusy={setBusy}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
open={configTextOpen}
|
||||
title={t('pages.index.config')}
|
||||
width={isMobile ? '100%' : 900}
|
||||
style={isMobile ? { top: 20, maxWidth: 'calc(100vw - 16px)' } : undefined}
|
||||
onCancel={() => setConfigTextOpen(false)}
|
||||
footer={[
|
||||
<Button
|
||||
key="download"
|
||||
onClick={downloadConfig}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
icon={<CloudDownloadOutlined />}
|
||||
>
|
||||
{isMobile ? 'Download' : 'config.json'}
|
||||
</Button>,
|
||||
<Button
|
||||
key="copy"
|
||||
type="primary"
|
||||
onClick={copyConfig}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
icon={<CopyOutlined />}
|
||||
>
|
||||
Copy
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<JsonEditor
|
||||
value={configText}
|
||||
onChange={setConfigText}
|
||||
minHeight={isMobile ? '300px' : '420px'}
|
||||
maxHeight={isMobile ? '500px' : '720px'}
|
||||
readOnly
|
||||
<LazyMount when={panelUpdateOpen}>
|
||||
<PanelUpdateModal
|
||||
open={panelUpdateOpen}
|
||||
info={panelUpdateInfo}
|
||||
onClose={() => setPanelUpdateOpen(false)}
|
||||
onBusy={setBusy}
|
||||
/>
|
||||
</Modal>
|
||||
</LazyMount>
|
||||
<LazyMount when={logsOpen}>
|
||||
<LogModal open={logsOpen} onClose={() => setLogsOpen(false)} />
|
||||
</LazyMount>
|
||||
<LazyMount when={backupOpen}>
|
||||
<BackupModal
|
||||
open={backupOpen}
|
||||
basePath={basePath}
|
||||
onClose={() => setBackupOpen(false)}
|
||||
onBusy={setBusy}
|
||||
/>
|
||||
</LazyMount>
|
||||
<LazyMount when={sysHistoryOpen}>
|
||||
<SystemHistoryModal
|
||||
open={sysHistoryOpen}
|
||||
status={status}
|
||||
onClose={() => setSysHistoryOpen(false)}
|
||||
/>
|
||||
</LazyMount>
|
||||
<LazyMount when={xrayMetricsOpen}>
|
||||
<XrayMetricsModal open={xrayMetricsOpen} onClose={() => setXrayMetricsOpen(false)} />
|
||||
</LazyMount>
|
||||
<LazyMount when={xrayLogsOpen}>
|
||||
<XrayLogModal open={xrayLogsOpen} onClose={() => setXrayLogsOpen(false)} />
|
||||
</LazyMount>
|
||||
<LazyMount when={versionOpen}>
|
||||
<VersionModal
|
||||
open={versionOpen}
|
||||
status={status}
|
||||
onClose={() => setVersionOpen(false)}
|
||||
onBusy={setBusy}
|
||||
/>
|
||||
</LazyMount>
|
||||
|
||||
<LazyMount when={configTextOpen}>
|
||||
<Modal
|
||||
open={configTextOpen}
|
||||
title={t('pages.index.config')}
|
||||
width={isMobile ? '100%' : 900}
|
||||
style={isMobile ? { top: 20, maxWidth: 'calc(100vw - 16px)' } : undefined}
|
||||
onCancel={() => setConfigTextOpen(false)}
|
||||
footer={[
|
||||
<Button
|
||||
key="download"
|
||||
onClick={downloadConfig}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
icon={<CloudDownloadOutlined />}
|
||||
>
|
||||
{isMobile ? 'Download' : 'config.json'}
|
||||
</Button>,
|
||||
<Button
|
||||
key="copy"
|
||||
type="primary"
|
||||
onClick={copyConfig}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
icon={<CopyOutlined />}
|
||||
>
|
||||
Copy
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<JsonEditor
|
||||
value={configText}
|
||||
onChange={setConfigText}
|
||||
minHeight={isMobile ? '300px' : '420px'}
|
||||
maxHeight={isMobile ? '500px' : '720px'}
|
||||
readOnly
|
||||
/>
|
||||
</Modal>
|
||||
</LazyMount>
|
||||
</Layout>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user