최근 사내에서 개발자들을 위한 간단한 코드 에디터 UI를 개발해야 일이 생겼다.
Task: Create UI to see/edit the configuration snapshot
개발 배경은 다음과 같다.
Customize가 가능한 대시보드를 만드는 프로젝트를 진행 중에 있는데, Customize를 위해 사용되는 configurations 를 설정하는 도구를 1. 엑셀 => 2. RDS 로 최근에 migrate했다.
최종 목표는 해당 대시보드 내에서 drag&drop 과 각 차트나 테이블 등 컴포넌트별로 edit할 수 있도록 UI를 업그레이드 할 예정이지만
개발자들의 편의를 위해, 개발자들을 위한 UI를 빠르게 개발하기로 결정하였다.
configurations 에 해당하는 raw json 을 보고 수정할 수 있는 UI를 개발하는 것이 목표다.
처음으로 User가 아닌 개발자를 위한 개발을 해보았다!
📌 사용한 라이브러리: Monaco Editor
📌 사용한 webpack: @monaco-editor/react
About Monaco Editor for React - use the monaco-editor in any React application without needing to use webpack (or rollup/parcel/etc) configuration files / plugins
yarn add monaco-editor monaco-editor-webpack-plugin @monaco-editor/react
MonacoEditor
Component 만들기import Editor from '@monaco-editor/react';
import { LANGUAGE_TYPE } from './constants';
import { ErrorType, LanguageType, MarkerType } from './type';
interface MonacoEditorProps {
value: string;
onChange: (newValue: string, event: any) => void;
onValidation: (newErrors: ErrorType[]) => void;
language?: LanguageType; // Default is json
}
const MonacoEditor: React.FC<MonacoEditorProps> = ({
value,
onChange,
onValidation,
language = LANGUAGE_TYPE.JSON,
}) => {
function handleEditorValidation(markers: MarkerType[]) {
const markerErrors = Array.from(
new Set(
markers.map((marker) => ({
startLineNumber: marker.startLineNumber,
endLineNumber: marker.endLineNumber,
}))
)
);
onValidation(markerErrors);
}
return (
<Editor height="80vh" language={language} value={value} onChange={onChange} onValidate={handleEditorValidation} />
);
};
export default MonacoEditor;
🔵 props
1.value
: Required
- MonacoEditor 에 보여줄 내용, input value라고 생각하면 됨
2.onChange
: Required
3.onValidation
: Required
- json format에 맞지 않으면 submit button을 disable 해야하기 때문에 필요함
4.language
: Optional, default isjson
MonacoEditor
Component 를 사용하는 component 예시const SettingModal = () => {
const [code, setCode] = useState('');
const [errors, setErrors] = useState<ErrorType[]>([]);
const handleCodeChange = (newCode) => {
setCode(newCode);
};
const handleValidation = (newErrors) => {
setErrors(newErrors);
};
return (
<MonacoEditor
value={code}
onChange={handleCodeChange}
onValidation={handleValidation}
language={LANGUAGE_TYPE.JSON}
/>
);
};
export default SettingModal;
DiffEditor
을 사용하고 싶었으나, onChange
property가 없는 관계로 Editor
을 사용하였다.
아래와 같이 여기에 의하면 manually 하게 onChange
동작 방식의 function을 정의하여 사용할 수 있다.
You could use something like this,
function handleEditorDidMount(editor, monaco) { console.log('handleEditorDidMount') // here is another way to get monaco instance // you can also store it in `useRef` for further usage monacoRef.current = monaco; const ed = editor.getModel().modified; ed.onDidChangeContent((event) => { onChange(ed.getValue()); }); }
Note that this can cause memory leak since too many listeners can be added with onDidChangeContent
하지만 memory leak 을 일으킬 수 있다고 하여 일단 1차적으로는 Editor
를 사용하기로 하였다.