From eb1181cd7061abbbe7ace4b551a48a6c8ce3a60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 16 Apr 2019 21:45:57 +0100 Subject: [PATCH] feat(ui): lazy load modals This uses React code splitting to only load modal content components if they are used - all modal components will be loaded the first time user open a modal, rather than on initial page load --- ui/src/Components/MainModal/index.js | 29 +++++++++++++++----- ui/src/Components/MainModal/index.test.js | 12 +++++++- ui/src/Components/SilenceModal/index.js | 29 +++++++++++++++----- ui/src/Components/SilenceModal/index.test.js | 12 +++++++- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/ui/src/Components/MainModal/index.js b/ui/src/Components/MainModal/index.js index 66948d4ed..bba66f8c2 100644 --- a/ui/src/Components/MainModal/index.js +++ b/ui/src/Components/MainModal/index.js @@ -6,12 +6,19 @@ import { observable, action } from "mobx"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCog } from "@fortawesome/free-solid-svg-icons/faCog"; +import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner"; import { AlertStore } from "Stores/AlertStore"; import { Settings } from "Stores/Settings"; import { TooltipWrapper } from "Components/TooltipWrapper"; import { Modal } from "Components/Modal"; -import { MainModalContent } from "./MainModalContent"; + +// https://github.com/facebook/react/issues/14603 +const MainModalContent = React.lazy(() => + import("./MainModalContent").then(module => ({ + default: module.MainModalContent + })) +); const MainModal = observer( class MainModal extends Component { @@ -49,12 +56,20 @@ const MainModal = observer( - + + + + } + > + + ); diff --git a/ui/src/Components/MainModal/index.test.js b/ui/src/Components/MainModal/index.test.js index daed8ce4c..83f3e7da7 100644 --- a/ui/src/Components/MainModal/index.test.js +++ b/ui/src/Components/MainModal/index.test.js @@ -31,12 +31,22 @@ describe("", () => { expect(tree.find("MainModalContent")).toHaveLength(0); }); - it("renders the modal when it is shown", () => { + it("renders a spinner placeholder while modal content is loading", () => { + const tree = MountedMainModal(); + const toggle = tree.find(".nav-link"); + toggle.simulate("click"); + expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0); + expect(tree.find("MainModalContent")).toHaveLength(0); + expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1); + }); + + it("renders modal content if fallback is not used", () => { const tree = MountedMainModal(); const toggle = tree.find(".nav-link"); toggle.simulate("click"); expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0); expect(tree.find("MainModalContent")).toHaveLength(1); + expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(0); }); it("hides the modal when toggle() is called twice", () => { diff --git a/ui/src/Components/SilenceModal/index.js b/ui/src/Components/SilenceModal/index.js index 400a261d2..52e0ea155 100644 --- a/ui/src/Components/SilenceModal/index.js +++ b/ui/src/Components/SilenceModal/index.js @@ -5,16 +5,23 @@ import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash"; +import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { Settings } from "Stores/Settings"; import { Modal } from "Components/Modal"; import { TooltipWrapper } from "Components/TooltipWrapper"; -import { SilenceModalContent } from "./SilenceModalContent"; import "./index.css"; +// https://github.com/facebook/react/issues/14603 +const SilenceModalContent = React.lazy(() => + import("./SilenceModalContent").then(module => ({ + default: module.SilenceModalContent + })) +); + const SilenceModal = observer( class SilenceModal extends Component { static propTypes = { @@ -42,12 +49,20 @@ const SilenceModal = observer( isOpen={silenceFormStore.toggle.visible} onExited={silenceFormStore.data.resetProgress} > - + + + + } + > + + ); diff --git a/ui/src/Components/SilenceModal/index.test.js b/ui/src/Components/SilenceModal/index.test.js index 0954eb3e0..8e39ad585 100644 --- a/ui/src/Components/SilenceModal/index.test.js +++ b/ui/src/Components/SilenceModal/index.test.js @@ -39,12 +39,22 @@ describe("", () => { expect(tree.find("SilenceModalContent")).toHaveLength(0); }); - it("renders the modal when it is shown", () => { + it("renders a spinner placeholder while modal content is loading", () => { + const tree = MountedSilenceModal(); + const toggle = tree.find(".nav-link"); + toggle.simulate("click"); + expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0); + expect(tree.find("SilenceModalContent")).toHaveLength(0); + expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1); + }); + + it("renders modal content if fallback is not used", () => { const tree = MountedSilenceModal(); const toggle = tree.find(".nav-link"); toggle.simulate("click"); expect(tree.find("FontAwesomeIcon")).not.toHaveLength(0); expect(tree.find("SilenceModalContent")).toHaveLength(1); + expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(0); }); it("hides the modal when toggle() is called twice", () => {