Monaco Editor快速入门

14,015 阅读3分钟

目标

  • Monaco Editor基础用法
  • 配置自定义内容补全

概述

Monaco EditorVS Code 底层的代码编辑器,开源协议是MIT,支持Edge、 Chrome、Firefox、Safari和Opera浏览器,但是不支持移动端浏览器或移动端框架

本文中涉及到的Monaco Editor版本号是0.27.0

安装

npm install monaco-editor@0.27.0

获取语言列表

monaco.languages.getLanguages()

返回一个语言列表数组,需要拿到的是其中的id字段

浏览器端创建指定语言编辑环境

<html>

<head>
    <style>
        #editor {
            width: 700px;
            height: 700px;
        }
    </style>
</head>

<body>
    <div id="editor"></div>
    <script>
        var require = {
            paths: {
                'vs': '../min/vs'
            }
        };
    </script>
    <script src="../min/vs/loader.js"></script>
    <script src="../min/vs/editor/editor.main.nls.js"></script>
    <script src="../min/vs/editor/editor.main.js"></script>
    <script>
        require(['vs/editor/editor.main'], function () {
            // "javascript"
            const editor = monaco.editor.create(document.getElementById('editor'), {
                model: null,
                minimap: {
                    enabled: false
                }
            });
            const newModel = monaco.editor.createModel(
                    `alert('hello');\nalert('hello');\nalert('hello');\nalert('hello');\nalert('hello')`,
                    "javascript"
            );
            editor.setModel(newModel);
        })
    </script>
</body>

</html>

monaco.editor.createModel方法的第二个参数就是指定语言,这个语言具体取值可以通过monaco.languages.getLanguages()获取到

ESM端创建

import React from 'react';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';
import 'monaco-editor/esm/vs/editor/contrib/find/findController.js';
import { defaultScript } from '../config/config';

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
        };
    }
    componentDidMount() {
        monaco.editor.create(document.getElementById('monaco'), {
            value: defaultScript,
            language: 'javascript'
        });
    }
    render() {
        return (
            <div id='monaco' />
        );
    }
}

export default App;

import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';用于引入语言包,import 'monaco-editor/esm/vs/editor/contrib/find/findController.js';用于引入搜索控件。当然上述功能可以用import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';替换,但缺点是打包体积过大

但是该方式存在问题,鼠标右键功能缺失,需要导入相应的web worker,但该部分目前根据官网的示例还未走通

vite环境下的使用

import React, { useEffect } from 'react';
import * as monaco from 'monaco-editor';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';

self.MonacoEnvironment = {
    getWorker(_, label) {
        if (label === 'json') {
            return new jsonWorker();
        }
        if (label === 'css' || label === 'scss' || label === 'less') {
            return new cssWorker();
        }
        if (label === 'html' || label === 'handlebars' || label === 'razor') {
            return new htmlWorker();
        }
        if (label === 'typescript' || label === 'javascript') {
            return new tsWorker();
        }
        return new editorWorker();
    },
};

export default props => {
    const [css] = useStyletron();
    let instance;
    useEffect(() => {
        instance = monaco.editor.create(document.getElementById(`monaco-editor-ser-${props.id}`), {
            value: 'var a=100;',
            language: 'javascript',
        });
    }, []);
    const showContent = () => {
        alert(instance.getValue());
    };
    return (
        <div>
            <div id={`monaco-editor-ser-${props.id}`} />
            <button onClick={showContent}>获取内容</button>
        </div>

    );
};

需要格外留意的是,还需要在vite的启动文件中用define字段配置process.env,否则会页面报错白屏

// 启动Vite
async function startVite() {
    const vite = await createServer({
        define: {
            'process.env': {}, // 重要
        },
        plugins: [
            ...
        ],
        server: {
            ...
        },
    });
    await vite.listen();
}

自动补全

vite版

import React, { useEffect } from 'react';
import { useStyletron } from 'styletron-react';
import * as monaco from 'monaco-editor';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';

self.MonacoEnvironment = {
    getWorker(_, label) {
        if (label === 'json') {
            return new jsonWorker();
        }
        if (label === 'css' || label === 'scss' || label === 'less') {
            return new cssWorker();
        }
        if (label === 'html' || label === 'handlebars' || label === 'razor') {
            return new htmlWorker();
        }
        if (label === 'typescript' || label === 'javascript') {
            return new tsWorker();
        }
        return new editorWorker();
    },
};

let instance;
export default props => {
    const [css] = useStyletron();
    const className = css({
        height: '400px',
        width: '400px',
        margin: '20px',
        border: '2px solid #aaa',
    });
    useEffect(() => {
        instance = monaco.editor.create(document.getElementById(`monaco-editor-ser-${props.id}`), {
            value: 'var a=100;',
            language: 'javascript',
        });
        monaco.languages.registerCompletionItemProvider('javascript', {
            provideCompletionItems(model, position) {
                const textUntilPosition = model.getValueInRange({
                    startLineNumber: position.lineNumber,
                    startColumn: 1,
                    endLineNumber: position.lineNumber,
                    endColumn: position.column,
                });
                const createComplete = _textUntilPosition => {
                    // 切割成数组
                    const words = _textUntilPosition.split(' ');
                    // 取当前输入值
                    const activeStr = words[words.length - 1];
                    return [{
                        label: 'wq',
                        kind: monaco.languages.CompletionItemKind.Keyword,
                        documentation: 'wq',
                        insertText: 'wq',
                    }];
                };
                const suggestions = createComplete(textUntilPosition);
                return {
                    suggestions: [{
                        label: 'jack',
                        kind: monaco.languages.CompletionItemKind.Keyword,
                        documentation: 'jack',
                        insertText: 'jack',
                    }, {
                        label: 'tom',
                        kind: monaco.languages.CompletionItemKind.Keyword,
                        documentation: 'tom',
                        insertText: 'tom',
                    }],
                };
            },
        });
    }, []);
    const showContent = () => {
        alert(instance.getValue());
    };
    return (
        <div>
            <div id={`monaco-editor-ser-${props.id}`} className={className} />
            <button onClick={showContent}>获取内容</button>
        </div>

    );
};

重点关注provideCompletionItems,返回一个对象,suggestions是个数组

获取当前编辑内容

/* eslint-disable no-restricted-globals,@typescript-eslint/no-unused-vars */

import React, { useEffect } from 'react';
import { useStyletron } from 'styletron-react';
import * as monaco from 'monaco-editor';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';

self.MonacoEnvironment = {
    getWorker(_, label) {
        if (label === 'json') {
            return new JsonWorker();
        }
        if (label === 'css' || label === 'scss' || label === 'less') {
            return new CssWorker();
        }
        if (label === 'html' || label === 'handlebars' || label === 'razor') {
            return new HtmlWorker();
        }
        if (label === 'typescript' || label === 'javascript') {
            return new TsWorker();
        }
        return new EditorWorker();
    },
};

let instance;
export default ({
    id, currentCode, readOnly, onChange
}) => {
    const [css] = useStyletron();
    const className = css({
        height: '500px',
        margin: '20px',
        border: '2px solid #fff',
    });
    useEffect(() => {
        instance = monaco.editor.create(document.getElementById(`monaco-editor-ser-${id}`), {
            value: currentCode,
            language: 'javascript',
        });
        instance.onDidChangeModelContent(() => {
            // 获取到当前编辑内容
            onChange(instance.getValue())
        })
    }, []);
    return <div id={`monaco-editor-ser-${id}`} className={className} />;
};

参考文章