网站搜索

Visual Studio Code - vscode 与 monaco-editor 之间不同的 TypeScript 推断管理


我刚刚写了一个复杂的函数,它接受三个参数:name、types 和 method。这个函数将一个方法存储在一个存储中。它从第二个参数推断第三个参数的方法的返回类型。

addMethod.d.ts

interface SimplifiedTypeMap {
  string: string;
  number: number;
  boolean: boolean;
}

type GlobalMethodAdd = <T extends keyof SimplifiedTypeMap>(
  name: string,
  types: T[],
  method: () => SimplifiedTypeMap[T]
) => void;

interface MethodStore {
  [name: string]: {
    types: (keyof SimplifiedTypeMap)[];
    method: () => SimplifiedTypeMap[keyof SimplifiedTypeMap];
  };
}

多亏了 TypeScript 引擎,最后一个参数(方法)的返回类型是根据第二个参数(类型)中的项目推断出来的,并且它强制函数的使用者编写具有特定返回类型的方法

addMethod.ts

import { random } from "lodash-es";

export const methodStorage: MethodStore = {};

const addMethod: GlobalMethodAdd = (name, types, method) => {
  methodStorage[name] = { types, method };
};

addMethod("test", ["string", "number"], () =>
  random(1, true) > 0.5 ? "abcd" : 1234
);

当我在 Visual-Studio Code 或 Codesandbox 上使用 addMethod 函数时,第三个参数的返回类型是众所周知的,但在 monaco-editor 上不是:

视觉工作室代码

codesandbox 编辑器

(失败的)Monaco 编辑器

这是我在 codesandbox 中的示例

=========================================================================

编辑 我发现正是使用 noLib 编译器选项才导致了这种情况。

reactMonaco.languages.typescript.typescriptDefaults.setCompilerOptions({
  target: reactMonaco.languages.typescript.ScriptTarget.ES5,
  noLib: true,
  allowNonTsExtensions: true,
  checkJs: true,
});

有没有办法在保持类型推断正常工作的同时避免 ES5 库自动补全?

答案#1

摘要

在 Monaco Editor 实现合适的解决方案之前,只需使用 monaco.languages.typescript.typescriptDefaults.addExtraLib(YOUR_CUSTOM_LIBRARY,"defaultLib:lib.es6.d.ts"),完全不必理会 setCompilerOptions。你可以通过从 https://github.com/microsoft/TypeScript/tree/master/lib 中组合你想要的部分来创建你自己的自定义库。

长版本

我也在使用 Monaco 编辑器,并且遇到了同样的问题。在仔细查看 Monaco 的源代码后,我认为我找到了一个解决方案(或变通方法?),不需要修改 Monaco 编辑器的源代码。

首先,让我们看看当前的行为。Monaco 编辑器使用所谓的 LanguageServiceHost 来实现与 TypeScript 相关的功能,另请参见使用语言服务 API :

它(语言主机)抽象了语言服务与外部世界之间的所有交互。语言服务主机将输入文件的管理、监控和维护工作委托给主机。

Monaco 编辑器的 TypeScript 语言宿主是 microsoft/monaco-typescript 仓库的一部分,可以在 tsWorker.ts 中找到。

现在,如果我们查看源代码,会发现 Monaco 编辑器目前只支持两个库文件:

  • lib.d.ts(源代码)
  • lib.es6.d.ts(源代码)

当编译器选项 target 设置为 ES5 时,会使用 lib.d.ts,否则会使用 lib.es6.d.ts。更多的调试显示,无论我们将 lib 选项设置为什么,这两个文件中的一个都会被用作库。正如你提到的,我们可以使用 noLib 选项,但这会导致其他副作用,比如类型推断不再正常工作。

所以现在我的想法是,大多数情况下,你不想要原有的库,而是想要一个不同的库。DOM 特定的部分(我猜你不想要)在 lib.dom.d.ts 中,它被 lib.d.ts 所包含。通常,至少添加 lib.es5.d.ts 是一个好主意,它包含 JavaScript 基础类型,例如 Object、Function 或 Number;或者可能是 lib.es2015.d.ts,它包含 Symbol、新的数组方法等等。那么问题变成了:我们如何让 Monaco Editor 使用自定义的——可能是空的——库?

在 monaco-typescript 中,库的源代码被硬编码在 lib.ts 文件中。所以如果我们使用像 webpack 这样的打包工具,我们可以直接用自己的版本替换该文件,该版本导出我们想使用的库的常量 lib_dts 或 lib_es6_dts。不过,这个选项并不总是可行,而且修改库的源代码无论如何都不是一个好主意。

但是,如果我们再次查看 tsWorker.ts 以及它如何获取给定文件名的内容,另一种解决方案(变通办法?)就出现了:如果我们添加一个具有特定名称的额外库,这个额外库将会被使用,而不是内置库。

  • 对于 lib.d.ts,使用文件名 defaultLib:lib.d.ts
  • 对于 lib.es6.d.ts,使用文件名 defaultLib:lib.es6.d.ts

总结一下,这里有一个可以在 Monaco Editor playground 上运行的你的代码示例。请注意,这将目标设置为 ES5(如你的示例所示),但我们也可以完全不修改编译器选项,只在添加额外库时使用 lib.es6.d.ts。

const code = `
interface SimplifiedTypeMap {
    string: string;
    number: number;
    boolean: boolean;
}

type GlobalMethodAdd = <T extends keyof SimplifiedTypeMap>(
    name: string,
    types: T[],
    method: () => SimplifiedTypeMap[T]
) => void;

interface MethodStore {
    [name: string]: {
        types: (keyof SimplifiedTypeMap)[];
        method: () => SimplifiedTypeMap[keyof SimplifiedTypeMap];
    };
}

export const methodStorage: MethodStore = {};

const addMethod: GlobalMethodAdd = (name, types, method) => {
    methodStorage[name] = { types, method };
};

// Try hovering over addMethod, type inferrence works correctly
addMethod("test", ["string", "number"], () =>
    1 > 0.5 ? "abcd" : 1234
);

// No DOM library loaded, so this gives an error
new HTMLAnchorElement();    
`;

async function setup() {
    const response = await fetch("https://raw.githubusercontent.com/microsoft/TypeScript/master/lib/lib.es5.d.ts");
    const libes5 = await response.text();

    monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
        allowNonTsExtensions: true,
        target: monaco.languages.typescript.ScriptTarget.ES5,
    });

    // Use "lib.custom.d.ts" for ES5, and "defaultLib:lib.es6.d.ts" for ESNext (the default)
    monaco.languages.typescript.typescriptDefaults.addExtraLib(
        libes5,
        "defaultLib:lib.d.ts"
    );

    monaco.editor.create(document.getElementById("container"), {
        value: code,
        language: "typescript"
    });
}

setup();

此外,这里是你 Code Sandbox 示例的修改版本。