mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 10:14:15 +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:
parent
c5b71041d3
commit
09df07ddf5
5 changed files with 251 additions and 176 deletions
20
frontend/src/components/LazyMount.tsx
Normal file
20
frontend/src/components/LazyMount.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Suspense, useEffect, useState, type ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface LazyMountProps {
|
||||||
|
when: boolean;
|
||||||
|
fallback?: ReactNode;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mounts children only after `when` first becomes true and keeps them mounted
|
||||||
|
// thereafter, so React.lazy modals get loaded on demand but their close
|
||||||
|
// animations still play out. Pair with `lazy(() => import(...))` modal imports
|
||||||
|
// on heavy list pages to keep the initial bundle small.
|
||||||
|
export default function LazyMount({ when, fallback = null, children }: LazyMountProps) {
|
||||||
|
const [mounted, setMounted] = useState(when);
|
||||||
|
useEffect(() => {
|
||||||
|
if (when && !mounted) setMounted(true);
|
||||||
|
}, [when, mounted]);
|
||||||
|
if (!mounted) return null;
|
||||||
|
return <Suspense fallback={fallback}>{children}</Suspense>;
|
||||||
|
}
|
||||||
|
|
@ -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 { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
|
|
@ -51,11 +51,12 @@ import AppSidebar from '@/components/AppSidebar';
|
||||||
import CustomStatistic from '@/components/CustomStatistic';
|
import CustomStatistic from '@/components/CustomStatistic';
|
||||||
import { IntlUtil, SizeFormatter } from '@/utils';
|
import { IntlUtil, SizeFormatter } from '@/utils';
|
||||||
import { setMessageInstance } from '@/utils/messageBus';
|
import { setMessageInstance } from '@/utils/messageBus';
|
||||||
import ClientFormModal from './ClientFormModal';
|
import LazyMount from '@/components/LazyMount';
|
||||||
import ClientInfoModal from './ClientInfoModal';
|
const ClientFormModal = lazy(() => import('./ClientFormModal'));
|
||||||
import ClientQrModal from './ClientQrModal';
|
const ClientInfoModal = lazy(() => import('./ClientInfoModal'));
|
||||||
import ClientBulkAddModal from './ClientBulkAddModal';
|
const ClientQrModal = lazy(() => import('./ClientQrModal'));
|
||||||
import ClientBulkAdjustModal from './ClientBulkAdjustModal';
|
const ClientBulkAddModal = lazy(() => import('./ClientBulkAddModal'));
|
||||||
|
const ClientBulkAdjustModal = lazy(() => import('./ClientBulkAdjustModal'));
|
||||||
import '@/styles/page-cards.css';
|
import '@/styles/page-cards.css';
|
||||||
import './ClientsPage.css';
|
import './ClientsPage.css';
|
||||||
|
|
||||||
|
|
@ -853,51 +854,61 @@ export default function ClientsPage() {
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<ClientFormModal
|
<LazyMount when={formOpen}>
|
||||||
open={formOpen}
|
<ClientFormModal
|
||||||
mode={formMode}
|
open={formOpen}
|
||||||
client={editingClient}
|
mode={formMode}
|
||||||
attachedIds={editingAttachedIds}
|
client={editingClient}
|
||||||
inbounds={inbounds}
|
attachedIds={editingAttachedIds}
|
||||||
ipLimitEnable={ipLimitEnable}
|
inbounds={inbounds}
|
||||||
tgBotEnable={tgBotEnable}
|
ipLimitEnable={ipLimitEnable}
|
||||||
save={onSave}
|
tgBotEnable={tgBotEnable}
|
||||||
onOpenChange={setFormOpen}
|
save={onSave}
|
||||||
/>
|
onOpenChange={setFormOpen}
|
||||||
<ClientInfoModal
|
/>
|
||||||
open={infoOpen}
|
</LazyMount>
|
||||||
client={infoClient}
|
<LazyMount when={infoOpen}>
|
||||||
inboundsById={inboundsById}
|
<ClientInfoModal
|
||||||
isOnline={infoClient ? isOnline(infoClient.email) : false}
|
open={infoOpen}
|
||||||
subSettings={subSettings}
|
client={infoClient}
|
||||||
onOpenChange={setInfoOpen}
|
inboundsById={inboundsById}
|
||||||
/>
|
isOnline={infoClient ? isOnline(infoClient.email) : false}
|
||||||
<ClientQrModal
|
subSettings={subSettings}
|
||||||
open={qrOpen}
|
onOpenChange={setInfoOpen}
|
||||||
client={qrClient}
|
/>
|
||||||
subSettings={subSettings}
|
</LazyMount>
|
||||||
onOpenChange={setQrOpen}
|
<LazyMount when={qrOpen}>
|
||||||
/>
|
<ClientQrModal
|
||||||
<ClientBulkAddModal
|
open={qrOpen}
|
||||||
open={bulkAddOpen}
|
client={qrClient}
|
||||||
inbounds={inbounds}
|
subSettings={subSettings}
|
||||||
ipLimitEnable={ipLimitEnable}
|
onOpenChange={setQrOpen}
|
||||||
onOpenChange={setBulkAddOpen}
|
/>
|
||||||
onSaved={() => setBulkAddOpen(false)}
|
</LazyMount>
|
||||||
/>
|
<LazyMount when={bulkAddOpen}>
|
||||||
<ClientBulkAdjustModal
|
<ClientBulkAddModal
|
||||||
open={bulkAdjustOpen}
|
open={bulkAddOpen}
|
||||||
count={selectedRowKeys.length}
|
inbounds={inbounds}
|
||||||
onOpenChange={setBulkAdjustOpen}
|
ipLimitEnable={ipLimitEnable}
|
||||||
onSubmit={async (addDays, addBytes) => {
|
onOpenChange={setBulkAddOpen}
|
||||||
const msg = await bulkAdjust([...selectedRowKeys], addDays, addBytes);
|
onSaved={() => setBulkAddOpen(false)}
|
||||||
if (msg?.success) {
|
/>
|
||||||
setSelectedRowKeys([]);
|
</LazyMount>
|
||||||
return msg.obj ?? { adjusted: 0 };
|
<LazyMount when={bulkAdjustOpen}>
|
||||||
}
|
<ClientBulkAdjustModal
|
||||||
return null;
|
open={bulkAdjustOpen}
|
||||||
}}
|
count={selectedRowKeys.length}
|
||||||
/>
|
onOpenChange={setBulkAdjustOpen}
|
||||||
|
onSubmit={async (addDays, addBytes) => {
|
||||||
|
const msg = await bulkAdjust([...selectedRowKeys], addDays, addBytes);
|
||||||
|
if (msg?.success) {
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
return msg.obj ?? { adjusted: 0 };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</LazyMount>
|
||||||
</Layout>
|
</Layout>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { lazy, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -28,14 +28,15 @@ import { useWebSocket } from '@/hooks/useWebSocket';
|
||||||
import { useNodes } from '@/hooks/useNodes';
|
import { useNodes } from '@/hooks/useNodes';
|
||||||
import AppSidebar from '@/components/AppSidebar';
|
import AppSidebar from '@/components/AppSidebar';
|
||||||
import CustomStatistic from '@/components/CustomStatistic';
|
import CustomStatistic from '@/components/CustomStatistic';
|
||||||
import TextModal from '@/components/TextModal';
|
const TextModal = lazy(() => import('@/components/TextModal'));
|
||||||
import PromptModal from '@/components/PromptModal';
|
const PromptModal = lazy(() => import('@/components/PromptModal'));
|
||||||
|
|
||||||
import { useInbounds } from './useInbounds';
|
import { useInbounds } from './useInbounds';
|
||||||
import InboundList from './InboundList';
|
import InboundList from './InboundList';
|
||||||
import InboundFormModal from './InboundFormModal';
|
import LazyMount from '@/components/LazyMount';
|
||||||
import InboundInfoModal from './InboundInfoModal';
|
const InboundFormModal = lazy(() => import('./InboundFormModal'));
|
||||||
import QrCodeModal from './QrCodeModal';
|
const InboundInfoModal = lazy(() => import('./InboundInfoModal'));
|
||||||
|
const QrCodeModal = lazy(() => import('./QrCodeModal'));
|
||||||
import '@/styles/page-cards.css';
|
import '@/styles/page-cards.css';
|
||||||
import './InboundsPage.css';
|
import './InboundsPage.css';
|
||||||
|
|
||||||
|
|
@ -517,56 +518,66 @@ export default function InboundsPage() {
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<InboundFormModal
|
<LazyMount when={formOpen}>
|
||||||
open={formOpen}
|
<InboundFormModal
|
||||||
onClose={() => setFormOpen(false)}
|
open={formOpen}
|
||||||
onSaved={refresh}
|
onClose={() => setFormOpen(false)}
|
||||||
mode={formMode}
|
onSaved={refresh}
|
||||||
dbInbound={formDbInbound}
|
mode={formMode}
|
||||||
dbInbounds={dbInbounds as any[]}
|
dbInbound={formDbInbound}
|
||||||
availableNodes={nodesList}
|
dbInbounds={dbInbounds as any[]}
|
||||||
/>
|
availableNodes={nodesList}
|
||||||
<InboundInfoModal
|
/>
|
||||||
open={infoOpen}
|
</LazyMount>
|
||||||
onClose={() => setInfoOpen(false)}
|
<LazyMount when={infoOpen}>
|
||||||
dbInbound={infoDbInbound}
|
<InboundInfoModal
|
||||||
clientIndex={infoClientIndex}
|
open={infoOpen}
|
||||||
remarkModel={remarkModel}
|
onClose={() => setInfoOpen(false)}
|
||||||
expireDiff={expireDiff}
|
dbInbound={infoDbInbound}
|
||||||
trafficDiff={trafficDiff}
|
clientIndex={infoClientIndex}
|
||||||
ipLimitEnable={ipLimitEnable}
|
remarkModel={remarkModel}
|
||||||
tgBotEnable={tgBotEnable}
|
expireDiff={expireDiff}
|
||||||
subSettings={subSettings}
|
trafficDiff={trafficDiff}
|
||||||
lastOnlineMap={lastOnlineMap}
|
ipLimitEnable={ipLimitEnable}
|
||||||
nodeAddress={infoNodeAddress}
|
tgBotEnable={tgBotEnable}
|
||||||
/>
|
subSettings={subSettings}
|
||||||
<QrCodeModal
|
lastOnlineMap={lastOnlineMap}
|
||||||
open={qrOpen}
|
nodeAddress={infoNodeAddress}
|
||||||
onClose={() => setQrOpen(false)}
|
/>
|
||||||
dbInbound={qrDbInbound}
|
</LazyMount>
|
||||||
client={null}
|
<LazyMount when={qrOpen}>
|
||||||
remarkModel={remarkModel}
|
<QrCodeModal
|
||||||
nodeAddress={qrNodeAddress}
|
open={qrOpen}
|
||||||
subSettings={subSettings}
|
onClose={() => setQrOpen(false)}
|
||||||
/>
|
dbInbound={qrDbInbound}
|
||||||
|
client={null}
|
||||||
|
remarkModel={remarkModel}
|
||||||
|
nodeAddress={qrNodeAddress}
|
||||||
|
subSettings={subSettings}
|
||||||
|
/>
|
||||||
|
</LazyMount>
|
||||||
|
|
||||||
<TextModal
|
<LazyMount when={textOpen}>
|
||||||
open={textOpen}
|
<TextModal
|
||||||
onClose={() => setTextOpen(false)}
|
open={textOpen}
|
||||||
title={textTitle}
|
onClose={() => setTextOpen(false)}
|
||||||
content={textContent}
|
title={textTitle}
|
||||||
fileName={textFileName}
|
content={textContent}
|
||||||
/>
|
fileName={textFileName}
|
||||||
<PromptModal
|
/>
|
||||||
open={promptOpen}
|
</LazyMount>
|
||||||
onClose={() => setPromptOpen(false)}
|
<LazyMount when={promptOpen}>
|
||||||
title={promptTitle}
|
<PromptModal
|
||||||
okText={promptOkText}
|
open={promptOpen}
|
||||||
type={promptType}
|
onClose={() => setPromptOpen(false)}
|
||||||
initialValue={promptInitial}
|
title={promptTitle}
|
||||||
loading={promptLoading}
|
okText={promptOkText}
|
||||||
onConfirm={onPromptConfirm}
|
type={promptType}
|
||||||
/>
|
initialValue={promptInitial}
|
||||||
|
loading={promptLoading}
|
||||||
|
onConfirm={onPromptConfirm}
|
||||||
|
/>
|
||||||
|
</LazyMount>
|
||||||
</Layout>
|
</Layout>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -40,18 +40,19 @@ import { useStatus } from '@/hooks/useStatus';
|
||||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||||
import AppSidebar from '@/components/AppSidebar';
|
import AppSidebar from '@/components/AppSidebar';
|
||||||
import CustomStatistic from '@/components/CustomStatistic';
|
import CustomStatistic from '@/components/CustomStatistic';
|
||||||
import JsonEditor from '@/components/JsonEditor';
|
import LazyMount from '@/components/LazyMount';
|
||||||
import { setMessageInstance } from '@/utils/messageBus';
|
import { setMessageInstance } from '@/utils/messageBus';
|
||||||
import StatusCard from './StatusCard';
|
import StatusCard from './StatusCard';
|
||||||
import XrayStatusCard from './XrayStatusCard';
|
import XrayStatusCard from './XrayStatusCard';
|
||||||
import PanelUpdateModal from './PanelUpdateModal';
|
|
||||||
import type { PanelUpdateInfo } from './PanelUpdateModal';
|
import type { PanelUpdateInfo } from './PanelUpdateModal';
|
||||||
import LogModal from './LogModal';
|
const JsonEditor = lazy(() => import('@/components/JsonEditor'));
|
||||||
import BackupModal from './BackupModal';
|
const PanelUpdateModal = lazy(() => import('./PanelUpdateModal'));
|
||||||
import SystemHistoryModal from './SystemHistoryModal';
|
const LogModal = lazy(() => import('./LogModal'));
|
||||||
import XrayMetricsModal from './XrayMetricsModal';
|
const BackupModal = lazy(() => import('./BackupModal'));
|
||||||
import XrayLogModal from './XrayLogModal';
|
const SystemHistoryModal = lazy(() => import('./SystemHistoryModal'));
|
||||||
import VersionModal from './VersionModal';
|
const XrayMetricsModal = lazy(() => import('./XrayMetricsModal'));
|
||||||
|
const XrayLogModal = lazy(() => import('./XrayLogModal'));
|
||||||
|
const VersionModal = lazy(() => import('./VersionModal'));
|
||||||
import '@/styles/page-cards.css';
|
import '@/styles/page-cards.css';
|
||||||
import './IndexPage.css';
|
import './IndexPage.css';
|
||||||
|
|
||||||
|
|
@ -435,67 +436,83 @@ export default function IndexPage() {
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<PanelUpdateModal
|
<LazyMount when={panelUpdateOpen}>
|
||||||
open={panelUpdateOpen}
|
<PanelUpdateModal
|
||||||
info={panelUpdateInfo}
|
open={panelUpdateOpen}
|
||||||
onClose={() => setPanelUpdateOpen(false)}
|
info={panelUpdateInfo}
|
||||||
onBusy={setBusy}
|
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
|
|
||||||
/>
|
/>
|
||||||
</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>
|
</Layout>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,16 @@ export default defineConfig({
|
||||||
manualChunks(id) {
|
manualChunks(id) {
|
||||||
if (!id.includes('node_modules')) return undefined;
|
if (!id.includes('node_modules')) return undefined;
|
||||||
if (id.includes('/node_modules/antd/')) return 'vendor-antd';
|
if (id.includes('/node_modules/antd/')) return 'vendor-antd';
|
||||||
if (id.includes('/@ant-design/icons/')) return 'vendor-icons';
|
if (id.includes('/@ant-design/icons/') || id.includes('/@ant-design/icons-svg/')) return 'vendor-icons';
|
||||||
|
if (
|
||||||
|
id.includes('/node_modules/@rc-component/')
|
||||||
|
|| id.includes('/node_modules/rc-')
|
||||||
|
|| id.includes('/@ant-design/cssinjs')
|
||||||
|
|| id.includes('/@ant-design/colors')
|
||||||
|
|| id.includes('/@ant-design/fast-color')
|
||||||
|
|| id.includes('/@ant-design/react-slick')
|
||||||
|
|| id.includes('/@ctrl/tinycolor')
|
||||||
|
) return 'vendor-antd';
|
||||||
if (
|
if (
|
||||||
id.includes('/node_modules/react-i18next/')
|
id.includes('/node_modules/react-i18next/')
|
||||||
|| id.includes('/node_modules/i18next/')
|
|| id.includes('/node_modules/i18next/')
|
||||||
|
|
@ -184,6 +193,13 @@ export default defineConfig({
|
||||||
|| id.includes('/node_modules/react-dom/')
|
|| id.includes('/node_modules/react-dom/')
|
||||||
|| id.includes('/node_modules/scheduler/')
|
|| id.includes('/node_modules/scheduler/')
|
||||||
) return 'vendor-react';
|
) return 'vendor-react';
|
||||||
|
if (
|
||||||
|
id.includes('/node_modules/codemirror/')
|
||||||
|
|| id.includes('/node_modules/@codemirror/')
|
||||||
|
|| id.includes('/node_modules/@lezer/')
|
||||||
|
) return 'vendor-codemirror';
|
||||||
|
if (id.includes('/node_modules/persian-calendar-suite/')) return 'vendor-jalali';
|
||||||
|
if (id.includes('/node_modules/otpauth/')) return 'vendor-otpauth';
|
||||||
if (id.includes('dayjs')) return 'vendor-dayjs';
|
if (id.includes('dayjs')) return 'vendor-dayjs';
|
||||||
if (id.includes('axios')) return 'vendor-axios';
|
if (id.includes('axios')) return 'vendor-axios';
|
||||||
return 'vendor';
|
return 'vendor';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue