Skip to content

Commit

Permalink
feat: Lazy loading and code splitting (#1802)
Browse files Browse the repository at this point in the history
This adds lazy loading for `chart` and `iris-grid` and enables it for
`MarkdownComponent` as well.

Replaced the exports for `Chart` and `IrisGrid` with lazy loading
wrappers. This should mean anywhere else we use the components in the
app should be automatically lazy loaded.
  • Loading branch information
mattrunyon authored Feb 26, 2024
1 parent 69e8cdd commit 25d1c09
Show file tree
Hide file tree
Showing 24 changed files with 164 additions and 19 deletions.
15 changes: 15 additions & 0 deletions packages/chart/src/LazyChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LoadingOverlay } from '@deephaven/components';
import { lazy, Suspense } from 'react';

const Chart = lazy(() => import('./Chart.js'));

function LazyChart(props: React.ComponentProps<typeof Chart>): JSX.Element {
return (
<Suspense fallback={<LoadingOverlay />}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Chart {...props} />
</Suspense>
);
}

export default LazyChart;
4 changes: 2 additions & 2 deletions packages/chart/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export { default as Chart } from './Chart';
export { default as Chart } from './LazyChart';
export { default as ChartModelFactory } from './ChartModelFactory';
export { default as ChartModel } from './ChartModel';
export { default as ChartUtils } from './ChartUtils';
export * from './ChartUtils';
export * from './DownsamplingError';
export { default as FigureChartModel } from './FigureChartModel';
export { default as MockChartModel } from './MockChartModel';
export { default as Plot } from './plotly/Plot';
export { default as Plot } from './plotly/LazyPlot';
export * from './ChartTheme';
export * from './ChartThemeProvider';
export { default as isFigureChartModel } from './isFigureChartModel';
Expand Down
15 changes: 15 additions & 0 deletions packages/chart/src/plotly/LazyPlot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LoadingOverlay } from '@deephaven/components';
import { lazy, Suspense } from 'react';

const PlotBase = lazy(() => import('./Plot.js'));

function Plot(props: React.ComponentProps<typeof PlotBase>): JSX.Element {
return (
<Suspense fallback={<LoadingOverlay />}>
{/* eslint-disable react/jsx-props-no-spreading */}
<PlotBase {...props} />
</Suspense>
);
}

export default Plot;
3 changes: 3 additions & 0 deletions packages/code-studio/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export default defineConfig(({ mode }) => {
if (id.includes('plotly.js')) {
return 'plotly';
}
if (id.includes('mathjax')) {
return 'mathjax';
}
return 'vendor';
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/* stylelint-disable custom-property-empty-line-before */
/* stylelint-disable alpha-value-notation */
.dh-spectrum-alias {

/**
* Intentionally using the classname twice so we have higher specificity than spectrum's definitions
* This is to ensure that our overrides are applied regardless of CSS chunk loading order
*/
.dh-spectrum-alias.dh-spectrum-alias {
/*********** Override variables in spectrum-global.css **********************/
--spectrum-alias-background-color-default: var(--dh-color-bg);
--spectrum-alias-background-color-disabled: var(--dh-color-disabled-bg);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.dh-spectrum-palette {
/**
* Intentionally using the classname twice so we have higher specificity than spectrum's definitions
* This is to ensure that our overrides are applied regardless of CSS chunk loading order
*/
.dh-spectrum-palette.dh-spectrum-palette {
/* Gray */
--spectrum-gray-50: var(--dh-color-gray-50);
--spectrum-gray-75: var(--dh-color-gray-75);
Expand Down
2 changes: 1 addition & 1 deletion packages/console/src/notebook/ScriptEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LoadingOverlay, ShortcutRegistry } from '@deephaven/components';
import Log from '@deephaven/log';
import type { IdeSession } from '@deephaven/jsapi-types';
import { assertNotNull } from '@deephaven/utils';
import { editor, IDisposable } from 'monaco-editor';
import type { editor, IDisposable } from 'monaco-editor';
import Editor from './Editor';
import { MonacoProviders, MonacoUtils } from '../monaco';
import './ScriptEditor.scss';
Expand Down
3 changes: 2 additions & 1 deletion packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import {
AdvancedSettings,
IrisGrid,
type IrisGridType,
IrisGridModel,
IrisGridUtils,
isIrisGridTableModelTemplate,
Expand Down Expand Up @@ -362,7 +363,7 @@ export class IrisGridPanel extends PureComponent<
}
}

irisGrid: RefObject<IrisGrid>;
irisGrid: RefObject<IrisGridType>;

pluginRef: RefObject<TablePluginElement>;

Expand Down
2 changes: 1 addition & 1 deletion packages/iris-grid/src/IrisGrid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import dh from '@deephaven/jsapi-shim';
import { DateUtils, Settings } from '@deephaven/jsapi-utils';
import { TestUtils } from '@deephaven/utils';
import { TypeValue } from '@deephaven/filters';
import { IrisGrid } from './IrisGrid';
import IrisGrid from './IrisGrid';
import IrisGridTestUtils from './IrisGridTestUtils';

class MockPath2D {
Expand Down
3 changes: 2 additions & 1 deletion packages/iris-grid/src/IrisGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ function isEmptyConfig({
sorts.length === 0
);
}

export type FilterData = {
operator: FilterTypeValue;
text: string;
Expand Down Expand Up @@ -451,7 +452,7 @@ export interface IrisGridState {
columnHeaderGroups: readonly ColumnHeaderGroup[];
}

export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
class IrisGrid extends Component<IrisGridProps, IrisGridState> {
static contextType = IrisGridThemeContext;

static minDebounce = 150;
Expand Down
2 changes: 1 addition & 1 deletion packages/iris-grid/src/IrisGridCellOverflowModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import { Editor } from '@deephaven/console';
import * as monaco from 'monaco-editor';
import type * as monaco from 'monaco-editor';
import {
Button,
CopyButton,
Expand Down
26 changes: 26 additions & 0 deletions packages/iris-grid/src/LazyIrisGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { forwardRef, lazy, Suspense } from 'react';
import { LoadingOverlay } from '@deephaven/components';
import type IrisGridType from './IrisGrid';
import type { IrisGridProps } from './IrisGrid';

const IrisGrid = lazy(() => import('./IrisGrid.js'));

const LazyIrisGrid = forwardRef<
IrisGridType,
JSX.LibraryManagedAttributes<typeof IrisGridType, IrisGridProps>
>(
(
// This creates the correct type to make defaultProps optional
props,
ref
): JSX.Element => (
<Suspense fallback={<LoadingOverlay />}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<IrisGrid ref={ref} {...props} />
</Suspense>
)
);

LazyIrisGrid.displayName = 'LazyIrisGrid';

export default LazyIrisGrid;
3 changes: 2 additions & 1 deletion packages/iris-grid/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import IrisGrid from './IrisGrid';
import IrisGrid from './LazyIrisGrid';

export default IrisGrid;
export { IrisGrid };
Expand All @@ -8,6 +8,7 @@ export * from './CommonTypes';
export { default as ColumnHeaderGroup } from './ColumnHeaderGroup';
export * from './PartitionedGridModel';
export * from './IrisGrid';
export type { default as IrisGridType } from './IrisGrid';
export { default as SHORTCUTS } from './IrisGridShortcuts';
export { default as IrisGridModel } from './IrisGridModel';
export { default as IrisGridTableModel } from './IrisGridTableModel';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KeyboardEvent } from 'react';
import { KeyHandler } from '@deephaven/grid';
import { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';
import IrisGridShortcuts from '../IrisGridShortcuts';

class ClearFilterKeyHandler extends KeyHandler {
Expand Down
2 changes: 1 addition & 1 deletion packages/iris-grid/src/key-handlers/CopyCellKeyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { KeyboardEvent } from 'react';
import { KeyHandler } from '@deephaven/grid';
import { ContextActionUtils } from '@deephaven/components';
import type { Grid } from '@deephaven/grid';
import { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';

class CopyCellKeyHandler extends KeyHandler {
private irisGrid: IrisGrid;
Expand Down
2 changes: 1 addition & 1 deletion packages/iris-grid/src/key-handlers/CopyKeyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { KeyboardEvent } from 'react';
import { ContextActionUtils } from '@deephaven/components';
import { KeyHandler } from '@deephaven/grid';
import { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';
import IrisGridUtils from '../IrisGridUtils';

class CopyKeyHandler extends KeyHandler {
Expand Down
2 changes: 1 addition & 1 deletion packages/iris-grid/src/key-handlers/ReverseKeyHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { KeyboardEvent } from 'react';
import { KeyHandler } from '@deephaven/grid';
import { TableUtils } from '@deephaven/jsapi-utils';
import { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';
import IrisGridShortcuts from '../IrisGridShortcuts';

class ReverseKeyHandler extends KeyHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
EventHandlerResult,
} from '@deephaven/grid';
import type { Column } from '@deephaven/jsapi-types';
import { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';
import { DisplayColumn } from '../IrisGridModel';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Grid,
GridMouseEvent,
} from '@deephaven/grid';
import type { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';

/**
* Detects mouse hover over column headers and displays the appropriate tooltip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
GridPoint,
EventHandlerResult,
} from '@deephaven/grid';
import type { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';

/**
* Handles sending data selected via double click
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
GridPoint,
EventHandlerResult,
} from '@deephaven/grid';
import type { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';

/**
* Trigger quick filters and advanced filters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
GridRangeIndex,
EventHandlerResult,
} from '@deephaven/grid';
import type { IrisGrid } from '../IrisGrid';
import type IrisGrid from '../IrisGrid';

/**
* Used to handle sorting on column header clicks
Expand Down
70 changes: 70 additions & 0 deletions tests/lazy-loading.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { test, expect, Request } from '@playwright/test';
import { openPlot } from './utils';

/**
* Checks the size of the response body of a request
* If the response body size is 0, the request is not checked
* This seems to happen for Safari sometimes
*
* @param request The request object. Playwright provides size on the request, not the response
* @param size The minimum size in bytes
*/
async function expectMinimumResponseSize(
request: Request | undefined,
size: number
) {
if (!request) {
throw new Error('Request is undefined');
}

const responseSize = (await request.sizes()).responseBodySize;

// Safari doesn't seem to provide response size for some requests
if (responseSize > 0) {
expect(responseSize).toBeGreaterThan(size);
}
}

test('lazy loads plotly', async ({ page }) => {
const requests: Request[] = [];
page.on('request', req => requests.push(req));

await page.goto('');
await page.waitForLoadState('networkidle');

expect(requests.some(req => req.url().includes('assets/plotly'))).toBe(false);

await openPlot(page, 'simple_plot');

const plotlyRequest = requests.find(req =>
req.url().includes('assets/plotly')
);
expect(plotlyRequest).toBeDefined();
await expectMinimumResponseSize(plotlyRequest, 300 * 1000); // 300kB
});

test('lazy loads mathjax', async ({ page }) => {
const requests: Request[] = [];
page.on('request', req => requests.push(req));

await page.goto('');
await page.waitForLoadState('networkidle');

expect(requests.some(req => req.url().includes('assets/mathjax'))).toBe(
false
);

const controlsButton = page.getByText('Controls');
await controlsButton.click();
const markdownButton = page.getByText('Markdown Widget');
await markdownButton.click();

await expect(page.locator('.markdown-panel')).toBeVisible();
await expect(page.locator('.markdown-panel .loading-spinner')).toHaveCount(0);

const mathjaxRequest = requests.find(req =>
req.url().includes('assets/mathjax')
);
expect(mathjaxRequest).toBeDefined();
await expectMinimumResponseSize(mathjaxRequest, 500 * 1000); // 500kB
});
4 changes: 4 additions & 0 deletions tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,13 @@ export async function openPlot(

if (waitForLoadFinished) {
// Wait until it's done loading
await expect(page.locator('.chart-panel-container')).toHaveCount(1);
await expect(
page.locator('.chart-panel-container .loading-spinner')
).toHaveCount(0);
await expect(
page.locator('.chart-panel-container .chart-wrapper')
).toHaveCount(1);
}
}

Expand Down

0 comments on commit 25d1c09

Please sign in to comment.