demo 执行器 之 `@typescript/ata`

473 阅读4分钟

demo 执行器 之 @typescript/ata

本文是在看过 如何在网页实现 TypeScript 编辑器? 这篇文章后, 使用@typescript/atademo执行器改进, 达到满意效果后有感而发, 后面用ata代指@typescript/ata, ata的功能概括成一句话: 把代码作为字符串输入ata, 解析出代码里用到的, 从jsdeliver找到对应的类型定义文件并下载.

感兴趣的朋友可以加v :

89908ef9f8b5804362dffd54a692d60.jpg

ata也是typescript官网用来做类型定义文件*.d.ts的自动获取.

image.png

结合monaco-editor使用时, 在代码新增或变化时, 使用ata解析代码,下载类型定义文件, 并导入到ts的类型提示里.

起因

大概在17年秋~19年夏期间, 利用业余时间, 基于qt5 + nwjs做了一款软件, 可参考enterApp使用说明

21年开始陆陆续续的这款软件做了些改进:

  • 替换nw.js, 改成electron
  • js,tsdemo执行器codemirror改成monaco-editor
  • 浏览器功能更加强大: 特别是在页面,可同时开多个查找器,每个查找器用不同颜色标记查找内容.

但是, demo执行器有两个缺陷:

  • 不能在敲代码时, 对于任意使用的npm库, 自动导入它的类型定义文件
  • 不能在运行时, 对于任意使用的npm库, 自动加载它的js文件

使用ata一番改造后, 已克服上面的两个缺陷, 另外:

  • 通过本地服务代理esm链接的文件导入, 既避免频繁调用jsdeliver, 也解决了通过esm引入antd时的react多实例问题.

引入ata之前demo执行器的做法

缺点是不能自动化, 比如在使用前, 只对lodash库的*.d.ts类型提示文件和<script>链接等配置好放在本地, 只能对lodash可用, 对于其它在npm的库无效.

类型提示

把要用库的类型提示文件*.d.ts手动提取出来, 再通过monaco-editormonaco.languages.typescript.typescriptDefaults.setExtraLibs()接口导入, 主要逻辑如下:

//react 的 .d.ts
let fCntReact = fs.readFileSync(`react.development.d.ts`, "utf8");
fCntReact += fs.readFileSync(`react-dom.development.d.ts`, "utf8");

let deps = [
  //react 的类型提示配置
  {
    content: fCntReact,
    filePath: "file:///node_modules/@types/react.d.ts",
  },
];

function find_ds(dir) {
  var dobj = fs.readdirSync(dir);
  dobj.forEach((d) => {
    let p = dir + "/" + d;
    if (d.includes(".d.ts")) {
      ///antd 的类型提示文件导入
      deps.push({
        content: fs.readFileSync(p, "utf-8"),
        filePath: "file:///node_modules/@types/antd" + p,
      });
    } else if (d !== "style" && senipc.isDirectory(p)) {
      find_ds(p);
    }
  });
}
//antd 的类型提示配置
find_ds("./antd_dts/");

monaco.languages.typescript.typescriptDefaults.setExtraLibs(deps);

运行时库文件处理

试过的如下两种方式都能用:

  • react,react-dom,ant-design,dayjs等库文件,用他们在jsdeliverdist文件夹的打包产物, 通过script标签引入
  • 把用到的react,react-dom,ant-design,dayjs等库, 使用esbuild把它们打包成一个独立的文件, 通过script标签引入

引入atademo执行器的做法

自动引入对应库*.d.ts类型提示文件, 通过import导入远程的esm库文件, 连npm install都不需要, 直接用.

ata流程介绍

ata通过使用typescript.jspreProcessFile(codeStr), 解析出import的库名, 然后使用jsdeliver提供的接口,拿到库对应的*.d.ts并下载, 再通过monaco-editormonaco.languages.typescript.typescriptDefaults.addExtraLib();方法, 把库的类型定义添加到ts提示数据里.

结合monaco-editor来说, 增加一个文本内容变化的监听,解析import的库,并进行类型定义下载和导入, 如下:

export function createATA(
  onDownloadFile: (code: string, path: string) => void
) {
  const ata = setupTypeAcquisition({
    projectName: "demoAta",
    //通过`<script src="https://fastly.jsdelivr.net/npm/typescript">`引入到全局的`ts`
    typescript: global.ts,
    logger: console,
    delegate: {
      receivedFile: (code, path) => {
        onDownloadFile(code, path);
      },
    },
  });
  return ata;
}

const ata = createATA((code, path) => {
  monaco.languages.typescript.typescriptDefaults.addExtraLib(
    code,
    `file://${path}`
  );
});

editor.onDidChangeModelContenFt(() => {
  ata(vObj.mn_et.getValue());
});

主要流程截图展示如下:

  • 引入ata, 因为对ata做了适配,缓存文件下载,避免频繁调jsdeliver, 放在/@ui/lib/ata下面

image.png

image.png

  • 绑定回调onDownloadFile

image.png

  • ata内部解析代码里的import

image.png

下图,getReferencesForModule()找出import的库名

image.png

下图,ts.preProcessFile(codeStr)

image.png

下图,Fconfig.delegate.receivedFile?.(dtsCode, dts.vfsPath); 执行绑定的回调onDownloadFile

image.png

ata适配

  • 缓存下载的*.d.ts, 本地已有时从本地读取

    下图, 缓存后的效果

image.png

  • 缓存要importjs文件, import时用本地服务的链接,文件不存在时,通过本地服务从远程获取, 另外通过本地服务也可以解决部分问题,如下:

    下图, 用的是react@18.2.0, 与在编辑器代码导入的版本不同, 导致react多实例问题

image.png

下图, 从本地服务读取, 可以对返回的内容做处理, 保证导入的是同一版本的react链接

image.png

最终demo执行器效果

  • html

image.png

  • js/ts

image.png

  • 运行

image.png