diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index e2e74a9cb..c66e7cf25 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -2,26 +2,48 @@ import { Spinner, SpinnerSize, TooltipHost } from "@fluentui/react"; import { CollectionTabKind } from "Contracts/ViewModels"; import Explorer from "Explorer/Explorer"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; -import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab"; -import { FabricHomeScreen } from "Explorer/SplashScreen/FabricHome"; import { SplashScreen } from "Explorer/SplashScreen/SplashScreen"; import { ConnectTab } from "Explorer/Tabs/ConnectTab"; -import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab"; import { QuickstartTab } from "Explorer/Tabs/QuickstartTab"; -import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab"; -import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab"; import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { userContext } from "UserContext"; import { useTeachingBubble } from "hooks/useTeachingBubble"; import ko from "knockout"; -import React, { MutableRefObject, useEffect, useRef, useState } from "react"; +import React, { MutableRefObject, Suspense, useEffect, useRef, useState } from "react"; import errorQuery from "../../../images/error_no_outline.svg"; import warningIconSvg from "../../../images/warning.svg"; import { useObservable } from "../../hooks/useObservable"; import { ReactTabKind, useTabs } from "../../hooks/useTabs"; import TabsBase from "./TabsBase"; +// Lazy-loaded tab components (code-split by API type) +const QueryCopilotTab = React.lazy(() => + import(/* webpackChunkName: "QueryCopilotTab" */ "Explorer/QueryCopilot/QueryCopilotTab").then((m) => ({ + default: m.QueryCopilotTab, + })), +); +const FabricHomeScreen = React.lazy(() => + import(/* webpackChunkName: "FabricHomeScreen" */ "Explorer/SplashScreen/FabricHome").then((m) => ({ + default: m.FabricHomeScreen, + })), +); +const PostgresConnectTab = React.lazy(() => + import(/* webpackChunkName: "PostgresConnectTab" */ "Explorer/Tabs/PostgresConnectTab").then((m) => ({ + default: m.PostgresConnectTab, + })), +); +const VcoreMongoConnectTab = React.lazy(() => + import(/* webpackChunkName: "VcoreMongoConnectTab" */ "Explorer/Tabs/VCoreMongoConnectTab").then((m) => ({ + default: m.VcoreMongoConnectTab, + })), +); +const VcoreMongoQuickstartTab = React.lazy(() => + import(/* webpackChunkName: "VcoreMongoQuickstartTab" */ "Explorer/Tabs/VCoreMongoQuickstartTab").then((m) => ({ + default: m.VcoreMongoQuickstartTab, + })), +); + type Tab = TabsBase | (TabsBase & { render: () => JSX.Element }); interface TabsProps { @@ -302,30 +324,33 @@ const isQueryErrorThrown = (tab?: Tab, tabKind?: ReactTabKind): boolean => { }; const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => { - switch (activeReactTab) { - case ReactTabKind.Connect: - return userContext.apiType === "VCoreMongo" ? ( - - ) : userContext.apiType === "Postgres" ? ( - - ) : ( - - ); - case ReactTabKind.Home: - if (isFabricNative()) { - return ; - } else { - return ; - } - case ReactTabKind.Quickstart: - return userContext.apiType === "VCoreMongo" ? ( - - ) : ( - - ); - case ReactTabKind.QueryCopilot: - return ; - default: - throw new Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`); - } + const content = (() => { + switch (activeReactTab) { + case ReactTabKind.Connect: + return userContext.apiType === "VCoreMongo" ? ( + + ) : userContext.apiType === "Postgres" ? ( + + ) : ( + + ); + case ReactTabKind.Home: + if (isFabricNative()) { + return ; + } else { + return ; + } + case ReactTabKind.Quickstart: + return userContext.apiType === "VCoreMongo" ? ( + + ) : ( + + ); + case ReactTabKind.QueryCopilot: + return ; + default: + throw new Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`); + } + })(); + return }>{content}; }; diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index f5308e6ea..6e2bc4ced 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -34,7 +34,7 @@ import Explorer from "../Explorer"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient"; import ConflictsTab from "../Tabs/ConflictsTab"; -import GraphTab from "../Tabs/GraphTab"; +import type GraphTab from "../Tabs/GraphTab"; import { NewMongoQueryTab } from "../Tabs/MongoQueryTab/MongoQueryTab"; import { NewMongoShellTab } from "../Tabs/MongoShellTab/MongoShellTab"; import { NewQueryTab } from "../Tabs/QueryTab/QueryTab"; @@ -450,7 +450,7 @@ export default class Collection implements ViewModels.Collection { } } - public onGraphDocumentsClick() { + public async onGraphDocumentsClick() { useSelectedNode.getState().setSelectedNode(this); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph); TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { @@ -483,7 +483,8 @@ export default class Collection implements ViewModels.Collection { tabTitle: title, }); - graphTab = new GraphTab({ + const { default: GraphTabModule } = await import(/* webpackChunkName: "GraphTab" */ "../Tabs/GraphTab"); + graphTab = new GraphTabModule({ account: userContext.databaseAccount, tabKind: ViewModels.CollectionTabKind.Graph, node: this, @@ -727,7 +728,7 @@ export default class Collection implements ViewModels.Collection { useTabs.getState().activateNewTab(newMongoQueryTab); } - public onNewGraphClick() { + public async onNewGraphClick() { const id: number = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Graph).length + 1; const title: string = "Graph Query " + id; @@ -739,7 +740,8 @@ export default class Collection implements ViewModels.Collection { tabTitle: title, }); - const graphTab: GraphTab = new GraphTab({ + const { default: GraphTabModule } = await import(/* webpackChunkName: "GraphTab" */ "../Tabs/GraphTab"); + const graphTab: GraphTab = new GraphTabModule({ account: userContext.databaseAccount, tabKind: ViewModels.CollectionTabKind.Graph, node: this, diff --git a/src/Main.tsx b/src/Main.tsx index 43e1a90f6..6bc9cdd9c 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -5,27 +5,14 @@ import "./ReactDevTools"; import { initializeIcons, loadTheme, useTheme } from "@fluentui/react"; import { FluentProvider, makeStyles, webDarkTheme, webLightTheme } from "@fluentui/react-components"; import { Platform } from "ConfigContext"; -import ContainerCopyPanel from "Explorer/ContainerCopy/ContainerCopyPanel"; import Explorer from "Explorer/Explorer"; -import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel"; -import { MongoQuickstartTutorial } from "Explorer/Quickstart/Tutorials/MongoQuickstartTutorial"; -import { SQLQuickstartTutorial } from "Explorer/Quickstart/Tutorials/SQLQuickstartTutorial"; import { userContext } from "UserContext"; import "allotment/dist/style.css"; -import "bootstrap/dist/css/bootstrap.css"; import { useCarousel } from "hooks/useCarousel"; import React from "react"; import ReactDOM from "react-dom"; -import "../externals/jquery-ui.min.css"; -import "../externals/jquery-ui.min.js"; -import "../externals/jquery-ui.structure.min.css"; -import "../externals/jquery-ui.theme.min.css"; -import "../externals/jquery.dataTables.min.css"; -import "../externals/jquery.typeahead.min.css"; -import "../externals/jquery.typeahead.min.js"; // Image Dependencies import { SidePanel } from "Explorer/Panes/PanelContainerComponent"; -import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel"; import { SidebarContainer } from "Explorer/Sidebar"; import { KeyboardShortcutRoot } from "KeyboardShortcuts"; import "allotment/dist/style.css"; @@ -72,6 +59,50 @@ import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer"; import { useThemeStore } from "./hooks/useTheme"; import "./less/DarkModeMenus.less"; import "./less/ThemeSystem.less"; +// Lazy-loaded components (code-split into separate chunks) +const ContainerCopyPanel = React.lazy( + () => import(/* webpackChunkName: "ContainerCopyPanel" */ "Explorer/ContainerCopy/ContainerCopyPanel"), +); +const QuickstartCarousel = React.lazy(() => + import(/* webpackChunkName: "QuickstartCarousel" */ "Explorer/Quickstart/QuickstartCarousel").then((m) => ({ + default: m.QuickstartCarousel, + })), +); +const SQLQuickstartTutorial = React.lazy(() => + import(/* webpackChunkName: "SQLQuickstartTutorial" */ "Explorer/Quickstart/Tutorials/SQLQuickstartTutorial").then( + (m) => ({ default: m.SQLQuickstartTutorial }), + ), +); +const MongoQuickstartTutorial = React.lazy(() => + import( + /* webpackChunkName: "MongoQuickstartTutorial" */ "Explorer/Quickstart/Tutorials/MongoQuickstartTutorial" + ).then((m) => ({ default: m.MongoQuickstartTutorial })), +); +const QueryCopilotCarousel = React.lazy(() => + import(/* webpackChunkName: "QueryCopilotCarousel" */ "Explorer/QueryCopilot/CopilotCarousel").then((m) => ({ + default: m.QueryCopilotCarousel, + })), +); + +// Defer loading legacy jQuery/Bootstrap CSS and JS — they are needed by Tables features, not on initial render +const loadLegacyDependencies = () => { + // @ts-expect-error — side-effect-only imports handled by webpack, not real TS/ES modules + import(/* webpackChunkName: "legacy-styles" */ "bootstrap/dist/css/bootstrap.css"); + // @ts-expect-error — webpack handles CSS imports + import(/* webpackChunkName: "legacy-styles" */ "../externals/jquery-ui.min.css"); + // @ts-expect-error — webpack handles JS side-effect imports + import(/* webpackChunkName: "legacy-scripts" */ "../externals/jquery-ui.min.js"); + // @ts-expect-error — webpack handles CSS imports + import(/* webpackChunkName: "legacy-styles" */ "../externals/jquery-ui.structure.min.css"); + // @ts-expect-error — webpack handles CSS imports + import(/* webpackChunkName: "legacy-styles" */ "../externals/jquery-ui.theme.min.css"); + // @ts-expect-error — webpack handles CSS imports + import(/* webpackChunkName: "legacy-styles" */ "../externals/jquery.dataTables.min.css"); + // @ts-expect-error — webpack handles CSS imports + import(/* webpackChunkName: "legacy-styles" */ "../externals/jquery.typeahead.min.css"); + import(/* webpackChunkName: "legacy-scripts" */ "../externals/jquery.typeahead.min.js"); +}; + // Initialize icons before React is loaded initializeIcons(undefined, { disableWarnings: true }); @@ -98,6 +129,8 @@ const App = (): JSX.Element => { import("../less/documentDBFabric.less"); } StyleConstants.updateStyles(); + // Load legacy jQuery/Bootstrap dependencies after initial render + loadLegacyDependencies(); }, [config?.platform]); const explorer = useKnockoutExplorer(config?.platform); @@ -131,11 +164,11 @@ const App = (): JSX.Element => { {userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? ( - <> + - > + ) : ( )} @@ -191,10 +224,12 @@ const DivExplorer: React.FC<{ explorer: Explorer }> = ({ explorer }) => { - {} - {} - {} - {} + + {} + {} + {} + {} + ); }; diff --git a/webpack.config.js b/webpack.config.js index 7dcc89828..0e6e1b921 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -123,7 +123,7 @@ module.exports = function (_env = {}, argv = {}) { new HtmlWebpackPlugin({ filename: "explorer.html", template: "src/explorer.html", - chunks: ["main"], + chunks: ["main", "vendor", "vendor-fluentui", "vendor-nteract"], }), new HtmlWebpackPlugin({ filename: "terminal.html", @@ -262,6 +262,30 @@ module.exports = function (_env = {}, argv = {}) { }, }), ], + splitChunks: { + chunks: "all", + maxInitialRequests: 10, + cacheGroups: { + fluentui: { + test: /[\\/]node_modules[\\/]@fluentui[\\/]/, + name: "vendor-fluentui", + chunks: "all", + priority: 30, + }, + nteract: { + test: /[\\/]node_modules[\\/]@nteract[\\/]/, + name: "vendor-nteract", + chunks: "all", + priority: 30, + }, + vendor: { + test: /[\\/]node_modules[\\/]/, + name: "vendor", + chunks: "all", + priority: 10, + }, + }, + }, }, watch: false, // Hack since it is hard to disable watch entirely with webpack dev server https://github.com/webpack/webpack-dev-server/issues/1251#issuecomment-654240734