From bc165cf8274aff6ea32b8de33b82abd184cbaf2c Mon Sep 17 00:00:00 2001 From: Vemparala Surya Vamsi <121419957+vsvamsi1@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:32:30 +0530 Subject: [PATCH] chore: decouple editor components (#37102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Decouple editor components and codemirror from the main chunk, the main chunk has dropped by 200Kb to 1.1Mb Fixes #`Issue Number` _or_ Fixes `Issue URL` > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /ok-to-test tags="@tag.All" ### :mag: Cypress test results > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: 4a5b3bcb8b56062c8c7feb711d7776bbae51bcbb > Cypress dashboard. > Tags: `@tag.All` > Spec: >
Wed, 30 Oct 2024 13:57:44 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No ## Summary by CodeRabbit - **New Features** - Implemented lazy loading for several components, enhancing performance and user experience during loading states. - Introduced new error handling UI with `EntityNotFoundPane` for better feedback when data is unavailable. - Added `LazilyLoadedChecklist` for optimized loading of the checklist component in onboarding. - **Bug Fixes** - Improved asynchronous handling in test cases for more reliable rendering assertions. - **Documentation** - Updated export statements for components to default exports for consistency. - **Chores** - Refactored routing logic to utilize lazy-loaded components across various sections of the application. --- app/client/cypress/support/Pages/JSEditor.ts | 1 + .../IDE/EditorPane/JS/{hooks.ts => hooks.tsx} | 87 +++++++--- .../Editor/IDE/EditorPane/Query/hooks.tsx | 149 ++++++++++++------ .../MainPane/{useRoutes.ts => useRoutes.tsx} | 26 ++- .../Editor/FirstTimeUserOnboarding/Modal.tsx | 24 ++- .../IDE/EditorPane/JS/JSRender.test.tsx | 25 ++- .../IDE/EditorPane/Query/QueryRender.test.tsx | 35 +++- .../pages/Editor/JSEditor/JSBlankState.tsx | 2 +- .../src/pages/Editor/JSEditor/index.tsx | 1 - .../Editor/QueryEditor/QueriesBlankState.tsx | 2 +- 10 files changed, 259 insertions(+), 93 deletions(-) rename app/client/src/ce/pages/Editor/IDE/EditorPane/JS/{hooks.ts => hooks.tsx} (67%) rename app/client/src/ce/pages/Editor/IDE/MainPane/{useRoutes.ts => useRoutes.tsx} (85%) diff --git a/app/client/cypress/support/Pages/JSEditor.ts b/app/client/cypress/support/Pages/JSEditor.ts index a8ed3d4fbac..832c83048d9 100644 --- a/app/client/cypress/support/Pages/JSEditor.ts +++ b/app/client/cypress/support/Pages/JSEditor.ts @@ -113,6 +113,7 @@ export class JSEditor { ); //Checking JS object was created successfully this.assertHelper.AssertNetworkStatus("@createNewJSCollection", 201); + cy.get(this._jsObjName).click({ force: true }); this.agHelper.AssertElementVisibility(this._jsObjTxt); // Assert that the name of the JS Object is focused when newly created this.agHelper.PressEnter(); diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/hooks.ts b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/hooks.tsx similarity index 67% rename from app/client/src/ce/pages/Editor/IDE/EditorPane/JS/hooks.ts rename to app/client/src/ce/pages/Editor/IDE/EditorPane/JS/hooks.tsx index 58614a67abe..34ce88dd556 100644 --- a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/hooks.ts +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/hooks.tsx @@ -1,4 +1,5 @@ -import { useCallback } from "react"; +import { lazy, Suspense, useCallback, useMemo } from "react"; +import React from "react"; import { useDispatch, useSelector } from "react-redux"; import { createNewJSCollection } from "actions/jsPaneActions"; import { getCurrentPageId } from "selectors/editorSelectors"; @@ -7,17 +8,16 @@ import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages"; import { JsFileIconV2 } from "pages/Editor/Explorer/ExplorerIcons"; import { SEARCH_ITEM_TYPES } from "components/editorComponents/GlobalSearch/utils"; import type { UseRoutes } from "ee/entities/IDE/constants"; -import JSEditor from "pages/Editor/JSEditor"; -import AddJS from "pages/Editor/IDE/EditorPane/JS/Add"; import { ADD_PATH } from "ee/constants/routes/appRoutes"; import history from "utils/history"; import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity"; import { useModuleOptions } from "ee/utils/moduleInstanceHelpers"; import { getJSUrl } from "ee/pages/Editor/IDE/EditorPane/JS/utils"; -import { JSBlankState } from "pages/Editor/JSEditor/JSBlankState"; import { getIDEViewMode } from "selectors/ideSelectors"; import { EditorViewMode } from "ee/entities/IDE/constants"; import { setListViewActiveState } from "actions/ideActions"; +import { retryPromise } from "utils/AppsmithUtils"; +import Skeleton from "widgets/Skeleton"; export const useJSAdd = () => { const pageId = useSelector(getCurrentPageId); @@ -93,25 +93,64 @@ export const useGroupedAddJsOperations = (): GroupedAddOperations => { ]; }; +const AddJS = lazy(async () => + retryPromise( + async () => + import( + /* webpackChunkName: "AddJS" */ "pages/Editor/IDE/EditorPane/JS/Add" + ), + ), +); +const JSEditor = lazy(async () => + retryPromise( + async () => + import(/* webpackChunkName: "JSEditor" */ "pages/Editor/JSEditor"), + ), +); + +const JSEmpty = lazy(async () => + retryPromise( + async () => + import( + /* webpackChunkName: "JSEmpty" */ "pages/Editor/JSEditor/JSBlankState" + ), + ), +); + export const useJSEditorRoutes = (path: string): UseRoutes => { - return [ - { - exact: true, - key: "AddJS", - component: AddJS, - path: [`${path}${ADD_PATH}`, `${path}/:baseCollectionId${ADD_PATH}`], - }, - { - exact: true, - key: "JSEditor", - component: JSEditor, - path: [path + "/:baseCollectionId"], - }, - { - key: "JSEmpty", - component: JSBlankState, - exact: true, - path: [path], - }, - ]; + return useMemo( + () => [ + { + exact: true, + key: "AddJS", + component: (args) => ( + }> + + + ), + path: [`${path}${ADD_PATH}`, `${path}/:baseCollectionId${ADD_PATH}`], + }, + { + exact: true, + key: "JSEditor", + component: (args) => ( + }> + + + ), + path: [path + "/:baseCollectionId"], + }, + { + key: "JSEmpty", + component: (args) => ( + }> + + + ), + exact: true, + path: [path], + }, + ], + [path], + ); }; diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx index 73ffb6ab8a7..7eeba362e2e 100644 --- a/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/Query/hooks.tsx @@ -1,4 +1,5 @@ -import { useCallback, useMemo } from "react"; +import { lazy, Suspense, useCallback, useMemo } from "react"; +import React from "react"; import history from "utils/history"; import { useLocation } from "react-router"; import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity"; @@ -23,19 +24,17 @@ import { BUILDER_PATH_DEPRECATED, } from "ee/constants/routes/appRoutes"; import { SAAS_EDITOR_API_ID_PATH } from "pages/Editor/SaaSEditor/constants"; -import ApiEditor from "pages/Editor/APIEditor"; import type { UseRoutes } from "ee/entities/IDE/constants"; -import QueryEditor from "pages/Editor/QueryEditor"; -import AddQuery from "pages/Editor/IDE/EditorPane/Query/Add"; import type { AppState } from "ee/reducers"; import keyBy from "lodash/keyBy"; import { getPluginEntityIcon } from "pages/Editor/Explorer/ExplorerIcons"; import type { ListItemProps } from "@appsmith/ads"; import { createAddClassName } from "pages/Editor/IDE/EditorPane/utils"; -import { QueriesBlankState } from "pages/Editor/QueryEditor/QueriesBlankState"; import { getIDEViewMode } from "selectors/ideSelectors"; import { EditorViewMode } from "ee/entities/IDE/constants"; import { setListViewActiveState } from "actions/ideActions"; +import { retryPromise } from "utils/AppsmithUtils"; +import Skeleton from "widgets/Skeleton"; export const useQueryAdd = () => { const location = useLocation(); @@ -114,47 +113,107 @@ export const useGroupedAddQueryOperations = (): GroupedAddOperations => { return groups; }; +const ApiEditor = lazy(async () => + retryPromise( + async () => + import(/* webpackChunkName: "APIEditor" */ "pages/Editor/APIEditor"), + ), +); + +const AddQuery = lazy(async () => + retryPromise( + async () => + import( + /* webpackChunkName: "AddQuery" */ "pages/Editor/IDE/EditorPane/Query/Add" + ), + ), +); +const QueryEditor = lazy(async () => + retryPromise( + async () => + import(/* webpackChunkName: "QueryEditor" */ "pages/Editor/QueryEditor"), + ), +); + +const QueryEmpty = lazy(async () => + retryPromise( + async () => + import( + /* webpackChunkName: "QueryEmpty" */ "pages/Editor/QueryEditor/QueriesBlankState" + ), + ), +); + export const useQueryEditorRoutes = (path: string): UseRoutes => { - return [ - { - key: "ApiEditor", - component: ApiEditor, - exact: true, - path: [ - BUILDER_PATH + API_EDITOR_ID_PATH, - BUILDER_CUSTOM_PATH + API_EDITOR_ID_PATH, - BUILDER_PATH_DEPRECATED + API_EDITOR_ID_PATH, - ], - }, - { - key: "AddQuery", - exact: true, - component: AddQuery, - path: [`${path}${ADD_PATH}`, `${path}/:baseQueryId${ADD_PATH}`], - }, - { - key: "SAASEditor", - component: QueryEditor, - exact: true, - path: [ - BUILDER_PATH + SAAS_EDITOR_API_ID_PATH, - BUILDER_CUSTOM_PATH + SAAS_EDITOR_API_ID_PATH, - BUILDER_PATH_DEPRECATED + SAAS_EDITOR_API_ID_PATH, - ], - }, - { - key: "QueryEditor", - component: QueryEditor, - exact: true, - path: [path + "/:baseQueryId"], - }, - { - key: "QueryEmpty", - component: QueriesBlankState, - exact: true, - path: [path], - }, - ]; + return useMemo( + () => [ + { + key: "ApiEditor", + component: (args) => { + return ( + }> + + + ); + }, + exact: true, + path: [ + BUILDER_PATH + API_EDITOR_ID_PATH, + BUILDER_CUSTOM_PATH + API_EDITOR_ID_PATH, + BUILDER_PATH_DEPRECATED + API_EDITOR_ID_PATH, + ], + }, + { + key: "AddQuery", + exact: true, + component: (args) => ( + }> + + + ), + path: [`${path}${ADD_PATH}`, `${path}/:baseQueryId${ADD_PATH}`], + }, + { + key: "SAASEditor", + component: (args) => { + return ( + }> + + + ); + }, + exact: true, + path: [ + BUILDER_PATH + SAAS_EDITOR_API_ID_PATH, + BUILDER_CUSTOM_PATH + SAAS_EDITOR_API_ID_PATH, + BUILDER_PATH_DEPRECATED + SAAS_EDITOR_API_ID_PATH, + ], + }, + { + key: "QueryEditor", + component: (args) => { + return ( + }> + + + ); + }, + exact: true, + path: [path + "/:baseQueryId"], + }, + { + key: "QueryEmpty", + component: (args) => ( + }> + + + ), + exact: true, + path: [path], + }, + ], + [path], + ); }; export const useAddQueryListItems = () => { diff --git a/app/client/src/ce/pages/Editor/IDE/MainPane/useRoutes.ts b/app/client/src/ce/pages/Editor/IDE/MainPane/useRoutes.tsx similarity index 85% rename from app/client/src/ce/pages/Editor/IDE/MainPane/useRoutes.ts rename to app/client/src/ce/pages/Editor/IDE/MainPane/useRoutes.tsx index c49f522c1c3..d0fdc4ecda0 100644 --- a/app/client/src/ce/pages/Editor/IDE/MainPane/useRoutes.ts +++ b/app/client/src/ce/pages/Editor/IDE/MainPane/useRoutes.tsx @@ -24,7 +24,6 @@ import { WIDGETS_EDITOR_ID_PATH, } from "constants/routes"; import CreateNewDatasourceTab from "pages/Editor/IntegrationEditor/CreateNewDatasourceTab"; -import OnboardingChecklist from "pages/Editor/FirstTimeUserOnboarding/Checklist"; import { SAAS_EDITOR_API_ID_ADD_PATH, SAAS_EDITOR_API_ID_PATH, @@ -38,7 +37,28 @@ import GeneratePage from "pages/Editor/GeneratePage"; import type { RouteProps } from "react-router"; import { useSelector } from "react-redux"; import { combinedPreviewModeSelector } from "selectors/editorSelectors"; +import { lazy, Suspense } from "react"; +import React from "react"; +import { retryPromise } from "utils/AppsmithUtils"; +import Skeleton from "widgets/Skeleton"; + +const FirstTimeUserOnboardingChecklist = lazy(async () => + retryPromise( + async () => + import( + /* webpackChunkName: "FirstTimeUserOnboardingChecklist" */ "pages/Editor/FirstTimeUserOnboarding/Checklist" + ), + ), +); + +export const LazilyLoadedFirstTimeUserOnboardingChecklist = () => { + return ( + }> + + + ); +}; export interface RouteReturnType extends RouteProps { key: string; } @@ -95,7 +115,9 @@ function useRoutes(path: string): RouteReturnType[] { }, { key: "OnboardingChecklist", - component: isPreviewMode ? WidgetsEditor : OnboardingChecklist, + component: isPreviewMode + ? WidgetsEditor + : FirstTimeUserOnboardingChecklist, exact: true, path: `${path}${BUILDER_CHECKLIST_PATH}`, }, diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Modal.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Modal.tsx index a0a70f11d60..6cd288820d1 100644 --- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Modal.tsx +++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Modal.tsx @@ -1,11 +1,29 @@ -import React from "react"; +import React, { lazy, Suspense } from "react"; import { MenuContent } from "@appsmith/ads"; import styled from "styled-components"; -import Checklist from "./Checklist"; import HelpMenu from "./HelpMenu"; import { useDispatch } from "react-redux"; import { showSignpostingModal } from "actions/onboardingActions"; +import { retryPromise } from "utils/AppsmithUtils"; +import Skeleton from "widgets/Skeleton"; + +const Checklist = lazy(async () => + retryPromise( + async () => + import( + /* webpackChunkName: "FirstTimeUserOnboardingChecklist" */ "./Checklist" + ), + ), +); + +export const LazilyLoadedChecklist = () => { + return ( + }> + + + ); +}; const SIGNPOSTING_POPUP_WIDTH = "360px"; const StyledMenuContent = styled(MenuContent)<{ animate: boolean }>` @@ -48,7 +66,7 @@ function OnboardingModal(props: { width={SIGNPOSTING_POPUP_WIDTH} > - {!props.showIntercomConsent && } + {!props.showIntercomConsent && } { localStorage.setItem("SPLITPANE_ANNOUNCEMENT", "false"); describe("JS Blank State", () => { - it("Renders Fullscreen Blank State", () => { - const { getByRole, getByText } = render( + it("Renders Fullscreen Blank State", async () => { + const { findByText, getByRole, getByText } = render( , @@ -31,7 +31,7 @@ describe("IDE Render: JS", () => { ); // Main pane text - getByText(createMessage(EDITOR_PANE_TEXTS.js_blank_state)); + await findByText(createMessage(EDITOR_PANE_TEXTS.js_blank_state)); // Left pane text getByText(createMessage(EDITOR_PANE_TEXTS.js_blank_state_description)); @@ -68,8 +68,8 @@ describe("IDE Render: JS", () => { }); }); - it("Renders Fullscreen Add in Blank State", () => { - const { getByTestId, getByText } = render( + it("Renders Fullscreen Add in Blank State", async () => { + const { findByText, getByTestId, getByText } = render( , @@ -80,7 +80,7 @@ describe("IDE Render: JS", () => { ); // Main pane text - getByText(createMessage(EDITOR_PANE_TEXTS.js_create_tab_title)); + await findByText(createMessage(EDITOR_PANE_TEXTS.js_create_tab_title)); // Left pane description getByText(createMessage(EDITOR_PANE_TEXTS.js_blank_state_description)); @@ -117,7 +117,7 @@ describe("IDE Render: JS", () => { }); describe("JS Edit Render", () => { - it("Renders JS routes in Full screen", () => { + it("Renders JS routes in Full screen", async () => { const page = PageFactory.build(); const js1 = JSObjectFactory.build({ pageId: page.pageId, @@ -143,6 +143,15 @@ describe("IDE Render: JS", () => { }, ); + await waitFor( + async () => { + const elements = getAllByText("JSObject1"); // Use the common test ID or selector + + expect(elements).toHaveLength(3); // Wait until there are exactly 3 elements + }, + { timeout: 3000, interval: 500 }, + ); + // There will be 3 JSObject1 text (Left pane list, editor tab and Editor form) expect(getAllByText("JSObject1").length).toEqual(3); // Left pane active state diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/QueryRender.test.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/QueryRender.test.tsx index 3bc3dca089b..65314ae7a72 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/QueryRender.test.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/QueryRender.test.tsx @@ -12,7 +12,7 @@ import { sagasToRunForTests } from "test/sagas"; import userEvent from "@testing-library/user-event"; import { getIDETestState } from "test/factories/AppIDEFactoryUtils"; import { PageFactory } from "test/factories/PageFactory"; -import { screen } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { GoogleSheetFactory } from "test/factories/Actions/GoogleSheetFactory"; const FeatureFlags = { @@ -24,8 +24,8 @@ const basePageId = "0123456789abcdef00000000"; describe("IDE URL rendering of Queries", () => { localStorage.setItem("SPLITPANE_ANNOUNCEMENT", "false"); describe("Query Blank State", () => { - it("Renders Fullscreen Blank State", () => { - const { getByRole, getByText } = render( + it("Renders Fullscreen Blank State", async () => { + const { findByText, getByRole, getByText } = render( , @@ -36,7 +36,7 @@ describe("IDE URL rendering of Queries", () => { ); // Main pane text - getByText(createMessage(EDITOR_PANE_TEXTS.query_blank_state)); + await findByText(createMessage(EDITOR_PANE_TEXTS.query_blank_state)); // Left pane text getByText(createMessage(EDITOR_PANE_TEXTS.query_blank_state_description)); @@ -69,8 +69,8 @@ describe("IDE URL rendering of Queries", () => { getByText(/new query \/ api/i); }); - it("Renders Fullscreen Add in Blank State", () => { - const { getByTestId, getByText } = render( + it("Renders Fullscreen Add in Blank State", async () => { + const { findByText, getByTestId, getByText } = render( , @@ -81,7 +81,9 @@ describe("IDE URL rendering of Queries", () => { ); // Create options are rendered - getByText(createMessage(EDITOR_PANE_TEXTS.queries_create_from_existing)); + await findByText( + createMessage(EDITOR_PANE_TEXTS.queries_create_from_existing), + ); getByText("New datasource"); getByText("REST API"); // Check new tab presence @@ -130,7 +132,7 @@ describe("IDE URL rendering of Queries", () => { }); describe("API Routes", () => { - it("Renders Api routes in Full screen", () => { + it("Renders Api routes in Full screen", async () => { const page = PageFactory.build(); const anApi = APIFactory.build({ pageId: page.pageId }); const state = getIDETestState({ @@ -153,6 +155,15 @@ describe("IDE URL rendering of Queries", () => { }, ); + await waitFor( + async () => { + const elements = getAllByText("Api1"); // Use the common test ID or selector + + expect(elements).toHaveLength(3); // Wait until there are exactly 3 elements + }, + { timeout: 3000, interval: 500 }, + ); + // There will be 3 Api1 text (Left pane list, editor tab and Editor form) expect(getAllByText("Api1").length).toEqual(3); // Left pane active state @@ -343,6 +354,14 @@ describe("IDE URL rendering of Queries", () => { }, ); + await waitFor( + async () => { + const elements = getAllByText("Query1"); // Use the common test ID or selector + + expect(elements).toHaveLength(3); // Wait until there are exactly 3 elements + }, + { timeout: 3000, interval: 500 }, + ); // There will be 3 Query1 text (Left pane list, editor tab and Editor form) expect(getAllByText("Query1").length).toBe(3); // Left pane active state diff --git a/app/client/src/pages/Editor/JSEditor/JSBlankState.tsx b/app/client/src/pages/Editor/JSEditor/JSBlankState.tsx index acf141ca0be..303f41b1893 100644 --- a/app/client/src/pages/Editor/JSEditor/JSBlankState.tsx +++ b/app/client/src/pages/Editor/JSEditor/JSBlankState.tsx @@ -26,4 +26,4 @@ const JSBlankState = () => { ); }; -export { JSBlankState }; +export default JSBlankState; diff --git a/app/client/src/pages/Editor/JSEditor/index.tsx b/app/client/src/pages/Editor/JSEditor/index.tsx index e625d505701..6fcd8956119 100644 --- a/app/client/src/pages/Editor/JSEditor/index.tsx +++ b/app/client/src/pages/Editor/JSEditor/index.tsx @@ -15,7 +15,6 @@ import AppJSEditorContextMenu from "./AppJSEditorContextMenu"; import { updateFunctionProperty } from "actions/jsPaneActions"; import type { OnUpdateSettingsProps } from "./JSEditorToolbar"; import { saveJSObjectName } from "actions/jsActionActions"; - const LoadingContainer = styled(CenteredWrapper)` height: 50%; `; diff --git a/app/client/src/pages/Editor/QueryEditor/QueriesBlankState.tsx b/app/client/src/pages/Editor/QueryEditor/QueriesBlankState.tsx index 2b6aae11ce0..0f23cd2c075 100644 --- a/app/client/src/pages/Editor/QueryEditor/QueriesBlankState.tsx +++ b/app/client/src/pages/Editor/QueryEditor/QueriesBlankState.tsx @@ -26,4 +26,4 @@ const QueriesBlankState = () => { ); }; -export { QueriesBlankState }; +export default QueriesBlankState;