This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 33174e6f feat: support route-level lazy loading (#764)
33174e6f is described below

commit 33174e6fe14729eecfeb4b0e67b5a4ca242b4d4c
Author: yx9o <[email protected]>
AuthorDate: Mon Aug 3 11:38:36 2026 +0800

    feat: support route-level lazy loading (#764)
---
 web/src/App.test.tsx |  39 +++++++++++++++-
 web/src/App.tsx      | 127 ++++++++++++++++++++++++++++++---------------------
 2 files changed, 112 insertions(+), 54 deletions(-)

diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx
index 4ac35351..13c152de 100644
--- a/web/src/App.test.tsx
+++ b/web/src/App.test.tsx
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-import { cleanup, fireEvent, render, screen, waitFor } from 
'@testing-library/react';
+import { act, cleanup, fireEvent, render, screen, waitFor } from 
'@testing-library/react';
+import { lazy, type ComponentType } from 'react';
 import { MemoryRouter, Route, Routes } from 'react-router-dom';
 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 import { getAuthStatus } from './api/auth';
-import { AuthGate } from './App';
+import { AuthGate, LazyRouteOutlet } from './App';
 import { LangProvider } from './i18n/LangContext';
 
 vi.mock('./api/auth', async (importOriginal) => {
@@ -31,6 +32,40 @@ vi.mock('./config', () => ({ API_BASE_URL: '/api', USE_MOCK: 
false }));
 
 const mockedGetAuthStatus = vi.mocked(getAuthStatus);
 
+describe('LazyRouteOutlet', () => {
+  afterEach(() => cleanup());
+
+  it('shows a localized fallback until the route module loads', async () => {
+    let resolvePage!: (module: { default: ComponentType }) => void;
+    const pageModule = new Promise<{ default: ComponentType }>((resolve) => {
+      resolvePage = resolve;
+    });
+    const LazyPage = lazy(() => pageModule);
+
+    render(
+      <LangProvider>
+        <MemoryRouter initialEntries={['/lazy']}>
+          <Routes>
+            <Route element={<LazyRouteOutlet />}>
+              <Route path="/lazy" element={<LazyPage />} />
+            </Route>
+          </Routes>
+        </MemoryRouter>
+      </LangProvider>,
+    );
+
+    expect(screen.getByRole('status', { name: '加载中' })).toBeInTheDocument();
+
+    await act(async () => {
+      resolvePage({ default: () => <div>lazy page loaded</div> });
+      await pageModule;
+    });
+
+    expect(await screen.findByText('lazy page loaded')).toBeInTheDocument();
+    expect(screen.queryByRole('status', { name: '加载中' 
})).not.toBeInTheDocument();
+  });
+});
+
 function renderGate() {
   return render(
     <LangProvider>
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 627fceb7..fa54c406 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import { useCallback, useEffect, useState } from 'react';
+import { lazy, Suspense, useCallback, useEffect, useState } from 'react';
 import { Button, Result, Spin } from 'antd';
 import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
 import { getAuthStatus } from './api/auth';
@@ -23,33 +23,34 @@ import { USE_MOCK } from './config';
 import { useLang } from './i18n/LangContext';
 import useAuthStore from './stores/authStore';
 import MainLayout from './layouts/MainLayout';
-import HomePage from './pages/home';
-import InstancePage from './pages/instance';
-import TopicPage from './pages/instance/topic';
-import ConsumerPage from './pages/instance/consumer';
-import MessagePage from './pages/instance/message';
-import AclPage from './pages/instance/acl';
-import DlqPage from './pages/instance/dlq';
-import ClusterPage from './pages/cluster';
-import K8sCertsPage from './pages/cluster/certs';
-import ClientsPage from './pages/cluster/clients';
-import DashboardOpsPage from './pages/home/dashboard';
-import AlertsPage from './pages/ops/alerts';
-import SystemAlertsPage from './pages/ops/systemAlerts';
-import AuditPage from './pages/ops/audit';
-import AiPage from './pages/ai';
-import SettingsPage from './pages/settings';
-import LlmSettingsPage from './pages/studio/LlmSettings';
-import ProxyPage from './pages/studio/Proxy';
-import LiteTopicPage from './pages/studio/LiteTopic';
-import GroupManagementPage from './pages/studio/GroupManagement';
-import BrokerClusterPage from './pages/studio/BrokerCluster';
-import SslSettingsPage from './pages/studio/SslSettings';
-import AlertManagementPage from './pages/studio/AlertManagement';
-import ProducerPage from './pages/studio/Producer';
-import OpsPage from './pages/studio/Ops';
 import LoginPage from './pages/login';
 
+const HomePage = lazy(() => import('./pages/home'));
+const InstancePage = lazy(() => import('./pages/instance'));
+const TopicPage = lazy(() => import('./pages/instance/topic'));
+const ConsumerPage = lazy(() => import('./pages/instance/consumer'));
+const MessagePage = lazy(() => import('./pages/instance/message'));
+const AclPage = lazy(() => import('./pages/instance/acl'));
+const DlqPage = lazy(() => import('./pages/instance/dlq'));
+const ClusterPage = lazy(() => import('./pages/cluster'));
+const K8sCertsPage = lazy(() => import('./pages/cluster/certs'));
+const ClientsPage = lazy(() => import('./pages/cluster/clients'));
+const DashboardOpsPage = lazy(() => import('./pages/home/dashboard'));
+const AlertsPage = lazy(() => import('./pages/ops/alerts'));
+const SystemAlertsPage = lazy(() => import('./pages/ops/systemAlerts'));
+const AuditPage = lazy(() => import('./pages/ops/audit'));
+const AiPage = lazy(() => import('./pages/ai'));
+const SettingsPage = lazy(() => import('./pages/settings'));
+const LlmSettingsPage = lazy(() => import('./pages/studio/LlmSettings'));
+const ProxyPage = lazy(() => import('./pages/studio/Proxy'));
+const LiteTopicPage = lazy(() => import('./pages/studio/LiteTopic'));
+const GroupManagementPage = lazy(() => 
import('./pages/studio/GroupManagement'));
+const BrokerClusterPage = lazy(() => import('./pages/studio/BrokerCluster'));
+const SslSettingsPage = lazy(() => import('./pages/studio/SslSettings'));
+const AlertManagementPage = lazy(() => 
import('./pages/studio/AlertManagement'));
+const ProducerPage = lazy(() => import('./pages/studio/Producer'));
+const OpsPage = lazy(() => import('./pages/studio/Ops'));
+
 type AuthGateState = 'checking' | 'allowed' | 'denied' | 'error';
 
 export function AuthGate() {
@@ -114,38 +115,60 @@ export function AuthGate() {
   return <Outlet />;
 }
 
+export function LazyRouteOutlet() {
+  const { t } = useLang();
+
+  return (
+    <Suspense
+      fallback={
+        <div
+          role="status"
+          aria-label={t('common.loading')}
+          style={{ minHeight: 240, display: 'grid', placeItems: 'center' }}
+        >
+          <Spin size="large" />
+        </div>
+      }
+    >
+      <Outlet />
+    </Suspense>
+  );
+}
+
 function App() {
   return (
     <Routes>
       <Route path="/login" element={<LoginPage />} />
       <Route element={<AuthGate />}>
         <Route path="/" element={<MainLayout />}>
-          <Route index element={<HomePage />} />
-          <Route path="instance" element={<InstancePage />} />
-          <Route path="instance/topic" element={<TopicPage />} />
-          <Route path="instance/consumer" element={<ConsumerPage />} />
-          <Route path="instance/message" element={<MessagePage />} />
-          <Route path="instance/acl" element={<AclPage />} />
-          <Route path="instance/dlq" element={<DlqPage />} />
-          <Route path="cluster" element={<ClusterPage />} />
-          <Route path="cluster/certs" element={<K8sCertsPage />} />
-          <Route path="cluster/clients" element={<ClientsPage />} />
-          <Route path="ops/dashboard" element={<DashboardOpsPage />} />
-          <Route path="ops/alerts" element={<AlertsPage />} />
-          <Route path="ops/system-alerts" element={<SystemAlertsPage />} />
-          <Route path="ops/audit" element={<AuditPage />} />
-          <Route path="ai" element={<AiPage />} />
-          <Route path="settings" element={<SettingsPage />} />
-          <Route path="studio/llm-settings" element={<LlmSettingsPage />} />
-          <Route path="studio/proxy" element={<ProxyPage />} />
-          <Route path="studio/lite-topic" element={<LiteTopicPage />} />
-          <Route path="studio/group-management" element={<GroupManagementPage 
/>} />
-          <Route path="studio/broker-cluster" element={<BrokerClusterPage />} 
/>
-          <Route path="studio/ssl-settings" element={<SslSettingsPage />} />
-          <Route path="studio/alert-management" element={<AlertManagementPage 
/>} />
-          <Route path="studio/producer" element={<ProducerPage />} />
-          <Route path="studio/ops" element={<OpsPage />} />
-          <Route path="*" element={<Navigate to="/" replace />} />
+          <Route element={<LazyRouteOutlet />}>
+            <Route index element={<HomePage />} />
+            <Route path="instance" element={<InstancePage />} />
+            <Route path="instance/topic" element={<TopicPage />} />
+            <Route path="instance/consumer" element={<ConsumerPage />} />
+            <Route path="instance/message" element={<MessagePage />} />
+            <Route path="instance/acl" element={<AclPage />} />
+            <Route path="instance/dlq" element={<DlqPage />} />
+            <Route path="cluster" element={<ClusterPage />} />
+            <Route path="cluster/certs" element={<K8sCertsPage />} />
+            <Route path="cluster/clients" element={<ClientsPage />} />
+            <Route path="ops/dashboard" element={<DashboardOpsPage />} />
+            <Route path="ops/alerts" element={<AlertsPage />} />
+            <Route path="ops/system-alerts" element={<SystemAlertsPage />} />
+            <Route path="ops/audit" element={<AuditPage />} />
+            <Route path="ai" element={<AiPage />} />
+            <Route path="settings" element={<SettingsPage />} />
+            <Route path="studio/llm-settings" element={<LlmSettingsPage />} />
+            <Route path="studio/proxy" element={<ProxyPage />} />
+            <Route path="studio/lite-topic" element={<LiteTopicPage />} />
+            <Route path="studio/group-management" 
element={<GroupManagementPage />} />
+            <Route path="studio/broker-cluster" element={<BrokerClusterPage 
/>} />
+            <Route path="studio/ssl-settings" element={<SslSettingsPage />} />
+            <Route path="studio/alert-management" 
element={<AlertManagementPage />} />
+            <Route path="studio/producer" element={<ProducerPage />} />
+            <Route path="studio/ops" element={<OpsPage />} />
+            <Route path="*" element={<Navigate to="/" replace />} />
+          </Route>
         </Route>
       </Route>
     </Routes>

Reply via email to