背景
本人前段时间开发了一个 babel
插件开发练习平台,项目地址:babel-plugin-demo
其中使用到了 monaco-editor
这个库,这个库集成了 vscode 编辑器的功能,可以在网页上编写代码,但在编写插件的时候由于缺少类型提示,导致要一边查看文档有哪些 API
,一边写插件,效率极低,因此想着能不能在 monaco-editor
中添加第三方库的类型提示,以提高开发效率。
具体是通过 @typescript/ata
这个库来实现的,它可以帮助我们获取 第三方库的 .d.ts
文件,然后在 monaco-editor
中添加进去,这样就可以有第三方库的类型提示了。
废话不多说,下面具体演示一下。
具体实现
首先初始化一个项目,这一部分我们不过多赘述,用 vite
就可以。
这里我们以 react
项目为例,所以这里我们安装 @monaco-editor/react
这个库,它是基于 monaco-editor
封装的 React
组件,使用起来大同小异。
zsh
pnpm i @monaco-editor/react
然后初始化一个 monaco-editor
编辑器, monaco-editor
的使用如果不会的可以去看看官方文档,不过官网有点不太友好,所以大家可以多去查查资料:
jsx
import { FC } from 'react';
import Editor, { EditorProps } from '@monaco-editor/react';
const App: FC = () => {
// 编辑器内容
const code = `let a: number = 1;`;
// 创建成功的回调 接受两个参数 editor 和 monaco
const onMount: EditorProps['onMount'] = (editor, monaco) => {
// 这里可以一写初始化编辑器的操作
};
return (
<div style={{ height: '100%' }}>
<Editor
language="typescript" // 编辑器语言
path="plugin.ts" // 编辑器文件名
value={code} // 编辑器内容
onMount={onMount} // 创建成功的回调
></Editor>
</div>
);
};
export default App;
当我们创建成功后,可以在 onMount
回调中进行一些初始化操作,这个回调会接受两个参数:
第一个参数是跟编辑器配置相关。
第二个参数是 monaco
对象,我们可以用它身上的方法 monaco.languages.typescript.typescriptDefaults.addExtraLib
方法为第三方库添加 TS
类型提示。
那么我们如何得到第三方库的 .d.ts
文件呢?
这就需要用到 @typescript/ata
这个库了
安装这个库:
zsh
pnpm i @typescript/ata
首先创建一个 ata
函数:
ts
import { setupTypeAcquisition } from '@typescript/ata';
import typescript from 'typescript';
const ata = setupTypeAcquisition({
projectName: 'ata-test', // 项目名称
typescript: typescript, // 导入 typescript 对象
logger: console, // 日志输出
delegate: {
receivedFile: (code: string, path: string) => {
// 这里可以获取到第三方库的 .d.ts 代码和路径
},
started: () => {
console.log('ATA start'); // 开始执行
},
progress: (downloaded: number, total: number) => {
console.log(`Got ${downloaded} out of ${total}`); // 下载进度
},
finished: (vfs) => {
console.log('ATA done', vfs); // 完成执行
},
},
});
我们重点关注 delegate
对象,它有四个方法:
receivedFile(code: string, path: string)
:接收到文件和代码时执行的操作,我们可以在这里把 获取到的.d.ts
文件添加给monaco
对象。started()
:开始执行时执行的操作progress(downloaded: number, total: number)
:下载进度时执行的操作finished(vfs)
:完成执行时执行的操作,这里可以获取到vfs
对象,它是一个Map
对象,里面存放着所有的文件路径和代码。
接下来我们就可以在 receivedFile
方法中执行 monaco
的 addExtraLib
方法了:
tsx
const onMount: EditorProps['onMount'] = (editor, monaco) => {
const ata = setupTypeAcquisition({
projectName: 'ata-test',
typescript: typescript,
logger: console,
delegate: {
receivedFile: (code: string, path: string) => {
// 这里添加第三方库的 .d.ts 代码和路径,注意这里的 path 要加上 `file://${path}` 前缀
monaco.languages.typescript.typescriptDefaults.addExtraLib(
code,
`file://${path}`,
);
},
started: () => {
console.log('ATA start');
},
progress: (downloaded: number, total: number) => {
console.log(`Got ${downloaded} out of ${total}`); // 下载进度
},
finished: (vfs) => {
console.log('ATA done', vfs); /
},
},
});
};
有了 ata
函数之后,如何知道该去下载哪些第三方库的 .d.ts
文件呢?
其实是通过调用 ata
函数,并给他传入编写的代码,他会去解析里面的 import
语句,然后去下载对应的 .d.ts
文件。
比如 我们要下载 axios
的 .d.ts
文件,我们可以这样写:
tsx
ata(`import axios from 'axios';`);
这样整个链路就走通了。
但这样写起来并不好,我们可以把创建 ata
的过程抽成一个函数,通过传递一个回调的方式执行为 monaco
对象添加 .d.ts
文件的操作:
ts
// ata.ts
import { setupTypeAcquisition } from '@typescript/ata';
import typescript from 'typescript';
type AddExtraLibs = (code: string, path: string) => void;
// 接受一个函数
export const createAta = (addExtraLibs: AddExtraLibs) => {
const ata = setupTypeAcquisition({
projectName: 'Babel Plugin Playground',
typescript: typescript,
logger: console,
delegate: {
receivedFile(code, path) {
addExtraLibs(code, path);
},
},
});
// 返回 ata 函数
return ata;
};
在 App
组件中,我们可以这样写:
tsx
import { FC } from 'react';
import Editor, { EditorProps } from '@monaco-editor/react';
// 导入 ata 函数
import { createAta } from './ata';
const App: FC = () => {
const code = `let a: number = 1;`;
const onMount: EditorProps['onMount'] = (editor, monaco) => {
// 传递一个回调给 ata 函数,在这里为 monaco 对象添加 .d.ts 文件
const ata = createAta((code, path) => {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
code,
`file://${path}`,
);
});
ata('import axios from "axios"');
};
return (
<div style={{ height: '100%' }}>
<Editor
language="typescript" // 编辑器语言
path="plugin.ts" // 编辑器文件名
value={code} // 编辑器内容
onMount={onMount} // 创建成功的回调
></Editor>
</div>
);
};
export default App;
这样就完成在monaco-editor
中为第三方库添加 TS
类型提示了。
当然,我们还可以通过监听 monaco
的代码变化来动态的下载第三方库的 .d.ts
文件,这样就可以实现在线编辑,实时获取 .d.ts
文件,就能给每个引进来的库添加 TS
类型提示了。
tsx
...
const onMount: EditorProps['onMount'] = (editor, monaco) => {
...
// 监听编辑器内容变化 触发自动补全
editor.onDidChangeModelContent(() => {
ata(editor.getValue());
});
// 首次渲染时触发自动补全
ata(editor.getValue());
...
};
...
这样就可以实现在线编辑,并实时得到类型提示,开发体验非常棒。
完整代码:
tsx
// App.tsx
import { FC } from 'react';
import Editor, { EditorProps } from '@monaco-editor/react';
// 导入 ata 函数
import { createAta } from './ata';
const App: FC = () => {
const code = `let a: number = 1;`;
const onMount: EditorProps['onMount'] = (editor, monaco) => {
// 传递一个回调给 ata 函数,在这里为 monaco 对象添加 .d.ts 文件
const ata = createAta((code, path) => {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
code,
`file://${path}`,
);
});
// 监听编辑器内容变化 触发自动补全
editor.onDidChangeModelContent(() => {
ata(editor.getValue());
});
// 首次渲染时触发自动补全
ata(editor.getValue());
};
return (
<div style={{ height: '100%' }}>
<Editor
language="typescript" // 编辑器语言
path="plugin.ts" // 编辑器文件名
value={code} // 编辑器内容
onMount={onMount} // 创建成功的回调
></Editor>
</div>
);
};
export default App;
ts
// ata.ts
import { setupTypeAcquisition } from '@typescript/ata';
import typescript from 'typescript';
type AddExtraLibs = (code: string, path: string) => void;
export const createAta = (addExtraLibs: AddExtraLibs) => {
const ata = setupTypeAcquisition({
projectName: 'Babel Plugin Playground',
typescript: typescript,
logger: console,
delegate: {
receivedFile(code, path) {
addExtraLibs(code, path);
},
},
});
return ata;
};