实现一个基于monaco-editor的编辑器组件【bysking】实现一个基于monaco-editor的编辑器组件
实现一个基于monaco-editor的编辑器组件
- 支持指定语言的语法校验
- 支持自定义提示词语
- 支持语法高亮(默认)
安装核心依赖npm包
"monaco-editor": "^0.36.1",
"monaco-sql-languages": "^0.12.2"
-
如何获取校验worker
-
新建webpack打包项目
-
yarn init -y 初始化一个项目
-
安装依赖:webpack, webpack-cli
-
新建配置文件: webpack.config.js
-
const path = require('path');
module.exports = {
entry: {
// 添加想要的worker自行打包
// 'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'mysql.worker': 'monaco-sql-languages/esm/languages/mysql/mysql.worker.js',
},
output: {
globalObject: 'self',
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [],
},
};
- package.json增加构建脚本,默认就是用 webpack.config.js 的配置进行打包
{
"name": "webpack-worker",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^5.93.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"monaco-editor": "^0.36.1",
"monaco-sql-languages": "^0.12.2"
}
}
这样我们就得到了:
mysql.worker.js
把这个文件添加到dis产物目录: lib/mysql.worker.js
- 实现worker文件加载地址获取:monaco-worker.js
import 'monaco-sql-languages/esm/languages/mysql/mysql.contribution'; // https://www.npmjs.com/package/monaco-sql-languages
const customeGetUrl = (moduleId, label) => {
const publicPath = process.env.UMI_ENV === 'local' ? '/' : '/tw-sub/';
const pathUrl =
window.location.origin + publicPath + `lib/${label}.worker.js`; // 就是上面的worker,用于校验
// 加载自定义校验worker
if (['mysql'].includes(label)) {
return pathUrl;
}
return '';
};
const originFn: any = window.MonacoEnvironment?.getWorkerUrl;
if (originFn && window.MonacoEnvironment) {
window.MonacoEnvironment.getWorkerUrl = (_moduleId, label) => {
const str = customeGetUrl(_moduleId, label);
if (str) {
return str;
}
return originFn.call(this, _moduleId, label);
};
}
- 组件代码:
import * as monaco from 'monaco-editor';
import { useEffect, useRef } from 'react';
import './monaco-worker';
import { destroyLang, registerLang } from './tool';
interface typeProps {
readOnly: boolean;
value?: string;
onChange?: (value?: string) => void;
options?: Record<string, unknown>;
}
const MonacoCodeEditor = (props: typeProps) => {
const { readOnly, value, onChange } = props;
const monacoEditor = useRef<any>();
const refCodeContainer = useRef();
const lastEditorValue = useRef<string>(value);
// [https://github.com/microsoft/monaco-editor/issues/3358]
const setMonacoEditorValue = (
editor: monaco.editor.ICodeEditor | monaco.editor.IStandaloneCodeEditor,
val: string,
) => {
if (!editor) {
return;
}
editor.updateOptions({ readOnly: false });
editor.pushUndoStop();
editor.executeEdits('name-of-edit', [
{
// @ts-ignore
range: editor.getModel()?.getFullModelRange(), // full range
text: val || '', // target value here
},
]);
editor.pushUndoStop();
editor.updateOptions({ readOnly: readOnly });
};
const init = () => {
if (monacoEditor.current) {
monacoEditor.current?.dispose();
}
monacoEditor.current = monaco.editor.create(refCodeContainer.current, {
value: value,
language: 'mysql',
automaticLayout: true, // 自动布局
theme: 'vs',
readOnly: readOnly,
scrollBeyondLastLine: false,
foldingStrategy: 'indentation', // 代码可分小段折叠
overviewRulerBorder: false, // 不要滚动条的边框
cursorStyle: 'line', // 光标样式
fixedOverflowWidgets: true, // hover提示框不被遮挡
scrollbar: {
useShadows: true,
verticalHasArrows: true,
horizontalHasArrows: true,
vertical: 'visible',
horizontal: 'visible',
verticalScrollbarSize: 10,
horizontalScrollbarSize: 10,
arrowSize: 10,
},
minimap: {
enabled: true,
},
});
registerLang(monaco);
monacoEditor.current?.onDidChangeCursorPosition(() => {
const val = monacoEditor.current?.getValue();
if (val !== lastEditorValue.current) {
onChange?.(val || '');
lastEditorValue.current = val || '';
}
});
};
useEffect(() => {
init();
return () => {
monacoEditor.current?.dispose();
destroyLang();
};
}, []);
useEffect(() => {
if (monacoEditor.current) {
setMonacoEditorValue(monacoEditor.current, value || '');
}
}, [value]);
useEffect(() => {
monacoEditor.current?.updateOptions({ readOnly: !!readOnly });
}, [readOnly]);
useEffect(() => {
setMonacoEditorValue(monacoEditor.current, value);
}, [value]);
return (
<div
style={{
border: '1px solid gray',
height: '20vh',
}}
>
<div ref={refCodeContainer} style={{ height: '100%' }}></div>
</div>
);
};
export default MonacoCodeEditor;
- 实现提示词注册逻辑
import { language as mysqlLanguage } from 'monaco-sql-languages/esm/languages/mysql/mysql.js';
const sqlLanguagesList = ['mysql'];
type typeSql = 'mysql';
// 语言数据对象
export const languageMap = {
mysql: mysqlLanguage,
};
// 存放已经注册的语言实例
let registedInstanceList: string[] = [];
// 语言注册实例
const languageInsMap: Record<string, any[]> = {};
function registerLangByType(monaco: any, sqlType: typeSql) {
return monaco.languages.registerCompletionItemProvider(sqlType, {
// 触发提示的字符
triggerCharacters: ['.', ' '],
provideCompletionItems: (model: any, position: any) => {
const suggestions: any[] = [];
const { lineNumber, column } = position;
const textBeforePointer = model.getValueInRange({
startLineNumber: lineNumber,
startColumn: 0,
endLineNumber: lineNumber,
endColumn: column,
});
// 可以根据用户的代码进行特殊推荐
const textAllBefore = model.getValue();
const contents = textBeforePointer.trim().split(/\s+/);
const lastContents = contents[contents?.length - 1]; // 获取最后一段非空字符串
// 注入SQL默认推荐
const sqlConfigKey = ['builtinFunctions', 'keywords', 'operators'];
sqlConfigKey.forEach((key) => {
const curLanguage = languageMap[sqlType];
curLanguage[key].forEach((sql: string) => {
suggestions.push({
label: sql, // 显示的提示内容;默认情况下,这也是选择完成时插入的文本。
insertText: sql, // 选择此完成时应插入到文档中的字符串或片段
kind: monaco.languages.CompletionItemKind.Function, // 此完成项的种类。编辑器根据图标的种类选择图标。
});
});
});
return {
suggestions: [...new Set([...suggestions])],
};
},
});
}
export const registerLang = (monaco: any, keyModule = 'defaultModule') => {
if (!languageInsMap[keyModule]) {
languageInsMap[keyModule] = [];
}
sqlLanguagesList.forEach((sqlType: typeSql) => {
if (registedInstanceList.includes(sqlType)) {
return;
}
const registerIns = registerLangByType(monaco, sqlType);
languageInsMap[keyModule].push(registerIns);
registedInstanceList.push(sqlType);
});
};
export const destroyLang = () => {
Object.keys(languageInsMap).forEach((key: string) => {
// 销毁函数
languageInsMap[key]?.forEach((ins) => {
ins?.dispose();
});
});
registedInstanceList = [];
};
- webpack配置插件
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
chainWebpack: (memo) => {
memo.plugin('monaco-editor-webpack-plugin').use(MonacoWebpackPlugin, [
// 按需配置
// { languages: ['javascript'] },
]);
return memo;
}
这样我们就实现了一个编辑器:
todo: 编辑器插件逻辑,键盘逻辑
转载自:https://juejin.cn/post/7403547816915910682