-
Notifications
You must be signed in to change notification settings - Fork 8.7k
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
perf: using-dynamic-imports #19204
perf: using-dynamic-imports #19204
Changes from 1 commit
572ac1a
cad7016
eef2041
c800a59
2627856
993b334
841781d
5f75929
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,29 +7,60 @@ import { | |
getSortedRowModel, | ||
createColumnHelper, | ||
} from "@tanstack/react-table"; | ||
import dynamic from "next/dynamic"; | ||
import { useMemo, useState } from "react"; | ||
import type { z } from "zod"; | ||
|
||
import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components"; | ||
import dayjs from "@calcom/dayjs"; | ||
import { FilterToggle } from "@calcom/features/bookings/components/FilterToggle"; | ||
import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer"; | ||
import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery"; | ||
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; | ||
import { DataTableProvider, DataTableWrapper } from "@calcom/features/data-table"; | ||
import { DataTableProvider } from "@calcom/features/data-table"; | ||
import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
import type { RouterOutputs } from "@calcom/trpc/react"; | ||
import { trpc } from "@calcom/trpc/react"; | ||
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui"; | ||
import { Alert, EmptyScreen, HorizontalTabs } from "@calcom/ui"; | ||
import { HorizontalTabs } from "@calcom/ui"; | ||
import { Icon } from "@calcom/ui"; | ||
|
||
import useMeQuery from "@lib/hooks/useMeQuery"; | ||
|
||
import BookingListItem from "@components/booking/BookingListItem"; | ||
import SkeletonLoader from "@components/booking/SkeletonLoader"; | ||
|
||
import type { validStatuses } from "~/bookings/lib/validStatuses"; | ||
|
||
const Alert = dynamic(() => import("@calcom/ui").then((mod) => mod.Alert)); | ||
|
||
const DataTableWrapper = dynamic( | ||
() => import("@calcom/features/data-table").then((mod) => mod.DataTableWrapper), | ||
{ | ||
ssr: false, | ||
loading: () => <SkeletonLoader />, | ||
} | ||
); | ||
|
||
Comment on lines
+33
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understood your concern , but there are cases when this won't be required like if the query fails, bookings are empty, or there are no upcoming bookings today. |
||
const BookingListItem = dynamic(() => import("@components/booking/BookingListItem"), { | ||
ssr: true, | ||
loading: () => <div className="h-20 animate-pulse rounded-md bg-gray-100" />, | ||
}); | ||
|
||
const EmptyScreen = dynamic(() => import("@calcom/ui").then((mod) => mod.EmptyScreen)); | ||
|
||
const WipeMyCalActionButton = dynamic( | ||
() => import("@calcom/app-store/wipemycalother/components").then((mod) => mod.WipeMyCalActionButton), | ||
{ | ||
ssr: false, | ||
loading: () => <Icon name="loader" className="animate-spin" />, | ||
} | ||
); | ||
const FiltersContainer = dynamic( | ||
() => import("@calcom/features/bookings/components/FiltersContainer").then((mod) => mod.FiltersContainer), | ||
{ | ||
ssr: false, | ||
loading: () => <Icon name="loader" className="animate-spin" />, | ||
} | ||
); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Screen.Recording.2025-02-10.at.2.12.20.PM.movWe should show a skeleton loader here instead of this loader There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's degrading the user experience by showing a loader when toggling filter components. It should already be there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Understood , but based on my analysis, dynamic import is the ideal approach for this scenario. Additionally, we are already dynamically importing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In case of the DateRangePicker, it's obvious to dynamically load it, because
It's at the 3rd step in the funnel. However, Can we get the specific number of bundle size different by dynamically importing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 290kB (transferred over network) and resource size is 1.3 MB There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Done |
||
type BookingListingStatus = z.infer<NonNullable<typeof filterQuerySchema>>["status"]; | ||
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0]; | ||
|
||
|
@@ -157,8 +188,7 @@ function BookingsContent({ status }: BookingsProps) { | |
]; | ||
}, [user, status]); | ||
|
||
const isEmpty = useMemo(() => !query.data?.pages[0]?.bookings.length, [query.data]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its computation is inexpensive and doesn't impact performance. |
||
|
||
const isEmpty = !query.data?.pages[0]?.bookings.length; | ||
const flatData = useMemo<RowData[]>(() => { | ||
const shownBookings: Record<string, BookingOutput[]> = {}; | ||
const filterBookings = (booking: BookingOutput) => { | ||
|
@@ -246,7 +276,7 @@ function BookingsContent({ status }: BookingsProps) { | |
<HorizontalTabs tabs={tabs} /> | ||
<FilterToggle setIsFiltersVisible={setIsFiltersVisible} /> | ||
</div> | ||
<FiltersContainer isFiltersVisible={isFiltersVisible} /> | ||
{isFiltersVisible && <FiltersContainer />} | ||
<main className="w-full"> | ||
<div className="flex w-full flex-col"> | ||
{query.status === "error" && ( | ||
|
@@ -255,7 +285,7 @@ function BookingsContent({ status }: BookingsProps) { | |
{(query.status === "pending" || query.isPaused) && <SkeletonLoader />} | ||
{query.status === "success" && !isEmpty && ( | ||
<> | ||
{!!bookingsToday.length && status === "upcoming" && ( | ||
{bookingsToday.length && status === "upcoming" && ( | ||
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} /> | ||
)} | ||
<DataTableWrapper | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Booking List Item is core part of /bookings. I don't think we should dynamically load this as this component would be required 99% of the times
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understood your concern , but there are cases when the user has no bookings or an error occurs, making this component unnecessary. Moreover, we’re dynamically importing it on the server, so it won’t impact client-side performance.
Also it's a heavy component , dynamic import will be beneficial
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too sure about this. As Udit said, it's 99% of the times required. Reducing the bundle size on the server side won't affect much.
In the end, we download the same size of the bundle on the client side (because we need them).
But with this approach, the client will have to make an asynchronous request to get these lazy components which is unnecessary separate requests with unncessary loaders.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood , but use cases where the component is required, the additional request overhead is negligible compared to the performance gains for the edge cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how this is a performance gain. DataTable and BookingListItem is an essential part of this page. Even if the size of page.js is reduced, we immediately load them in separate requests.
On the other hand, I think it's indeed a performance win for
WipeMyCalActionButton
because it's clearly "optionally" used after a certain amount of time of the page load.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your perspective, but the performance gain comes from deferring the loading of
DataTable and BookingListItem
(in times when no bookings , error , etc ) until they are actually needed, rather than including them in the initial page payload.While it's true that these components are eventually required and are loaded in separate requests, the initial page load benefits because :
This approach is particularly effective because these components aren’t required for every use case (e.g., when there are no bookings or an error occurs). By deferring their loading, we reduce unnecessary server load and improve the perceived load time for the end user.
Let me know if I am missing something