diff --git a/package-lock.json b/package-lock.json index 536b879d6..959cce2be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3776,6 +3776,8 @@ }, "node_modules/@monaco-editor/react": { "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", "license": "MIT", "dependencies": { "@monaco-editor/loader": "^1.5.0" @@ -10138,7 +10140,8 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==", - "license": "(MPL-2.0 OR Apache-2.0)" + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true }, "node_modules/dot-case": { "version": "3.0.4", @@ -18780,6 +18783,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.54.0.tgz", "integrity": "sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.1.7", "marked": "14.0.0" @@ -18790,6 +18794,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -26967,7 +26972,6 @@ "@patternfly/react-table": "^6.1.0", "@segment/analytics-next": "^1.76.0", "clsx": "^2.1.0", - "monaco-editor": "^0.54.0", "path-browserify": "^1.0.1", "posthog-js": "^1.194.4", "react-markdown": "^9.0.1", @@ -27004,8 +27008,18 @@ "victory-voronoi-container": "^37.1.1" }, "peerDependencies": { + "@monaco-editor/react": "^4.7.0", + "monaco-editor": "^0.54.0", "react": "^18 || ^19", "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@monaco-editor/react": { + "optional": false + }, + "monaco-editor": { + "optional": false + } } }, "packages/module/node_modules/@patternfly/patternfly": { diff --git a/packages/module/package.json b/packages/module/package.json index 6b2a6915d..a666d82aa 100644 --- a/packages/module/package.json +++ b/packages/module/package.json @@ -36,11 +36,10 @@ "@patternfly/react-code-editor": "^6.1.0", "@patternfly/react-core": "^6.1.0", "@patternfly/react-icons": "^6.1.0", - "@patternfly/react-table": "^6.1.0", "@patternfly/react-styles": "^6.1.0", + "@patternfly/react-table": "^6.1.0", "@segment/analytics-next": "^1.76.0", "clsx": "^2.1.0", - "monaco-editor": "^0.54.0", "path-browserify": "^1.0.1", "posthog-js": "^1.194.4", "react-markdown": "^9.0.1", @@ -52,9 +51,19 @@ "unist-util-visit": "^5.0.0" }, "peerDependencies": { + "monaco-editor": "^0.54.0", + "@monaco-editor/react": "^4.7.0", "react": "^18 || ^19", "react-dom": "^18 || ^19" }, + "peerDependenciesMeta": { + "monaco-editor": { + "optional": false + }, + "@monaco-editor/react": { + "optional": false + } + }, "devDependencies": { "@octokit/rest": "^18.0.0", "@patternfly/documentation-framework": "6.28.9", diff --git a/packages/module/src/CodeModal/CodeModal.tsx b/packages/module/src/CodeModal/CodeModal.tsx index 31baab50d..59788042c 100644 --- a/packages/module/src/CodeModal/CodeModal.tsx +++ b/packages/module/src/CodeModal/CodeModal.tsx @@ -5,17 +5,17 @@ import type { FunctionComponent, MouseEvent } from 'react'; import { useState, useEffect, useRef } from 'react'; import path from 'path-browserify'; -import * as monaco from 'monaco-editor'; -import { loader } from '@monaco-editor/react'; // Import PatternFly components import { CodeEditor } from '@patternfly/react-code-editor'; import { + Bullseye, Button, getResizeObserver, ModalBody, ModalFooter, ModalHeader, + Spinner, Stack, StackItem } from '@patternfly/react-core'; @@ -23,8 +23,16 @@ import FileDetails, { extensionToLanguage } from '../FileDetails'; import { ChatbotDisplayMode } from '../Chatbot'; import ChatbotModal from '../ChatbotModal/ChatbotModal'; -// Configure Monaco loader to use the npm package instead of CDN -loader.config({ monaco }); +// Try to lazy load - some consumers need to be below a certain bundle size, but can't use the CDN and don't have webpack +let monacoInstance: typeof import('monaco-editor') | null = null; +const loadMonaco = async () => { + if (!monacoInstance) { + const [monaco, { loader }] = await Promise.all([import('monaco-editor'), import('@monaco-editor/react')]); + monacoInstance = monaco; + loader.config({ monaco }); + } + return monacoInstance; +}; export interface CodeModalProps { /** Class applied to code editor */ @@ -63,6 +71,8 @@ export interface CodeModalProps { modalBodyClassName?: string; /** Class applied to modal footer */ modalFooterClassName?: string; + /** Aria label applied to spinner when loading Monaco */ + spinnerAriaLabel?: string; } export const CodeModal: FunctionComponent = ({ @@ -84,13 +94,32 @@ export const CodeModal: FunctionComponent = ({ modalHeaderClassName, modalBodyClassName, modalFooterClassName, + spinnerAriaLabel = 'Loading', ...props }: CodeModalProps) => { const [newCode, setNewCode] = useState(code); - const [editorInstance, setEditorInstance] = useState(null); + const [editorInstance, setEditorInstance] = useState(null); const [isEditorReady, setIsEditorReady] = useState(false); + const [isMonacoLoading, setIsMonacoLoading] = useState(false); + const [isMonacoLoaded, setIsMonacoLoaded] = useState(false); const containerRef = useRef(null); + useEffect(() => { + if (isModalOpen && !isMonacoLoaded && !isMonacoLoading) { + setIsMonacoLoading(true); + loadMonaco() + .then(() => { + setIsMonacoLoaded(true); + setIsMonacoLoading(false); + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error('Failed to load Monaco editor:', error); + setIsMonacoLoading(false); + }); + } + }, [isModalOpen, isMonacoLoaded, isMonacoLoading]); + useEffect(() => { if (!isModalOpen || !isEditorReady || !editorInstance || !containerRef.current) { return; @@ -148,6 +177,42 @@ export const CodeModal: FunctionComponent = ({ } }; + const renderMonacoEditor = () => { + if (isMonacoLoading) { + return ( + + + + ); + } + if (isMonacoLoaded) { + return ( + + ); + } + return null; + }; + const modal = ( = ({
- + {renderMonacoEditor()}
diff --git a/packages/module/tsconfig.json b/packages/module/tsconfig.json index e67c79125..9c6eb57dd 100644 --- a/packages/module/tsconfig.json +++ b/packages/module/tsconfig.json @@ -5,7 +5,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, - "module": "es2015" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + "module": "es2020" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */