Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Startup LL Desktop #5072

Merged
merged 8 commits into from Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/empty-ligers-unite.md
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": minor
---

Improve startup time with lazy loading + remove json files from renderer bundle
139 changes: 90 additions & 49 deletions apps/ledger-live-desktop/src/renderer/Default.tsx
@@ -1,27 +1,13 @@
import React, { useEffect } from "react";
import React, { useEffect, lazy, Suspense } from "react";
import styled from "styled-components";
import { ipcRenderer } from "electron";
import { Redirect, Route, Switch, useHistory } from "react-router-dom";
import { useSelector } from "react-redux";
import { FeatureToggle } from "@ledgerhq/live-common/featureFlags/index";
import TrackAppStart from "~/renderer/components/TrackAppStart";
import { PlatformCatalog, LiveApp } from "~/renderer/screens/platform";
import { BridgeSyncProvider } from "~/renderer/bridge/BridgeSyncContext";
import { SyncNewAccounts } from "~/renderer/bridge/SyncNewAccounts";
import Dashboard from "~/renderer/screens/dashboard";
import Settings from "~/renderer/screens/settings";
import Accounts from "~/renderer/screens/accounts";
import Card from "~/renderer/screens/card";
import Manager from "~/renderer/screens/manager";
import Exchange from "~/renderer/screens/exchange";
import Earn from "./screens/earn";
import SwapWeb from "./screens/swapWeb";
import Swap2 from "~/renderer/screens/exchange/Swap2";
import USBTroubleshooting from "~/renderer/screens/USBTroubleshooting";
import Account from "~/renderer/screens/account";
import Asset from "~/renderer/screens/asset";
import { PlatformCatalog, LiveApp } from "~/renderer/screens/platform";
import NFTGallery from "~/renderer/screens/nft/Gallery";
import NFTCollection from "~/renderer/screens/nft/Gallery/Collection";
import Box from "~/renderer/components/Box/Box";
import { useListenToHidDevices } from "./hooks/useListenToHidDevices";
import ExportLogsButton from "~/renderer/components/ExportLogsButton";
Expand Down Expand Up @@ -52,16 +38,7 @@ import Drawer from "~/renderer/drawers/Drawer";
import UpdateBanner from "~/renderer/components/Updater/Banner";
import FirmwareUpdateBanner from "~/renderer/components/FirmwareUpdateBanner";
import VaultSignerBanner from "~/renderer/components/VaultSignerBanner";
import RecoverRestore from "~/renderer/components/RecoverRestore";
import Onboarding from "~/renderer/components/Onboarding";
import PostOnboardingScreen from "~/renderer/components/PostOnboardingScreen";
import { hasCompletedOnboardingSelector } from "~/renderer/reducers/settings";
import Market from "~/renderer/screens/market";
import MarketCoinScreen from "~/renderer/screens/market/MarketCoinScreen";
import Learn from "~/renderer/screens/learn";
import WelcomeScreenSettings from "~/renderer/screens/settings/WelcomeScreenSettings";
import SyncOnboarding from "./components/SyncOnboarding";
import RecoverPlayer from "~/renderer/screens/recover/Player";
import { updateIdentify } from "./analytics/segment";
import { useDiscoverDB } from "./screens/platform/v2/hooks";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
Expand All @@ -70,9 +47,64 @@ import {
useFetchCurrencyAll,
useFetchCurrencyFrom,
} from "@ledgerhq/live-common/exchange/swap/hooks/index";
import { Flex, InfiniteLoader } from "@ledgerhq/react-ui";
import useAccountsWithFundsListener from "@ledgerhq/live-common/hooks/useAccountsWithFundsListener";
import { accountsSelector } from "./reducers/accounts";

const Dashboard = lazy(() => import("~/renderer/screens/dashboard"));
const Settings = lazy(() => import("~/renderer/screens/settings"));
const Accounts = lazy(() => import("~/renderer/screens/accounts"));
const Card = lazy(() => import("~/renderer/screens/card"));
const Manager = lazy(() => import("~/renderer/screens/manager"));
const Exchange = lazy(() => import("~/renderer/screens/exchange"));
const Earn = lazy(() => import("~/renderer/screens/earn"));
const SwapWeb = lazy(() => import("~/renderer/screens/swapWeb"));
const Swap2 = lazy(() => import("~/renderer/screens/exchange/Swap2"));

const Market = lazy(() => import("~/renderer/screens/market"));
const MarketCoinScreen = lazy(() => import("~/renderer/screens/market/MarketCoinScreen"));
const Learn = lazy(() => import("~/renderer/screens/learn"));
const WelcomeScreenSettings = lazy(
() => import("~/renderer/screens/settings/WelcomeScreenSettings"),
);
const SyncOnboarding = lazy(() => import("./components/SyncOnboarding"));
const RecoverPlayer = lazy(() => import("~/renderer/screens/recover/Player"));

const NFTGallery = lazy(() => import("~/renderer/screens/nft/Gallery"));
const NFTCollection = lazy(() => import("~/renderer/screens/nft/Gallery/Collection"));
const RecoverRestore = lazy(() => import("~/renderer/components/RecoverRestore"));
const Onboarding = lazy(() => import("~/renderer/components/Onboarding"));
const PostOnboardingScreen = lazy(() => import("~/renderer/components/PostOnboardingScreen"));
const USBTroubleshooting = lazy(() => import("~/renderer/screens/USBTroubleshooting"));
const Asset = lazy(() => import("~/renderer/screens/asset"));
const Account = lazy(() => import("~/renderer/screens/account"));

const LoaderWrapper = styled.div`
padding: 24px;
align-self: center;
display: flex;
align-items: center;
justify-content: center;
margin: auto;
`;

const Fallback = () => (
<LoaderWrapper>
<Flex alignItems="center" justifyContent="center" borderRadius={9999} size={60} mb={5}>
<InfiniteLoader size={58} />
</Flex>
</LoaderWrapper>
);

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line react/display-name
const withSuspense = Component => props => (
<Suspense fallback={<Fallback />}>
<Component {...props} />
</Suspense>
);

// in order to test sentry integration, we need the ability to test it out.
const LetThisCrashForCrashTest = () => {
throw new Error("CrashTestRendering");
Expand Down Expand Up @@ -197,28 +229,34 @@ export default function Default() {
path="/onboarding"
render={() => (
<>
<Onboarding />
<Suspense fallback={<Fallback />}>
<Onboarding />
</Suspense>
<Drawer />
</>
)}
/>
<Route path="/sync-onboarding" component={SyncOnboarding} />
<Route path="/sync-onboarding" render={withSuspense(SyncOnboarding)} />
<Route
path="/post-onboarding"
render={() => (
<>
<PostOnboardingScreen />
<Suspense fallback={<Fallback />}>
<PostOnboardingScreen />
</Suspense>
<Drawer />
</>
)}
/>
<Route path="/recover-restore" component={RecoverRestore} />
<Route path="/recover-restore" render={withSuspense(RecoverRestore)} />

<Route path="/USBTroubleshooting">
<USBTroubleshooting onboarding={!hasCompletedOnboarding} />
<Suspense fallback={<Fallback />}>
<USBTroubleshooting onboarding={!hasCompletedOnboarding} />
</Suspense>
</Route>
{!hasCompletedOnboarding ? (
<Route path="/settings" component={WelcomeScreenSettings} />
<Route path="/settings" render={withSuspense(WelcomeScreenSettings)} />
) : (
<Route>
<Switch>
Expand All @@ -240,7 +278,7 @@ export default function Default() {
>
<FeatureToggle featureId="protectServicesDesktop">
<Switch>
<Route path="/recover/:appId" component={RecoverPlayer} />
<Route path="/recover/:appId" render={withSuspense(RecoverPlayer)} />
</Switch>
</FeatureToggle>
<MainSideBar />
Expand All @@ -251,38 +289,41 @@ export default function Default() {
<VaultSignerBanner />
</TopBannerContainer>
<Switch>
<Route path="/" exact component={Dashboard} />
<Route path="/settings" component={Settings} />
<Route path="/accounts" component={Accounts} />
<Route path="/card" component={Card} />
<Route path="/" exact render={withSuspense(Dashboard)} />
<Route path="/settings" render={withSuspense(Settings)} />
<Route path="/accounts" render={withSuspense(Accounts)} />
<Route path="/card" render={withSuspense(Card)} />
<Redirect from="/manager/reload" to="/manager" />
<Route path="/manager" component={Manager} />
<Route path="/manager" render={withSuspense(Manager)} />
<Route
path="/platform"
component={() => <PlatformCatalog db={discoverDB} />}
exact
/>
<Route path="/platform/:appId?" component={LiveApp} />
<Route path="/earn" component={Earn} />
<Route exact path="/exchange/:appId?" component={Exchange} />
<Route path="/earn" render={withSuspense(Earn)} />
<Route exact path="/exchange/:appId?" render={withSuspense(Exchange)} />
<Route
exact
path="/account/:id/nft-collection"
component={NFTGallery}
render={withSuspense(NFTGallery)}
/>
<Route path="/swap-web" component={SwapWeb} />
<Route path="/swap-web" render={withSuspense(SwapWeb)} />
<Route
path="/account/:id/nft-collection/:collectionAddress?"
component={NFTCollection}
render={withSuspense(NFTCollection)}
/>
<Route path="/account/:parentId/:id" render={withSuspense(Account)} />
<Route path="/account/:id" render={withSuspense(Account)} />
<Route path="/asset/:assetId+" render={withSuspense(Asset)} />
<Route path="/swap" render={withSuspense(Swap2)} />
<Route
path="/market/:currencyId"
render={withSuspense(MarketCoinScreen)}
/>
<Route path="/account/:parentId/:id" component={Account} />
<Route path="/account/:id" component={Account} />
<Route path="/asset/:assetId+" component={Asset} />
<Route path="/swap" component={Swap2} />
<Route path="/market/:currencyId" component={MarketCoinScreen} />
<Route path="/market" component={Market} />
<Route path="/market" render={withSuspense(Market)} />
<FeatureToggle featureId="learn">
<Route path="/learn" component={Learn} />
<Route path="/learn" render={withSuspense(Learn)} />
</FeatureToggle>
</Switch>
</Page>
Expand Down
Expand Up @@ -45,8 +45,8 @@ export const useListenToHidDevices = () => {
const timeoutSyncDevices = setTimeout(syncDevices, 1000);

return () => {
clearTimeout(timeoutSyncDevices);
sub.unsubscribe();
clearTimeout?.(timeoutSyncDevices);
sub?.unsubscribe?.();
};
}, [dispatch]);

Expand Down
12 changes: 7 additions & 5 deletions apps/ledger-live-desktop/src/renderer/init.tsx
Expand Up @@ -8,7 +8,10 @@ import { checkLibs } from "@ledgerhq/live-common/sanityChecks";
import { importPostOnboardingState } from "@ledgerhq/live-common/postOnboarding/actions";
import i18n from "i18next";
import { webFrame, ipcRenderer } from "electron";
import { createRoot } from "react-dom/client";
// We can't use new createRoot for now. We have issues we react-redux 7.x and lazy load of components
// https://github.com/reduxjs/react-redux/issues/1977
// eslint-disable-next-line react/no-deprecated
import { render } from "react-dom";
import moment from "moment";
import each from "lodash/each";
import { reload, getKey, loadLSS } from "~/renderer/storage";
Expand Down Expand Up @@ -47,7 +50,7 @@ import { Device } from "@ledgerhq/live-common/hw/actions/types";
import { listCachedCurrencyIds } from "./bridge/cache";
import { LogEntry } from "winston";

const domNode = document.getElementById("react-root");
const rootNode = document.getElementById("react-root");
const TAB_KEY = 9;

async function init() {
Expand Down Expand Up @@ -246,9 +249,8 @@ async function init() {
};
}
function r(Comp: JSX.Element) {
if (domNode) {
const rootNode = createRoot(domNode);
rootNode.render(Comp);
if (rootNode) {
render(Comp, rootNode);
}
}
init()
Expand Down
Expand Up @@ -54,6 +54,8 @@ type RenderActionParams = {
disabled?: boolean;
tooltip?: string;
accountActionsTestId?: string;
contrastText: string;
currency: TokenCurrency | CryptoCurrency;
};

const ButtonSettings = styled(Tabbable).attrs<{ disabled?: boolean }>(() => ({
Expand Down Expand Up @@ -94,6 +96,36 @@ type Props = {
openModal: Function;
} & OwnProps;

const ActionItem = ({
label,
onClick,
event,
eventProperties,
icon,
disabled,
tooltip,
accountActionsTestId,
contrastText,
currency,
}: RenderActionParams) => {
const Icon = icon;
const Action = (
<ActionDefault
disabled={disabled}
onClick={onClick}
event={event}
eventProperties={eventProperties}
iconComponent={Icon && <Icon size={14} overrideColor={contrastText} currency={currency} />}
labelComponent={label}
accountActionsTestId={accountActionsTestId}
/>
);
if (tooltip) {
return <Tooltip content={tooltip}>{Action}</Tooltip>;
}
return Action;
};

const AccountHeaderSettingsButtonComponent = ({ account, parentAccount, openModal, t }: Props) => {
const mainAccount = getMainAccount(account, parentAccount);
const currency = getAccountCurrency(account);
Expand Down Expand Up @@ -246,37 +278,11 @@ const AccountHeaderActions = ({ account, parentAccount, openModal }: Props) => {
});
}, [openModal, parentAccount, account, buttonSharedTrackingFields]);

const renderAction = ({
label,
onClick,
event,
eventProperties,
icon,
disabled,
tooltip,
accountActionsTestId,
}: RenderActionParams) => {
const Icon = icon;
const Action = (
<ActionDefault
disabled={disabled}
onClick={onClick}
event={event}
eventProperties={eventProperties}
iconComponent={Icon && <Icon size={14} overrideColor={contrastText} currency={currency} />}
labelComponent={label}
accountActionsTestId={accountActionsTestId}
/>
);
if (tooltip) {
return <Tooltip content={tooltip}>{Action}</Tooltip>;
}
return Action;
};

const manageActions: RenderActionParams[] = [
...manageList.map(item => ({
...item,
contrastText,
currency,
eventProperties: {
...buttonSharedTrackingFields,
...item.eventProperties,
Expand All @@ -287,7 +293,9 @@ const AccountHeaderActions = ({ account, parentAccount, openModal }: Props) => {
const buyHeader = <BuyActionDefault onClick={() => onBuySell("buy")} />;
const sellHeader = <SellActionDefault onClick={() => onBuySell("sell")} />;
const swapHeader = <SwapActionDefault onClick={onSwap} />;
const manageActionsHeader = manageActions.map(item => renderAction(item));
const manageActionsHeader = manageActions.map(item => (
<ActionItem {...item} key={item.accountActionsTestId} />
));

const NonEmptyAccountHeader = (
<FadeInButtonsContainer data-test-id="account-buttons-group" show={showButtons}>
Expand Down
4 changes: 3 additions & 1 deletion apps/ledger-live-desktop/tests/models/AccountPage.ts
Expand Up @@ -51,7 +51,9 @@ export class AccountPage {
}

async scrollToOperations() {
await this.page.locator("id=operation-list").scrollIntoViewIfNeeded();
const operationList = this.page.locator("id=operation-list");
await operationList.waitFor();
await operationList.scrollIntoViewIfNeeded({ timeout: 1000 });
}

async startCosmosStakingFlow() {
Expand Down
1 change: 1 addition & 0 deletions apps/ledger-live-desktop/tests/models/AddAccountModal.ts
Expand Up @@ -36,6 +36,7 @@ export class AddAccountModal extends Modal {
}

async getFirstAccountName() {
await this.page.waitForTimeout(500);
const firstAccountName = await this.accountsList.locator("input").first().inputValue();
return firstAccountName;
}
Expand Down
4 changes: 4 additions & 0 deletions apps/ledger-live-desktop/tests/models/LiveAppWebview.ts
Expand Up @@ -112,6 +112,10 @@ export class LiveAppWebview {
return waitFor(() => this.textIsPresent(textToCheck));
}

async waitForLoaded() {
return this.page.waitForLoadState("domcontentloaded");
}

async textIsPresent(textToCheck: string) {
const result: boolean = await this.page.evaluate(textToCheck => {
const webview = document.querySelector("webview");
Expand Down