React版的OpenDSL Editor

595 阅读5分钟

背景介绍

接着上一篇:如何为小众编程语言开发一个在线IDE? 里面规划了React的封装,同时交流群里面也提到了这个需求,就优先开发这部分,直接先看效果图,在线访问:[editor.321zou.com/]

image.png

使用方式

//导入npm包
import OpenDSLEditor from 'opendsl-editor-react';

//使用
<OpenDSLEditor
    language="aviatorscript"
    height="500"
    width="100%"
    onChange={onChange}
    value={code}
/>

技术实现

在网上搜索react版本的monaco-editor,找到了一个:

github.com/react-monac…

在此基础上封装,封装相对简单,难的是怎么和OpenDSL整合以便后续扩展,和React的启动部分。

多语言支持

为了支持多语言,同时与monaco耦合降到最低,添加了DSLLoader类,作为monaco和DSL直接的桥梁,实现如下:

OpenDSLEditor.js:直接集成MonacoEditor,并且只依赖DSLLoader

import MonacoEditor from "react-monaco-editor";
import DSLLoader from "./DSLLoader";

class OpenDSLEditor extends MonacoEditor {

    constructor(props) {
        super(props);
        new DSLLoader().load();
    }
}

export default OpenDSLEditor;

DSLLoader.js实现如下:在里面处理多语言的加载,为了方便,直接new处理就执行完内部逻辑,在衔接处保持简单,这样易于集成

import AviatorScript from "./dsl/aviatorscript/AviatorScript";

class DSLLoader {

    constructor() {}

    load() {
        new AviatorScript();
    }

}

export default DSLLoader;

多语言支持:使用目录区分不同的语言,看import的路径部分

代码如下:设置完语言id之后,就是配置多种功能特性,不同特性使用js模块拆分

import { monaco } from "react-monaco-editor";
import monarch from "./monarch";
import suggestions from "./suggestions";

class AviatorScript {

    constructor() {
        this.languageId = "aviatorscript";
        monaco.languages.register({ id: this.languageId });//设置语言id
        this.configHighlight(); //配置高亮
        this.configSuggestions(); //配置自动提示补全
    }

    getLanguageId() {
        return this.languageId;
    }

    configHighlight() {
        monaco.languages.setMonarchTokensProvider(this.languageId, monarch);
    }

    configSuggestions() {

        monaco.languages.registerCompletionItemProvider(this.languageId, {
            provideCompletionItems: () => {
                suggestions.forEach(suggestion => {
                    delete suggestion.range;
                });
                return {
                    suggestions
                };
            }
        })
    }
}
export default AviatorScript;

以上就是编辑器封装部分,最耗时的是后面的集成使用部分。

要发布出去对外使用,前提要上传到npm中,幸好之前一点基础,但是长时间不要用又遇到几个坑。

最开始参考react-monaco-editor的example,很快弄出来一个demo,执行都正常,里面使用webpack方式启动服务,和标准的react项目有差异,在改造成标准React后,问题就出现,自动提示补全功能不起作用了,语法高亮还行,然后就开始漫长的问题排查过程:

  • 猜测是模块拆分导致,然后去掉模块化,直接赋值,也是不行(排除)
  • 两种技术风格不一样导致,然后网上搜索,多次尝试,也是不行
  • 分析两种方式在浏览器中的加载内容,核心部分大同小异(有lambda和function写法差异,修改后也无效)

多次尝试依然没有结果,就先用最开始那种行的方式,先发布一版。

第二天后,由于服务器卡死原因(后续补充),又去仔细阅读react-monaco-editor,发现里面有React说明,按照说明重新做一遍,果然可以,关键原因就是与monaco的ESM有关(彻底理解ESM、AMD、CMD之间的差异,对后端来说还是有些困难 )。

最开始的demo是写在一个项目里,用后缀差异的方式提供两类配置文件,然后在README里面添加使用说明,后面发现这种不友好,直接拆分为两个demo项目,并且删除两种方式中不相关的文件,这样就一目了然,并且大大简化README里面的内容,这个也算一种优化。

意外部分

在整个开发过程中遇到了两个意外:

npm无法发布

npm的Two Factor Authentication,第一次尝试,按照官方文档操作一遍,最后发现npm发布时一直提示没有权限

npm ERR! code ENEEDAUTH
npm ERR! need auth This command requires you to be logged in.
npm ERR! need auth You need to authorize this machine using `npm adduser`

然后就开始认为是2FA导致,排查加尝试耗时很久,最后才想起来,使用淘宝的npm加速,去掉加速之后,就正常了。

感悟:尝试新东西和添加新功能不要同时进行,否则很难排查定位。

服务器卡死

本地好好的,一发布到服务器上面,启动,看见webpack一大堆加载内容,然后服务器就卡死了,然后登录都不行,最后只能重启服务器,来来回回折腾好几次,还是出现卡死,最后发现是内存占用过高,webpack方式占用居然500+M,服务器只有2G内存,就占用了四分之一,需要优化,这是使用webpack的方式,然后尝试使用React的方式,可是结果一样还是占用500+M内存,然后就想升级服务器。由于使用阿里云的轻量服务器,一看升级步骤,还挺麻烦,而且还要花银子,就犹豫了一下。

后面想小程序的云开发里面的有静态资源服务,然后开通,把本地资源打包成静态资源,部署到里面,居然可以,操作还很方便,唯一不好的就是自定义域名需要提供证书,这时想起域名支持URL隐式跳转,刚好满足需求:使用自定义域名,又使用开发云的静态资源服务,减少了服务器的内存。

感悟:出现意外、出现坑不一定是坏事,极有可能是好事,因为出现意外,就被迫想其他方案,这个时候很有可能诞生新方案、创新型方案,而且新方案成型之后,再融合新老方案的各自的优点,又会产生更新的方案,就这样在折腾中不断改良、精简、完善。