不到100行代码,让monaco也可以自动补全、导入本地定义!

in 前后端开发 with 0 comment

VSCode in vList~

首先是效果,可以看到,NJS完美解析和补全
作为对照,aList惨败

aList的monaco无法补全

实现思路

我们尝试了很多方法,第一个就是研究官方函数,发现没有可注入文件系统的地方
为了证明这条路的确不行,我问了AI,AI是这样告诉我的

要在 Monaco Editor 中实现文件系统的读写功能,并支持导入和类型分析,通常需要集成一个语言服务器(Language Server)来提供这些功能。Monaco Editor 本身并不直接提供这些功能,但可以通过集成语言服务器协议(LSP)来实现。
在使用 Monaco Editor 时,如果你需要为其提供文件系统的读写功能,通常需要通过一些中间层来实现,因为 Monaco Editor 本身并不直接提供文件系统的读写接口

然后我在网上找了很久,找到了这个项目 monaco-vscode-api

当时我可激动了,妈妈呀,这项目通过打补丁直接使monaco摇身一变成为了VSCode

激动人心!这不就是VSCode吗

但是缺陷也很大

于是我们决定另辟蹊径,首要的是解决导入问题

导入错误

基于语法分析

我们发现,导入大抵只有6种

那么我们的想法是,分析语法预加载到内存fs(inmemory://)!

const preg_import = /import\s+(?:.+\s+from\s+)?['"]([^'"]+\.(?:js|ts|tsx|jsx))['"]/g,
    preg_cjs_import = /(?:require|import)\(['"]([^'"]+\.(ts|js|tsx|jsx))['"]\)/g,
    ref_syntax = /\/\/\/\s*\<reference.+path=\"(.+\.ts)\".*\>[\r\n]+/g,

这三条正则匹配了6种情况,同时暴露第一个子匹配为文件路径
为了加载到内存中,我们需要循环匹配import并使用monaco.languages.typescript.typescriptDefaults.addExtraLib导入这个文件

for(const match of [
    ...code.matchAll(preg_import),
    ...code.matchAll(preg_cjs_import),
    ...code.matchAll(ref_syntax)
]){
    // 这里stat是为了获取直链,用URL作路径分析
    const file = await FS.stat(new URL(match[1], pfile.url).pathname);
    if(file.type == 'dir') continue;
    // analysis_import返回文件内容,降低开销
    const content = await analysis_import(file);
    // 导入到Monaco中
    languages.typescript.typescriptDefaults.addExtraLib(content, 'inmemory:' + file.path);
}

为了完美的体验,我们需要定义tsconfig,可选但是建议加上

// 设置tsconfig
languages.typescript.typescriptDefaults.setCompilerOptions({
    target: languages.typescript.ScriptTarget.Latest,
    allowNonTsExtensions: true,
    moduleResolution: languages.typescript.ModuleResolutionKind.NodeJs,
    module: languages.typescript.ModuleKind.ESNext,
    noEmit: true,
    allowJs: false,    // 不是false似乎有把TS当作JS的问题
    typeRoots: ["node_modules/@types"]
});

完成!相比较于补丁项目,是不是很简单呢?集成度也很好呢!

Responses