Skip to content

Commit

Permalink
refactor(ui): code-split gigantic Monaco Editor dep (#12150)
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Gilgur <agilgur5@gmail.com>
  • Loading branch information
agilgur5 authored and terrytangyuan committed Nov 27, 2023
1 parent 8615f53 commit 4b162df
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 32 deletions.
4 changes: 2 additions & 2 deletions ui/src/app/apidocs/components/apiDocs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export const ApiDocs = () => {

// lazy load SwaggerUI as it is infrequently used and imports very large components (which can be split into a separate bundle)
const LazySwaggerUI = React.lazy(() => {
import('swagger-ui-react/swagger-ui.css');
return import('swagger-ui-react');
import(/* webpackChunkName: "swagger-ui-react-css" */ 'swagger-ui-react/swagger-ui.css');
return import(/* webpackChunkName: "swagger-ui-react" */ 'swagger-ui-react');
});

function SuspenseSwaggerUI(props: any) {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/reports/components/report-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const ReportsContainer = (props: RouteComponentProps<any>) => (
);

// lazy load Reports as it is infrequently used and imports large Chart components (which can be split into a separate bundle)
const LazyReports = React.lazy(() => import('./reports'));
const LazyReports = React.lazy(() => import(/* webpackChunkName: "reports" */ './reports'));

function SuspenseReports(props: RouteComponentProps<any>) {
return (
Expand Down
66 changes: 39 additions & 27 deletions ui/src/app/shared/components/object-editor/object-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {languages} from 'monaco-editor/esm/vs/editor/editor.api';
import * as React from 'react';
import {createRef, useEffect, useState} from 'react';
import {useEffect, useRef, useState} from 'react';
import MonacoEditor from 'react-monaco-editor';

import {uiUrl} from '../../base';
import {ScopedLocalStorage} from '../../scoped-local-storage';
import {Button} from '../button';
import {parse, stringify} from '../object-parser';
import {PhaseIcon} from '../phase-icon';
import {SuspenseMonacoEditor} from '../suspense-monaco-editor';

interface Props<T> {
type?: string;
Expand All @@ -22,48 +23,59 @@ export const ObjectEditor = <T extends any>({type, value, buttons, onChange}: Pr
const [error, setError] = useState<Error>();
const [lang, setLang] = useState<string>(storage.getItem('lang', defaultLang));
const [text, setText] = useState<string>(stringify(value, lang));
const editor = useRef<MonacoEditor>(null);

useEffect(() => storage.setItem('lang', lang, defaultLang), [lang]);
useEffect(() => setText(stringify(value, lang)), [value]);
useEffect(() => setText(stringify(parse(text), lang)), [lang]);
useEffect(() => {
if (!editor.current) {
return;
}

// we ONLY want to change the text, if the normalized version has changed, this prevents white-space changes
// from resulting in a significant change
const editorText = stringify(parse(editor.current.editor.getValue()), lang);
const editorLang = editor.current.editor.getValue().startsWith('{') ? 'json' : 'yaml';
if (text !== editorText || lang !== editorLang) {
editor.current.editor.setValue(stringify(parse(text), lang));
}
}, [text, lang]);
}, [editor, text, lang]);

useEffect(() => {
if (type && lang === 'json') {
if (!type || lang !== 'json') {
return;
}

(async () => {
const uri = uiUrl('assets/jsonschema/schema.json');
fetch(uri)
.then(res => res.json())
.then(swagger => {
// adds auto-completion to JSON only
languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri,
fileMatch: ['*'],
schema: {
$id: 'http://workflows.argoproj.io/' + type + '.json',
$ref: '#/definitions/' + type,
$schema: 'http://json-schema.org/draft-07/schema',
definitions: swagger.definitions
}
try {
const res = await fetch(uri);
const swagger = await res.json();
// lazy load this, otherwise all of monaco-editor gets imported into the main bundle
const languages = (await import(/* webpackChunkName: "monaco-editor" */ 'monaco-editor/esm/vs/editor/editor.api')).languages;
// adds auto-completion to JSON only
languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri,
fileMatch: ['*'],
schema: {
$id: 'http://workflows.argoproj.io/' + type + '.json',
$ref: '#/definitions/' + type,
$schema: 'http://json-schema.org/draft-07/schema',
definitions: swagger.definitions
}
]
});
})
.catch(setError);
}
}
]
});
} catch (err) {
setError(err);
}
})();
}, [lang, type]);

const editor = createRef<MonacoEditor>();
// this calculation is rough, it is probably hard to work for for every case, essentially it is:
// some pixels above and below for buttons, plus a bit of a buffer/padding
const height = Math.max(600, window.innerHeight * 0.9 - 250);
Expand Down Expand Up @@ -100,7 +112,7 @@ export const ObjectEditor = <T extends any>({type, value, buttons, onChange}: Pr
{buttons}
</div>
<div>
<MonacoEditor
<SuspenseMonacoEditor
ref={editor}
key='editor'
defaultValue={text}
Expand Down
22 changes: 22 additions & 0 deletions ui/src/app/shared/components/suspense-monaco-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import {MonacoEditorProps} from 'react-monaco-editor';
import MonacoEditor from 'react-monaco-editor';

import {Loading} from './loading';

// lazy load Monaco Editor as it is a gigantic component (which can be split into a separate bundle)
const LazyMonacoEditor = React.lazy(() => {
return import(/* webpackChunkName: "react-monaco-editor" */ 'react-monaco-editor');
});

// workaround, react-monaco-editor's own default no-op seems to fail when lazy loaded, causing a crash when unmounted
// react-monaco-editor's default no-op: https://github.com/react-monaco-editor/react-monaco-editor/blob/7e5a4938cd328bf95ebc1288967f2037c6023b5a/src/editor.tsx#L184
const noop = () => {}; // tslint:disable-line:no-empty

export const SuspenseMonacoEditor = React.forwardRef(function InnerMonacoEditor(props: MonacoEditorProps, ref: React.MutableRefObject<MonacoEditor>) {
return (
<React.Suspense fallback={<Loading />}>
<LazyMonacoEditor ref={ref} editorWillUnmount={noop} {...props} />
</React.Suspense>
);
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as React from 'react';
import {useEffect, useState} from 'react';
import MonacoEditor from 'react-monaco-editor';

import {Artifact, ArtifactRepository, execSpec, Workflow} from '../../../../models';
import {artifactKey, artifactURN} from '../../../shared/artifacts';
import ErrorBoundary from '../../../shared/components/error-boundary';
import {ErrorNotice} from '../../../shared/components/error-notice';
import {FirstTimeUserPanel} from '../../../shared/components/first-time-user-panel';
import {GiveFeedbackLink} from '../../../shared/components/give-feedback-link';
import {LinkButton} from '../../../shared/components/link-button';
import {SuspenseMonacoEditor} from '../../../shared/components/suspense-monaco-editor';
import {useCollectEvent} from '../../../shared/components/use-collect-event';
import {services} from '../../../shared/services';
import requests from '../../../shared/services/requests';
Expand Down Expand Up @@ -82,7 +83,7 @@ export const ArtifactPanel = ({
) : show ? (
<ViewBox>
{object ? (
<MonacoEditor
<SuspenseMonacoEditor
value={object}
language='json'
height='500px'
Expand Down

0 comments on commit 4b162df

Please sign in to comment.