-
Notifications
You must be signed in to change notification settings - Fork 4.5k
chore: decoupled svg imports from main bundle #36662
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
Changes from all commits
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 |
|---|---|---|
| @@ -1,9 +1,13 @@ | ||
| // See readme.md for why this file exists. | ||
|
|
||
| import React, { useMemo } from "react"; | ||
| import React, { | ||
| useEffect, | ||
| useState, | ||
| type ComponentType, | ||
| type SVGProps, | ||
| } from "react"; | ||
| import classNames from "classnames"; | ||
| import type { IconProps } from "@blueprintjs/core"; | ||
| import svgImportsMap from "components/designSystems/blueprintjs/icon/svgImportsMap"; | ||
| // Below symbols must be imported directly from target files to avoid crashes | ||
| // caused by cyclic dependencies in @blueprintjs/core. | ||
| import { | ||
|
|
@@ -12,12 +16,34 @@ import { | |
| intentClass, | ||
| } from "@blueprintjs/core/lib/esm/common/classes"; | ||
| import { importSvg } from "@appsmith/ads-old"; | ||
| import type { IconName } from "@blueprintjs/core"; | ||
|
|
||
| // This export must be named "IconSize" to match the exports of @blueprintjs/core/lib/esm/components/icon | ||
| export enum IconSize { | ||
| STANDARD = 16, | ||
| LARGE = 20, | ||
| } | ||
| type IconMapType = Record< | ||
| IconName, | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-imports | ||
| Record<IconSize, () => Promise<typeof import("*.svg")>> | ||
| >; | ||
|
|
||
| // Create a variable to cache the imported module | ||
| let cachedSvgImportsMap: IconMapType | null = null; | ||
|
|
||
| // Function to lazily load the file once | ||
| const loadSvgImportsMapOnce = async () => { | ||
| if (!cachedSvgImportsMap) { | ||
| const { default: svgImportsMap } = await import( | ||
| "components/designSystems/blueprintjs/icon/svgImportsMap" | ||
| ); | ||
|
|
||
| cachedSvgImportsMap = svgImportsMap; // Cache the module for future use | ||
| } | ||
|
|
||
| return cachedSvgImportsMap; | ||
| }; | ||
|
|
||
| function Icon(props: IconProps) { | ||
| const { | ||
|
|
@@ -31,18 +57,32 @@ function Icon(props: IconProps) { | |
| tagName: TagName = "span", | ||
| ...htmlprops | ||
| } = props; | ||
| const [SvgIcon, setSvgIcon] = useState<ComponentType< | ||
| SVGProps<SVGSVGElement> | ||
| > | null>(null); | ||
|
|
||
| // choose which pixel grid is most appropriate for given icon size | ||
| const pixelGridSize = | ||
| size >= IconSize.LARGE ? IconSize.LARGE : IconSize.STANDARD; | ||
|
|
||
| // render the icon, or nothing if icon name is unknown. | ||
| const SvgIcon = useMemo(() => { | ||
| if (typeof icon === "string" && icon in svgImportsMap) { | ||
| return importSvg(svgImportsMap[icon][pixelGridSize]); | ||
| } | ||
| useEffect(() => { | ||
| const loadScript = async () => { | ||
| // Load the cached svgImportsMap once | ||
| const svgImportsMap = await loadSvgImportsMapOnce(); | ||
|
|
||
| if (typeof icon === "string" && icon in svgImportsMap) { | ||
| const SvgIcon = await importSvg(svgImportsMap[icon][pixelGridSize]); | ||
|
|
||
| setSvgIcon(() => SvgIcon); // Set the component as lazy-loaded | ||
| } | ||
| }; | ||
|
|
||
| loadScript(); | ||
|
|
||
| return () => null; | ||
| // Cleanup on unmount | ||
| return () => { | ||
| setSvgIcon(null); | ||
| }; | ||
|
Comment on lines
+60
to
+85
Contributor
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. Time for a pop quiz on asynchronous operations! Now, class, let's look at how we're loading our SVG icons. We're using some fancy React hooks called useState and useEffect. Can anyone tell me what these do? Very good! useState helps us remember things, and useEffect helps us do things at the right time. But wait a minute... I see something missing. Can anyone spot it? That's right, we're not handling errors! What if something goes wrong when we're trying to load our icons? Let's improve this, shall we? Here's a little homework for you: useEffect(() => {
const loadScript = async () => {
try {
const svgImportsMap = await loadSvgImportsMapOnce();
if (typeof icon === "string" && icon in svgImportsMap) {
const SvgIcon = await importSvg(svgImportsMap[icon][pixelGridSize]);
setSvgIcon(() => SvgIcon);
}
} catch (error) {
console.error("Error loading SVG icon:", error);
setSvgIcon(null);
}
};
loadScript();
return () => {
setSvgIcon(null);
};
}, [icon, pixelGridSize]);Can you see how we've added a try-catch block? This is like a safety net for our code. If anything goes wrong, we'll know about it and our program won't crash. Always remember to handle your errors, class! |
||
| }, [icon, pixelGridSize]); | ||
|
|
||
| if (icon == null || typeof icon === "boolean") { | ||
|
|
@@ -68,13 +108,15 @@ function Icon(props: IconProps) { | |
| icon={icon} | ||
| title={htmlTitle} | ||
| > | ||
| <SvgIcon | ||
| data-icon={icon} | ||
| fill={color} | ||
| height={size} | ||
| viewBox={viewBox} | ||
| width={size} | ||
| /> | ||
| {SvgIcon && ( | ||
| <SvgIcon | ||
| data-icon={icon} | ||
| fill={color} | ||
| height={size} | ||
| viewBox={viewBox} | ||
| width={size} | ||
| /> | ||
| )} | ||
| </TagName> | ||
| ); | ||
| } | ||
|
|
||
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.
🛠️ Refactor suggestion
Let's enhance error handling in asynchronous operations
When dealing with asynchronous functions like
loadScript, it's important to anticipate potential errors that might occur during the loading process. To make our code more robust, consider adding a try-catch block to handle any exceptions gracefully.Here's how you might modify the
loadScriptfunction:useEffect(() => { const loadScript = async () => { + try { // Load the cached svgImportsMap once const svgImportsMap = await loadSvgImportsMapOnce(); if (typeof icon === "string" && icon in svgImportsMap) { const SvgIcon = await importSvg(svgImportsMap[icon][pixelGridSize]); setSvgIcon(() => SvgIcon); // Set the component as lazy-loaded } + } catch (error) { + console.error("Error loading SVG icon:", error); + // You might set a default icon or handle the error state here + setSvgIcon(null); + } }; loadScript(); // Cleanup on unmount return () => { setSvgIcon(null); }; }, [icon, pixelGridSize]);By adding error handling, we ensure that our application remains stable even if an icon fails to load. This will enhance the user experience and prevent unforeseen issues.
📝 Committable suggestion