Skip to content

Commit 9d9ff41

Browse files
authored
refactor(FR-2874): replace codemirror with monaco in BAICodeEditor (#7390)
1 parent 08b7a18 commit 9d9ff41

7 files changed

Lines changed: 143 additions & 748 deletions

File tree

pnpm-lock.yaml

Lines changed: 50 additions & 712 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ allowBuilds:
9797
overrides:
9898
tar-fs@2: 2.1.4
9999
node-forge: '>=1.3.2'
100-
'@codemirror/state': 6.5.4
101100
# langium is pulled transitively via @ant-design/x and @lobehub/ui →
102101
# mermaid → @mermaid-js/parser. 4.2.1–4.2.3 import vscode-languageserver-
103102
# {types,protocol}, vscode-jsonrpc and @chevrotain/regexp-to-ast without

react/package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111
"@ant-design/icons": "catalog:",
1212
"@ant-design/x": "^2.5.0",
1313
"@cloudscape-design/board-components": "3.0.60",
14-
"@codemirror/lang-jinja": "^6.0.1",
15-
"@codemirror/lang-liquid": "^6.3.2",
16-
"@codemirror/language": "^6.12.3",
1714
"@dicebear/collection": "^9.4.2",
1815
"@dicebear/core": "^9.4.2",
1916
"@lobehub/fluent-emoji": "^2.0.0",
@@ -24,8 +21,6 @@
2421
"@react-hook/resize-observer": "^2.0.2",
2522
"@tanstack/react-query": "catalog:",
2623
"@testing-library/react": "catalog:",
27-
"@uiw/codemirror-extensions-langs": "^4.25.9",
28-
"@uiw/react-codemirror": "^4.25.9",
2924
"ahooks": "catalog:",
3025
"ai": "^5.0.173",
3126
"ajv": "^8.18.0",

react/src/components/BAICodeEditor.tsx

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,42 @@
22
@license
33
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
44
*/
5+
import { loadMonacoEditor } from '../helper/monacoEditor';
56
import useControllableState_deprecated from '../hooks/useControllableState';
67
import { useThemeMode } from '../hooks/useThemeMode';
7-
import { loadLanguage, LanguageName } from '@uiw/codemirror-extensions-langs';
8-
import CodeMirror, {
9-
ReactCodeMirrorProps,
10-
EditorView,
11-
} from '@uiw/react-codemirror';
8+
import type { EditorProps } from '@monaco-editor/react';
9+
import { Skeleton, theme } from 'antd';
10+
import React, { Suspense } from 'react';
1211

13-
interface BAICodeEditorProps extends Omit<ReactCodeMirrorProps, 'language'> {
12+
const MonacoEditor: React.LazyExoticComponent<React.FC<EditorProps>> =
13+
React.lazy(() =>
14+
loadMonacoEditor().then((module) => ({
15+
default: module.Editor as React.FC<EditorProps>,
16+
})),
17+
);
18+
19+
// Language alias preserved from the previous codemirror-based API so existing
20+
// call sites keep working. Extend as needed when adding new languages.
21+
export type BAICodeEditorLanguage = 'json' | 'sh' | 'yaml' | 'toml';
22+
23+
const MONACO_LANGUAGE_MAP: Record<BAICodeEditorLanguage, string> = {
24+
json: 'json',
25+
sh: 'shell',
26+
yaml: 'yaml',
27+
toml: 'plaintext',
28+
};
29+
30+
interface BAICodeEditorProps extends Omit<
31+
EditorProps,
32+
'language' | 'value' | 'onChange'
33+
> {
1434
value?: string;
1535
onChange?: (value: string) => void;
16-
language?: LanguageName;
36+
language?: BAICodeEditorLanguage;
1737
editable?: boolean;
1838
showLineNumbers?: boolean;
1939
lineWrapping?: boolean;
40+
style?: React.CSSProperties;
2041
}
2142

2243
const BAICodeEditor: React.FC<BAICodeEditorProps> = ({
@@ -26,34 +47,62 @@ const BAICodeEditor: React.FC<BAICodeEditorProps> = ({
2647
editable = false,
2748
showLineNumbers = true,
2849
lineWrapping = false,
29-
...CodeMirrorProps
50+
height = 200,
51+
style,
52+
options,
53+
...editorProps
3054
}) => {
55+
'use memo';
3156
const { isDarkMode } = useThemeMode();
57+
const { token } = theme.useToken();
3258

3359
const [script, setScript] = useControllableState_deprecated<string>({
3460
defaultValue: '',
3561
value,
3662
onChange,
3763
});
38-
const languageExtension = loadLanguage(language);
39-
const extensions = languageExtension ? [languageExtension] : [];
4064

41-
return (
42-
<CodeMirror
43-
theme={isDarkMode ? 'dark' : 'light'}
44-
extensions={
45-
lineWrapping ? [EditorView.lineWrapping, ...extensions] : extensions
46-
}
47-
editable={editable}
48-
readOnly={!editable}
49-
basicSetup={{
50-
lineNumbers: showLineNumbers,
65+
const loadingFallback = (
66+
<Skeleton
67+
active
68+
style={{
69+
paddingInline: token.paddingContentHorizontal,
70+
paddingBlock: token.paddingContentVertical,
5171
}}
52-
value={script}
53-
onChange={(value) => setScript(value)}
54-
{...CodeMirrorProps}
5572
/>
5673
);
74+
75+
return (
76+
<div
77+
style={{
78+
border: `1px solid ${token.colorBorder}`,
79+
borderRadius: token.borderRadius,
80+
overflow: 'hidden',
81+
...style,
82+
}}
83+
>
84+
<Suspense fallback={loadingFallback}>
85+
<MonacoEditor
86+
language={MONACO_LANGUAGE_MAP[language]}
87+
height={height}
88+
theme={isDarkMode ? 'vs-dark' : 'vs'}
89+
value={script}
90+
onChange={(v: string | undefined) => setScript(v ?? '')}
91+
loading={loadingFallback}
92+
options={{
93+
readOnly: !editable,
94+
lineNumbers: showLineNumbers ? 'on' : 'off',
95+
wordWrap: lineWrapping ? 'on' : 'off',
96+
minimap: { enabled: false },
97+
scrollBeyondLastLine: false,
98+
fixedOverflowWidgets: true,
99+
...options,
100+
}}
101+
{...editorProps}
102+
/>
103+
</Suspense>
104+
</div>
105+
);
57106
};
58107

59108
export default BAICodeEditor;

react/src/components/BAIJSONViewerModal.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
@license
33
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
44
*/
5-
import { useThemeMode } from '../hooks/useThemeMode';
65
import BAICodeEditor from './BAICodeEditor';
76
import { Alert } from 'antd';
87
import { BAIFlex, BAIModal, BAIModalProps } from 'backend.ai-ui';
@@ -20,7 +19,6 @@ const BAIJSONViewerModal: React.FC<BAIJSONViewerModalProps> = ({
2019
...modalProps
2120
}) => {
2221
const { t } = useTranslation();
23-
const { isDarkMode } = useThemeMode();
2422

2523
const { formattedJson, hasError } = useMemo(() => {
2624
if (typeof json === 'string') {
@@ -56,7 +54,6 @@ const BAIJSONViewerModal: React.FC<BAIJSONViewerModalProps> = ({
5654
<BAICodeEditor
5755
value={formattedJson}
5856
language={'json'}
59-
theme={isDarkMode ? 'dark' : 'light'}
6057
editable={false}
6158
/>
6259
</BAIFlex>

react/src/components/ContainerRegistryEditorModal.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { ContainerRegistryEditorModalCreateMutation } from '../__generated__/Con
66
import { ContainerRegistryEditorModalFragment$key } from '../__generated__/ContainerRegistryEditorModalFragment.graphql';
77
import { ContainerRegistryEditorModalModifyRegistryMutation } from '../__generated__/ContainerRegistryEditorModalModifyRegistryMutation.graphql';
88
import { useSuspendedBackendaiClient } from '../hooks';
9-
import { useThemeMode } from '../hooks/useThemeMode';
109
import BAICodeEditor from './BAICodeEditor';
1110
import HiddenFormItem from './HiddenFormItem';
1211
import ProjectSelectForAdminPage from './ProjectSelectForAdminPage';
@@ -52,7 +51,6 @@ const ContainerRegistryEditorModal: React.FC<
5251
> = ({ containerRegistryFrgmt = null, onOk, ...modalProps }) => {
5352
const { t } = useTranslation();
5453
const { token } = theme.useToken();
55-
const { isDarkMode } = useThemeMode();
5654
const { message, modal } = App.useApp();
5755

5856
const baiClient = useSuspendedBackendaiClient();
@@ -544,7 +542,6 @@ const ContainerRegistryEditorModal: React.FC<
544542
<BAICodeEditor
545543
editable
546544
language="json"
547-
theme={isDarkMode ? 'dark' : 'light'}
548545
style={{ width: '100%' }}
549546
/>
550547
</Form.Item>

react/vite.config.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,27 @@ export default defineConfig(({ mode }) => {
536536
nodePolyfills({
537537
include: ['buffer', 'stream'],
538538
globals: {
539-
Buffer: true,
539+
// Why not `Buffer: true` in dev:
540+
// With `true`, the plugin prepends
541+
// `import __buffer_polyfill from 'vite-plugin-node-polyfills/shims/buffer';`
542+
// `globalThis.Buffer = globalThis.Buffer || __buffer_polyfill;`
543+
// to every chunk that touches Buffer. Vite's dep optimizer also
544+
// wraps that same shim into a CJS-interop chunk — so the chunk
545+
// that *exports* `__vite__cjsImport0_vitePluginNodePolyfills_
546+
// shims_buffer` also *imports* it (via the injected prelude),
547+
// and the import lands in the same chunk before the export is
548+
// initialized → TDZ on first browser load. `'build'` scopes
549+
// the injection to the production rollup build only.
550+
//
551+
// Why this is safe to do here:
552+
// No app code under `react/src` or `packages/backend.ai-ui/src`
553+
// references the Buffer global. The only Buffer.* call that
554+
// survives into the prod bundle is `Buffer.byteLength` inside
555+
// `cross-fetch/dist/browser-ponyfill.js` (pulled transitively
556+
// by `i18next-http-backend`), and that lives on a Node-only
557+
// code path the browser ponyfill never enters. The polyfill
558+
// itself could likely be removed entirely; see follow-up.
559+
Buffer: 'build',
540560
global: false,
541561
process: false,
542562
},

0 commit comments

Comments
 (0)