You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TypeScript Server插件中注入全局虚拟类型定义?

如何在TypeScript Server插件中注入全局虚拟类型定义?

我之前也踩过类似的坑,你的核心思路完全正确——通过补丁LanguageServiceHost的方法注入虚拟.d.ts文件,但可能有些关键细节没处理到位,导致TypeScript Server无法正确识别虚拟文件。下面是经过验证的完整实现方案,你可以直接参考:

核心问题分析

你的代码里有几个容易忽略的关键点:

  • 没有正确创建ScriptSnapshot实例(我注意到你代码里的virtualSnapshot变量未定义)
  • 部分LanguageServiceHost的核心方法没有补丁(比如getScriptVersion、getScriptKind)
  • 虚拟文件的路径处理可能和TypeScript的文件系统校验逻辑不兼容

完整工作代码示例

import path from "path";
import type * as ts from "typescript/lib/tsserverlibrary";

const init: ts.server.PluginModuleFactory = (modules) => {
  const ts = modules.typescript;

  function create(info: ts.server.PluginCreateInfo): ts.LanguageService {
    const logger = info.project.projectService.logger;
    logger.info("Test TSServer Plugin: 插件加载成功!");

    // 定义虚拟文件信息:用特殊前缀命名避免和真实文件冲突
    const virtualFileName = path.join(
      info.project.getCurrentDirectory(),
      "__virtual-global-types.d.ts"
    );
    // 全局声明的标准写法:必须加export {},让文件被识别为模块
    const virtualFileContent = `
declare global {
  interface Person {
    name: string;
    age: number;
  }
}
export {};
`;
    // 创建虚拟文件的ScriptSnapshot实例
    const virtualSnapshot = ts.ScriptSnapshot.fromString(virtualFileContent);
    // 给虚拟文件分配固定版本号(动态版本可根据需求调整)
    const virtualFileVersion = "1.0.0";

    const isVirtualFile = (fileName: string) => fileName === virtualFileName;

    // 1. 补丁getScriptFileNames:将虚拟文件加入项目文件列表
    const originalGetScriptFileNames = info.languageServiceHost.getScriptFileNames.bind(info.languageServiceHost);
    info.languageServiceHost.getScriptFileNames = () => {
      const files = originalGetScriptFileNames();
      return files.includes(virtualFileName) ? files : [...files, virtualFileName];
    };

    // 2. 补丁getScriptSnapshot:返回虚拟文件的内容快照
    const originalGetScriptSnapshot = info.languageServiceHost.getScriptSnapshot.bind(info.languageServiceHost);
    info.languageServiceHost.getScriptSnapshot = (fileName) => {
      if (isVirtualFile(fileName)) return virtualSnapshot;
      return originalGetScriptSnapshot(fileName);
    };

    // 3. 补丁getScriptVersion:给虚拟文件提供版本跟踪
    const originalGetScriptVersion = info.languageServiceHost.getScriptVersion.bind(info.languageServiceHost);
    info.languageServiceHost.getScriptVersion = (fileName) => {
      if (isVirtualFile(fileName)) return virtualFileVersion;
      return originalGetScriptVersion(fileName);
    };

    // 4. 补丁getScriptKind:标记虚拟文件为TypeScript声明文件
    const originalGetScriptKind = info.languageServiceHost.getScriptKind?.bind(info.languageServiceHost);
    info.languageServiceHost.getScriptKind = (fileName) => {
      if (isVirtualFile(fileName)) return ts.ScriptKind.TS;
      return originalGetScriptKind?.(fileName) ?? ts.ScriptKind.Unknown;
    };

    // 5. 补丁getCanonicalFileName:确保文件名格式一致性(避免路径大小写/格式冲突)
    const originalGetCanonicalFileName = info.languageServiceHost.getCanonicalFileName?.bind(info.languageServiceHost);
    info.languageServiceHost.getCanonicalFileName = (fileName) => {
      if (isVirtualFile(fileName)) return ts.sys.resolvePath(fileName);
      return originalGetCanonicalFileName?.(fileName) ?? fileName;
    };

    return info.languageService;
  }

  return { create };
};

export default init;

关键注意事项

  1. 虚拟文件命名:建议用__前缀命名虚拟文件,避免和项目中的真实文件重名
  2. 全局声明格式:必须添加export {},否则declare global块不会被TypeScript识别为全局声明
  3. 全方法补丁:TypeScript Server会通过多个方法校验文件合法性,缺少任何一个都可能导致虚拟文件被判定为无效
  4. 版本兼容性:确保插件依赖的TypeScript版本和项目使用的版本一致,避免因API差异导致的报错

验证方式

重启TypeScript Server后,在项目任意.ts文件中直接编写代码:

const person: Person = { name: "John", age: 18 }; // 应正确识别Person类型,无类型报错

如果仍有问题,可以通过logger.info打印各个补丁方法的调用情况,排查哪个环节未正确处理虚拟文件。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:42:58