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

feat: Lazy loading and code splitting #1802

Merged
merged 16 commits into from
Feb 26, 2024
Merged
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'));
mofojed marked this conversation as resolved.
Show resolved Hide resolved

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';
bmingles marked this conversation as resolved.
Show resolved Hide resolved
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> {
bmingles marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -107,9 +107,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
Loading