diff --git a/ui/src/app/apidocs/components/apiDocs.tsx b/ui/src/app/apidocs/components/apiDocs.tsx index 9e6f8676b1e8..8d6a6ee9e6e8 100644 --- a/ui/src/app/apidocs/components/apiDocs.tsx +++ b/ui/src/app/apidocs/components/apiDocs.tsx @@ -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) { diff --git a/ui/src/app/reports/components/report-container.tsx b/ui/src/app/reports/components/report-container.tsx index 1a14d2eee11d..1bd13a631105 100644 --- a/ui/src/app/reports/components/report-container.tsx +++ b/ui/src/app/reports/components/report-container.tsx @@ -9,7 +9,7 @@ export const ReportsContainer = (props: RouteComponentProps) => ( ); // 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) { return ( diff --git a/ui/src/app/shared/components/object-editor/object-editor.tsx b/ui/src/app/shared/components/object-editor/object-editor.tsx index 4fbbee76ffd9..363772e66982 100644 --- a/ui/src/app/shared/components/object-editor/object-editor.tsx +++ b/ui/src/app/shared/components/object-editor/object-editor.tsx @@ -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 { type?: string; @@ -22,11 +23,16 @@ export const ObjectEditor = ({type, value, buttons, onChange}: Pr const [error, setError] = useState(); const [lang, setLang] = useState(storage.getItem('lang', defaultLang)); const [text, setText] = useState(stringify(value, lang)); + const editor = useRef(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); @@ -34,36 +40,42 @@ export const ObjectEditor = ({type, value, buttons, onChange}: Pr 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(); // 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); @@ -100,7 +112,7 @@ export const ObjectEditor = ({type, value, buttons, onChange}: Pr {buttons}
- { + 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) { + return ( + }> + + + ); +}); diff --git a/ui/src/app/workflows/components/workflow-details/artifact-panel.tsx b/ui/src/app/workflows/components/workflow-details/artifact-panel.tsx index 9e4837ca75a7..1b73a6c11b3c 100644 --- a/ui/src/app/workflows/components/workflow-details/artifact-panel.tsx +++ b/ui/src/app/workflows/components/workflow-details/artifact-panel.tsx @@ -1,6 +1,6 @@ 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'; @@ -8,6 +8,7 @@ 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'; @@ -82,7 +83,7 @@ export const ArtifactPanel = ({ ) : show ? ( {object ? ( -