From 54166222f6a6f6e6feaf482b81f89bf732045ff3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 16 Oct 2024 11:52:23 +0200 Subject: [PATCH] SONAR-23205 Add lazy loading on most routes to improve build and dev server perfs --- .../src/main/js/app/utils/startReactApp.tsx | 32 +++++---- .../src/main/js/apps/audit-logs/routes.tsx | 4 +- .../main/js/apps/background-tasks/routes.tsx | 4 +- .../src/main/js/apps/code/routes.tsx | 4 +- .../src/main/js/apps/coding-rules/routes.tsx | 4 +- .../js/apps/component-measures/routes.tsx | 4 +- .../src/main/js/apps/dependencies/routes.tsx | 4 +- .../src/main/js/apps/groups/routes.tsx | 4 +- .../src/main/js/apps/issues/routes.tsx | 4 +- .../src/main/js/apps/marketplace/routes.tsx | 4 +- .../src/main/js/apps/overview/routes.tsx | 4 +- .../js/apps/permission-templates/routes.tsx | 6 +- .../src/main/js/apps/permissions/routes.tsx | 10 ++- .../main/js/apps/projectActivity/routes.tsx | 4 +- .../main/js/apps/projectBranches/routes.tsx | 4 +- .../main/js/apps/projectDeletion/routes.tsx | 28 ++++++++ .../src/main/js/apps/projectDump/routes.tsx | 4 +- .../js/apps/projectInformation/routes.tsx | 4 +- .../src/main/js/apps/projectKey/routes.tsx | 28 ++++++++ .../src/main/js/apps/projectLinks/routes.tsx | 28 ++++++++ .../main/js/apps/projectNewCode/routes.tsx | 6 +- .../js/apps/projectQualityGate/routes.tsx | 4 +- .../js/apps/projectQualityProfiles/routes.tsx | 4 +- .../src/main/js/apps/projects/routes.tsx | 10 ++- .../js/apps/projectsManagement/routes.tsx | 4 +- .../src/main/js/apps/quality-gates/routes.tsx | 4 +- .../main/js/apps/quality-profiles/routes.tsx | 14 ++-- .../main/js/apps/security-hotspots/routes.tsx | 28 ++++++++ .../src/main/js/apps/settings/routes.tsx | 6 +- .../src/main/js/apps/system/routes.tsx | 4 +- .../src/main/js/apps/tutorials/routes.tsx | 4 +- .../src/main/js/apps/users/routes.tsx | 4 +- .../src/main/js/apps/web-api-v2/routes.tsx | 4 +- .../src/main/js/apps/web-api/routes.tsx | 4 +- .../src/main/js/apps/webhooks/routes.tsx | 6 +- .../helpers/lazyLoadComponent.tsx | 71 +++++++++++++++++++ server/sonar-web/vite.config.mjs | 8 +-- .../resources/org/sonar/l10n/core.properties | 3 +- 38 files changed, 317 insertions(+), 59 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx create mode 100644 server/sonar-web/src/main/js/apps/projectKey/routes.tsx create mode 100644 server/sonar-web/src/main/js/apps/projectLinks/routes.tsx create mode 100644 server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx create mode 100644 server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index 605c7910ce1..d2031afbc0b 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -33,10 +33,10 @@ import { createBrowserRouter, createRoutesFromElements, } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; import accountRoutes from '../../apps/account/routes'; import auditLogsRoutes from '../../apps/audit-logs/routes'; import backgroundTasksRoutes from '../../apps/background-tasks/routes'; -import ChangeAdminPasswordApp from '../../apps/change-admin-password/ChangeAdminPasswordApp'; import codeRoutes from '../../apps/code/routes'; import codingRulesRoutes from '../../apps/coding-rules/routes'; import componentMeasuresRoutes from '../../apps/component-measures/routes'; @@ -50,11 +50,11 @@ import permissionTemplatesRoutes from '../../apps/permission-templates/routes'; import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes'; import projectActivityRoutes from '../../apps/projectActivity/routes'; import projectBranchesRoutes from '../../apps/projectBranches/routes'; -import ProjectDeletionApp from '../../apps/projectDeletion/App'; +import projectDeletionRoutes from '../../apps/projectDeletion/routes'; import projectDumpRoutes from '../../apps/projectDump/routes'; import projectInfoRoutes from '../../apps/projectInformation/routes'; -import ProjectKeyApp from '../../apps/projectKey/ProjectKeyApp'; -import ProjectLinksApp from '../../apps/projectLinks/ProjectLinksApp'; +import projectKeyRoutes from '../../apps/projectKey/routes'; +import projectLinksRoutes from '../../apps/projectLinks/routes'; import projectNewCodeDefinitionRoutes from '../../apps/projectNewCode/routes'; import projectQualityGateRoutes from '../../apps/projectQualityGate/routes'; import projectQualityProfilesRoutes from '../../apps/projectQualityProfiles/routes'; @@ -62,7 +62,7 @@ import projectsRoutes from '../../apps/projects/routes'; import projectsManagementRoutes from '../../apps/projectsManagement/routes'; import qualityGatesRoutes from '../../apps/quality-gates/routes'; import qualityProfilesRoutes from '../../apps/quality-profiles/routes'; -import SecurityHotspotsApp from '../../apps/security-hotspots/SecurityHotspotsApp'; +import securityHotspotsRoutes from '../../apps/security-hotspots/routes'; import sessionsRoutes from '../../apps/sessions/routes'; import settingsRoutes from '../../apps/settings/routes'; import systemRoutes from '../../apps/system/routes'; @@ -81,17 +81,13 @@ import AdminContainer from '../components/AdminContainer'; import App from '../components/App'; import ComponentContainer from '../components/ComponentContainer'; import DocumentationRedirect from '../components/DocumentationRedirect'; -import FormattingHelp from '../components/FormattingHelp'; import GlobalContainer from '../components/GlobalContainer'; import Landing from '../components/Landing'; import MigrationContainer from '../components/MigrationContainer'; import NonAdminPagesContainer from '../components/NonAdminPagesContainer'; import NotFound from '../components/NotFound'; -import PluginRiskConsent from '../components/PluginRiskConsent'; import ProjectAdminContainer from '../components/ProjectAdminContainer'; -import ResetPassword from '../components/ResetPassword'; import SimpleContainer from '../components/SimpleContainer'; -import SonarLintConnection from '../components/SonarLintConnection'; import { DEFAULT_APP_STATE } from '../components/app-state/AppStateContext'; import AppStateContextProvider from '../components/app-state/AppStateContextProvider'; import { @@ -123,8 +119,12 @@ function renderComponentRoutes() { element={} /> {projectIssuesRoutes()} +<<<<<<< HEAD {dependenciesRoutes()} } /> +======= + {securityHotspotsRoutes()} +>>>>>>> 6803c465323 (SONAR-23205 Add lazy loading on most routes to improve build and dev server perfs) {projectQualityGateRoutes()} {projectQualityProfilesRoutes()} {projectInfoRoutes()} @@ -144,9 +144,9 @@ function renderComponentRoutes() { {settingsRoutes()} {webhooksRoutes()} - } /> - } /> - } /> + {projectDeletionRoutes()} + {projectLinksRoutes()} + {projectKeyRoutes()} {projectPermissionsRoutes()} @@ -185,6 +185,14 @@ function renderRedirects() { ); } +const FormattingHelp = lazyLoadComponent(() => import('../components/FormattingHelp')); +const SonarLintConnection = lazyLoadComponent(() => import('../components/SonarLintConnection')); +const ResetPassword = lazyLoadComponent(() => import('../components/ResetPassword')); +const ChangeAdminPasswordApp = lazyLoadComponent( + () => import('../../apps/change-admin-password/ChangeAdminPasswordApp'), +); +const PluginRiskConsent = lazyLoadComponent(() => import('../components/PluginRiskConsent')); + const router = createBrowserRouter( createRoutesFromElements( <> diff --git a/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx b/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx index 0eb7f96e57d..7178d9ff58d 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx @@ -19,8 +19,10 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import AuditApp from './components/AuditApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; const routes = () => } />; +const AuditApp = lazyLoadComponent(() => import('./components/AuditApp')); + export default routes; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx b/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx index d31a23bf1fc..f50eb2fb0f9 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import BackgroundTasksApp from './components/BackgroundTasksApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const BackgroundTasksApp = lazyLoadComponent(() => import('./components/BackgroundTasksApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/code/routes.tsx b/server/sonar-web/src/main/js/apps/code/routes.tsx index 7710f63cdd3..11c029616c3 100644 --- a/server/sonar-web/src/main/js/apps/code/routes.tsx +++ b/server/sonar-web/src/main/js/apps/code/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import CodeApp from './components/CodeApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const CodeApp = lazyLoadComponent(() => import('./components/CodeApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx index 8ac74338daa..67a18a55a23 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx @@ -19,10 +19,12 @@ */ import React, { useEffect } from 'react'; import { Route, useLocation, useNavigate } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; import { RawQuery } from '~sonar-aligned/types/router'; -import CodingRulesApp from './components/CodingRulesApp'; import { parseQuery, serializeQuery } from './query'; +const CodingRulesApp = lazyLoadComponent(() => import('./components/CodingRulesApp')); + const EXPECTED_SPLIT_PARTS = 2; function parseHash(hash: string): RawQuery { diff --git a/server/sonar-web/src/main/js/apps/component-measures/routes.tsx b/server/sonar-web/src/main/js/apps/component-measures/routes.tsx index a547a34802e..debc43d2744 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/routes.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/routes.tsx @@ -19,10 +19,12 @@ */ import React from 'react'; import { Navigate, Route, useParams, useSearchParams } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; import { searchParamsToQuery } from '~sonar-aligned/helpers/router'; import NavigateWithParams from '../../app/utils/NavigateWithParams'; import { omitNil } from '../../helpers/request'; -import ComponentMeasuresApp from './components/ComponentMeasuresApp'; + +const ComponentMeasuresApp = lazyLoadComponent(() => import('./components/ComponentMeasuresApp')); const routes = () => ( diff --git a/server/sonar-web/src/main/js/apps/dependencies/routes.tsx b/server/sonar-web/src/main/js/apps/dependencies/routes.tsx index 27525c61107..0c9ab45fb5c 100644 --- a/server/sonar-web/src/main/js/apps/dependencies/routes.tsx +++ b/server/sonar-web/src/main/js/apps/dependencies/routes.tsx @@ -20,7 +20,9 @@ import React from 'react'; import { Route } from 'react-router-dom'; -import DependenciesApp from './DependenciesApp'; +import { lazyLoadComponent } from '../../sonar-aligned/helpers/lazyLoadComponent'; + +const DependenciesApp = lazyLoadComponent(() => import('./DependenciesApp')); export const dependenciesRoutes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/groups/routes.tsx b/server/sonar-web/src/main/js/apps/groups/routes.tsx index db9738ea9d3..7f46c245ae3 100644 --- a/server/sonar-web/src/main/js/apps/groups/routes.tsx +++ b/server/sonar-web/src/main/js/apps/groups/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import GroupsApp from './GroupsApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const GroupsApp = lazyLoadComponent(() => import('./GroupsApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/issues/routes.tsx b/server/sonar-web/src/main/js/apps/issues/routes.tsx index 7f30e41a0db..48cbe65591f 100644 --- a/server/sonar-web/src/main/js/apps/issues/routes.tsx +++ b/server/sonar-web/src/main/js/apps/issues/routes.tsx @@ -19,9 +19,11 @@ */ import React, { useEffect } from 'react'; import { Route, useNavigate, useSearchParams } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; import { omitNil } from '../../helpers/request'; import { IssueType } from '../../types/issues'; -import IssuesApp from './components/IssuesApp'; + +const IssuesApp = lazyLoadComponent(() => import('./components/IssuesApp')); export const globalIssuesRoutes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/marketplace/routes.tsx b/server/sonar-web/src/main/js/apps/marketplace/routes.tsx index 39fa3ef5239..8dedc452273 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/routes.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import MarketplaceAppContainer from './MarketplaceAppContainer'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const MarketplaceAppContainer = lazyLoadComponent(() => import('./MarketplaceAppContainer')); export const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/overview/routes.tsx b/server/sonar-web/src/main/js/apps/overview/routes.tsx index a0434016ce6..1a2249ef833 100644 --- a/server/sonar-web/src/main/js/apps/overview/routes.tsx +++ b/server/sonar-web/src/main/js/apps/overview/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import App from './components/App'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const App = lazyLoadComponent(() => import('./components/App')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx index 7bd12ec84eb..9dbdef25593 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx @@ -19,7 +19,11 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import PermissionTemplatesApp from './components/PermissionTemplatesApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const PermissionTemplatesApp = lazyLoadComponent( + () => import('./components/PermissionTemplatesApp'), +); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/permissions/routes.tsx b/server/sonar-web/src/main/js/apps/permissions/routes.tsx index 0a46773795e..c2fa8129d22 100644 --- a/server/sonar-web/src/main/js/apps/permissions/routes.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/routes.tsx @@ -19,8 +19,14 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import GlobalPermissionsApp from './global/components/PermissionsGlobalApp'; -import PermissionsProjectApp from './project/components/PermissionsProjectApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const GlobalPermissionsApp = lazyLoadComponent( + () => import('./global/components/PermissionsGlobalApp'), +); +const PermissionsProjectApp = lazyLoadComponent( + () => import('./project/components/PermissionsProjectApp'), +); export const globalPermissionsRoutes = () => ( } /> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx b/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx index 3bd17ae6488..31cbc16ae8c 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectActivityApp from './components/ProjectActivityApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectActivityApp = lazyLoadComponent(() => import('./components/ProjectActivityApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx b/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx index 0e278472115..5efbbd4a633 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectBranchesApp from './ProjectBranchesApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectBranchesApp = lazyLoadComponent(() => import('./ProjectBranchesApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx new file mode 100644 index 00000000000..d6974af0f41 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const App = lazyLoadComponent(() => import('./App')); + +const routes = () => } />; + +export default routes; diff --git a/server/sonar-web/src/main/js/apps/projectDump/routes.tsx b/server/sonar-web/src/main/js/apps/projectDump/routes.tsx index 1cb748338c7..b757c6c7f93 100644 --- a/server/sonar-web/src/main/js/apps/projectDump/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectDump/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectDumpApp from './ProjectDumpApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectDumpApp = lazyLoadComponent(() => import('./ProjectDumpApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx b/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx index 903beaf840e..61f6295b5b1 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectInformationApp from './ProjectInformationApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectInformationApp = lazyLoadComponent(() => import('./ProjectInformationApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/projectKey/routes.tsx b/server/sonar-web/src/main/js/apps/projectKey/routes.tsx new file mode 100644 index 00000000000..592c0751106 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectKey/routes.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectKeyApp = lazyLoadComponent(() => import('./ProjectKeyApp')); + +const routes = () => } />; + +export default routes; diff --git a/server/sonar-web/src/main/js/apps/projectLinks/routes.tsx b/server/sonar-web/src/main/js/apps/projectLinks/routes.tsx new file mode 100644 index 00000000000..d7718698ade --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectLinks/routes.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectLinksApp = lazyLoadComponent(() => import('./ProjectLinksApp')); + +const routes = () => } />; + +export default routes; diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx index 9ab18067b63..75666bd89fc 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx @@ -19,7 +19,11 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectNewCodeDefinitionApp from './components/ProjectNewCodeDefinitionApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectNewCodeDefinitionApp = lazyLoadComponent( + () => import('./components/ProjectNewCodeDefinitionApp'), +); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx index 67db45e1f78..dbe8257b94f 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectQualityGateApp from './ProjectQualityGateApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectQualityGateApp = lazyLoadComponent(() => import('./ProjectQualityGateApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx index 11b8f414d2c..8acac8ae60b 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectQualityProfilesApp from './ProjectQualityProfilesApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectQualityProfilesApp = lazyLoadComponent(() => import('./ProjectQualityProfilesApp')); const routes = () => ( } /> diff --git a/server/sonar-web/src/main/js/apps/projects/routes.tsx b/server/sonar-web/src/main/js/apps/projects/routes.tsx index 62a2cb6ceba..cf062a6bc9e 100644 --- a/server/sonar-web/src/main/js/apps/projects/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projects/routes.tsx @@ -19,12 +19,16 @@ */ import React from 'react'; import { Navigate, Route } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; import { save } from '../../helpers/storage'; -import CreateProjectPage from '../create/project/CreateProjectPage'; -import DefaultPageSelector from './components/DefaultPageSelector'; -import FavoriteProjectsContainer from './components/FavoriteProjectsContainer'; import { PROJECTS_ALL, PROJECTS_DEFAULT_FILTER } from './utils'; +const DefaultPageSelector = lazyLoadComponent(() => import('./components/DefaultPageSelector')); +const FavoriteProjectsContainer = lazyLoadComponent( + () => import('./components/FavoriteProjectsContainer'), +); +const CreateProjectPage = lazyLoadComponent(() => import('../create/project/CreateProjectPage')); + function PersistNavigate() { save(PROJECTS_DEFAULT_FILTER, PROJECTS_ALL); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx index 5e766881a53..0a3c7a85cea 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ProjectManagementApp from './ProjectManagementApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const ProjectManagementApp = lazyLoadComponent(() => import('./ProjectManagementApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx b/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx index c3cf1a8f8fd..4765280f5fb 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import App from './components/App'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const App = lazyLoadComponent(() => import('./components/App')); const routes = () => ( diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx index a6182ec8110..7909d2d3b96 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx @@ -19,12 +19,14 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import ChangelogContainer from './changelog/ChangelogContainer'; -import ComparisonContainer from './compare/ComparisonContainer'; -import ProfileContainer from './components/ProfileContainer'; -import QualityProfilesApp from './components/QualityProfilesApp'; -import ProfileDetails from './details/ProfileDetails'; -import HomeContainer from './home/HomeContainer'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const QualityProfilesApp = lazyLoadComponent(() => import('./components/QualityProfilesApp')); +const HomeContainer = lazyLoadComponent(() => import('./home/HomeContainer')); +const ProfileContainer = lazyLoadComponent(() => import('./components/ProfileContainer')); +const ProfileDetails = lazyLoadComponent(() => import('./details/ProfileDetails')); +const ChangelogContainer = lazyLoadComponent(() => import('./changelog/ChangelogContainer')); +const ComparisonContainer = lazyLoadComponent(() => import('./compare/ComparisonContainer')); export enum QualityProfilePath { SHOW = 'show', diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx new file mode 100644 index 00000000000..7d75b302b05 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const SecurityHotspotsApp = lazyLoadComponent(() => import('./SecurityHotspotsApp')); + +const routes = () => } />; + +export default routes; diff --git a/server/sonar-web/src/main/js/apps/settings/routes.tsx b/server/sonar-web/src/main/js/apps/settings/routes.tsx index 6e01fcb5b8d..a2ce4ea9874 100644 --- a/server/sonar-web/src/main/js/apps/settings/routes.tsx +++ b/server/sonar-web/src/main/js/apps/settings/routes.tsx @@ -19,8 +19,10 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import SettingsApp from './components/SettingsApp'; -import EncryptionApp from './encryption/EncryptionApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const SettingsApp = lazyLoadComponent(() => import('./components/SettingsApp')); +const EncryptionApp = lazyLoadComponent(() => import('./encryption/EncryptionApp')); const routes = () => ( diff --git a/server/sonar-web/src/main/js/apps/system/routes.tsx b/server/sonar-web/src/main/js/apps/system/routes.tsx index 55351850983..cf8319590a6 100644 --- a/server/sonar-web/src/main/js/apps/system/routes.tsx +++ b/server/sonar-web/src/main/js/apps/system/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import SystemApp from './components/SystemApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const SystemApp = lazyLoadComponent(() => import('./components/SystemApp')); export const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/tutorials/routes.tsx b/server/sonar-web/src/main/js/apps/tutorials/routes.tsx index 3556deb36d8..d08cadabfc4 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/routes.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import TutorialsApp from './components/TutorialsApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const TutorialsApp = lazyLoadComponent(() => import('./components/TutorialsApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/users/routes.tsx b/server/sonar-web/src/main/js/apps/users/routes.tsx index 337bec0bf33..3920d1ca079 100644 --- a/server/sonar-web/src/main/js/apps/users/routes.tsx +++ b/server/sonar-web/src/main/js/apps/users/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import UsersApp from './UsersApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const UsersApp = lazyLoadComponent(() => import('./UsersApp')); export const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx b/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx index 36df5337c4c..e85d40f8c42 100644 --- a/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx +++ b/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import WebApiApp from './WebApiApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const WebApiApp = lazyLoadComponent(() => import('./WebApiApp')); const routes = () => } />; diff --git a/server/sonar-web/src/main/js/apps/web-api/routes.tsx b/server/sonar-web/src/main/js/apps/web-api/routes.tsx index 3468f7ce9d9..6f2537898c0 100644 --- a/server/sonar-web/src/main/js/apps/web-api/routes.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/routes.tsx @@ -19,7 +19,9 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import WebApiApp from './components/WebApiApp'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; + +const WebApiApp = lazyLoadComponent(() => import('./components/WebApiApp')); const routes = () => ( diff --git a/server/sonar-web/src/main/js/apps/webhooks/routes.tsx b/server/sonar-web/src/main/js/apps/webhooks/routes.tsx index 1aaab6eb14a..ce4b06474ad 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/routes.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/routes.tsx @@ -19,8 +19,10 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import App from './components/App'; +import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent'; -export const routes = () => } />; +const WebhooksApp = lazyLoadComponent(() => import('./components/App')); + +export const routes = () => } />; export default routes; diff --git a/server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx b/server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx new file mode 100644 index 00000000000..0d47d9a8b89 --- /dev/null +++ b/server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { FlagMessage } from 'design-system'; +import React, { Component, lazy, Suspense } from 'react'; +import { translate } from '../../helpers/l10n'; +import { requestTryAndRepeatUntil } from '../../helpers/request'; + +export function lazyLoadComponent>( + factory: () => Promise<{ default: T }>, + displayName?: string, +) { + const LazyComponent = lazy(() => + requestTryAndRepeatUntil(factory, { max: 2, slowThreshold: 2 }, () => true), + ); + + function LazyComponentWrapper(props: React.ComponentProps) { + return ( + + + + + + ); + } + + LazyComponentWrapper.displayName = displayName; + return LazyComponentWrapper; +} + +interface ErrorBoundaryProps { + children: React.ReactNode; +} + +interface ErrorBoundaryState { + hasError: boolean; +} + +export class LazyErrorBoundary extends Component { + state: ErrorBoundaryState = { hasError: false }; + + static getDerivedStateFromError() { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + render() { + if (this.state.hasError) { + return ( + {translate('default_component_error_message')} + ); + } + return this.props.children; + } +} diff --git a/server/sonar-web/vite.config.mjs b/server/sonar-web/vite.config.mjs index ca11da7afa6..b0759c5f7f1 100644 --- a/server/sonar-web/vite.config.mjs +++ b/server/sonar-web/vite.config.mjs @@ -85,13 +85,9 @@ export default ({ mode }) => { manualChunks: { // vendor js chunk will contain only react dependencies vendor: ['react', 'react-router-dom', 'react-dom'], + echoes: ['@sonarsource/echoes-react'], + datefns: ['date-fns'], lodash: ['lodash/lodash.js'], - highlightjs: [ - 'highlight.js', - 'highlightjs-apex', - 'highlightjs-cobol', - 'highlightjs-sap-abap', - ], }, }, plugins: [], diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index c6d7a3a53ed..9e9d817c5dc 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -307,6 +307,7 @@ copy_to_clipboard=Click to copy to clipboard copied_action=Copied to clipboard created_by=Created by default_error_message=The request cannot be processed. Try again later. +default_component_error_message=The component cannot be loaded. Try again later. default_save_field_error_message=This field cannot be saved. Try again later. default_severity=Default severity edit_permissions=Edit Permissions @@ -2894,7 +2895,7 @@ email_notification.test.message_text=This is a test message from SonarQube. email_notification.test.create_test_email=Create test email email_notification.test.submit=Send test email email_notification.test.success=Your email was sent successfully -email_notification.test.failure=Your email could not be sent. Ensure your authentication configuration settings and email recipient are valid. +email_notification.test.failure=Your email could not be sent. Ensure your authentication configuration settings and email recipient are valid. email_notification.state.value_should_be_valid_email=A valid email address is required. #------------------------------------------------------------------------------ -- 2.39.5