如何为如下所示的 JavaScript 库编写 Typescript 定义文件?

Posted

技术标签:

【中文标题】如何为如下所示的 JavaScript 库编写 Typescript 定义文件?【英文标题】:How do I write a Typescript definition file for a JavaScript library written like the following? 【发布时间】:2019-04-16 20:29:50 【问题描述】:

我有一个旧的 javascript 库,我编写该库与 AMDCommonJS 一起使用。我忘记了影响我以这种方式编写库的原始来源,但我的 JavaScript 库是按照以下模式编写的。

(function(window) 
 'use strict';

 function getLib() 
  var mylib = ;
  mylib.getSomething = function() 
   return 'x';
  
  mylib.getCar = function(make, model, year) 
   return 
    make: make,
    model: model,
    year: year
   ;
  
  return mylib;
 

 if(typeof module === 'object' && module && typeof module.exports === 'object') 
    module.exports = getLib();
   else 
    if(typeof(mylib) === 'undefined') 
      window.mylib = getLib();
    

    if(typeof define === 'function' && define.amd) 
      define('mylib', [], getLib());
    
  
)(this);

如何为此 JavaScript 库编写类型定义 d.ts 文件?

我尝试如下写d.ts,但它不起作用。在我的 Angular 应用程序中,我输入了以下文件 node_modules/@types/mylib/index.d.ts

interface Car 
 make: string;
 model: string;
 year: number;


interface MyLib 
 getSomething(): string;
 getCar(make: string, model: string, year: number): Car;


declare module "mylib" 
 export let mylib: MyLib

在我的控制器中,我只是尝试导入库并将其调试到控制台,但得到undefined

import mylib from 'mylib'; //IDE doesn't complain, seems to find the type def

export class MyPage 
 constructor() 
  console.log(mylib); //undefined
  console.log(mylib.getCar('ford', 'f150', 2018)); //code won't reach here
 

请注意,JavaScript 包 mylib 不在 NPM 上,而是一个私有存储库,我已经将它(例如 npm install mylib --save)安装到 node_modules/mylib。我提到这一点是因为我不确定类型 def 文件应该放在哪里。

感谢任何帮助。

更新:

根据以下建议,我已将 d.ts 文件修改为如下所示,并将其放入 mylib/index.d.ts

declare module "mylib" 
    let mylib: MyLib;
    export = mylib; 

现在,我可以通过两种方式导入库。第一种方式。

import mylib from 'mylib';

第二种方式。

import * as mylib from 'mylib';

在 IDE VS Code 中,没有任何抱怨(没有红线暗示问题)。此外,我将tsconfig.json 修改为如下所示。


  "compileOnSave": false,
  "compilerOptions": 
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "commonjs",
    "esModuleInterop": true,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2015.promise",
      "es2017",
      "dom"
    ]
  

当我运行ng build 时,我看到了下面的消息。

node_modules/typescript/lib/lib.dom.d.ts(3268,88) 中的错误:错误 TS2344:类型“SVGElementTagNameMap[K]”不满足约束“节点”。 输入'SVGSVGElement | SVG圆元素 | SVGClipPathElement | SVGDefsElement | SVGDescElement | SVGElli...' 不可分配给类型 'Node'。 类型“SVGFEColorMatrixElement”不可分配给类型“节点”。 属性“值”的类型不兼容。 类型“SVGAnimatedNumberList”不可分配给类型“string[]”。 “SVGAnimatedNumberList”类型中缺少属性“includes”。 node_modules/typescript/lib/lib.dom.d.ts(3626,85):错误 TS2344:类型“SVGElementTagNameMap[K]”不满足约束“节点”。 输入'SVGSVGElement | SVG圆元素 | SVGClipPathElement | SVGDefsElement | SVGDescElement | SVGElli...' 不可分配给类型 'Node'。 类型“SVGFEColorMatrixElement”不可分配给类型“节点”。 node_modules/typescript/lib/lib.dom.d.ts(10405,11):错误 TS2430:接口“SVGFEColorMatrixElement”错误地扩展了接口“SVGElement”。 属性“值”的类型不兼容。 类型“SVGAnimatedNumberList”不可分配给类型“string[]”。 node_modules/typescript/lib/lib.dom.d.ts(14172,86):错误 TS2344:类型“SVGElementTagNameMap[K]”不满足约束“节点”。 输入'SVGSVGElement | SVG圆元素 | SVGClipPathElement | SVGDefsElement | SVGDescElement | SVGElli...' 不可分配给类型 'Node'。 类型“SVGFEColorMatrixElement”不可分配给类型“节点”。

“有趣”的是,如果我注释掉 import 然后运行 ​​ng buildng serve,编译错误就会消失。当应用程序处于实时重新加载模式时,我可以简单地取消注释import,尽管出现编译错误,但我仍然可以在应用程序中使用库的所有功能。

【问题讨论】:

【参考方案1】:

在您的类型声明文件中,getSomethinggetCar 函数是名为 mylib 的命名导出的成员,但在实现中,getSomethinggetCar 本身是模块的导出。编写声明文件的最简洁方式如下:

declare module "mylib" 
 export function getSomething(): string;
 export function getCar(make: string, model: string, year: number): Car;

(使用 artem 所说的导出分配是另一种选择。)

旁注:不建议在node_modules/@types/mylib/index.d.ts 手动创建文件,因为npm 和yarn 等包管理工具会控制整个node_modules 目录。相反,您可以将声明文件添加到原始 mylib 包或将实际的 @types/mylib 包发布到您的私有存储库。或者,如果您只想在此 Angular 应用程序中使用类型声明,您可以将 index.d.ts 文件放在 Angular 应用程序自己的目录中,然后 (1) 设置 baseUrl and paths options 以便找到 mylib 的模块分辨率您的文件或 (2) 为您的类型声明创建一个 package.json 文件,并使用相对路径在您的主 package.json 中将其注册为依赖项。

【讨论】:

请让我澄清一下;您是说一种方法可以简单地将类型定义文件添加到mylib 的根目录吗?那么在npm install mylib --save 上,它最终会出现在node_modules/mylib/index.d.ts 中吗?然后从那里我可以导入为import mylib from 'mylib'? 是的,这在查找声明文件方面应该有效。一个单独的问题是,只有根据 artem 的回答启用esModuleInterop,默认导入才会起作用。如果您想导入整个模块,我建议您改用import * as mylib from 'mylib';。无论esModuleInterop如何,这都有效。【参考方案2】:

既然你提到了 NPM,我假设使用 mylib 的代码正在使用 CommonJS 模块解析,并且你正在 node 中运行它(或者你正在使用类似 webpack 的东西)。

那么,这个导出变体就生效了:

module.exports = getLib();

这里将mylib对象作为一个整体导出,并没有引入任何命名为mylib的东西,所以命名为import

import mylib from 'mylib'. 

在运行时未定义。

在TypeScript中如何消费这种export比较复杂,but starting from TypeScript 2.7,通常的工作方式是

    使用export assignment编写类型声明

    declare module "mylib" 
        let mylib: MyLib;
        export = mylib; 
    
    

    --module=commonjs--esModuleInterop=true编译

    像默认导出一样导入它

    import mylib from 'mylib';
    

更新

为避免全局范围内的名称冲突,最好将 mylib 中的所有类型声明在 mylib 模块范围内,如下所示

declare module "mylib" 

    export interface Car 
        make: string;
        model: string;
        year: number;
    

    export interface MyLib 
        getSomething(): string;
        getCar(make: string, model: string, year: number): Car;
    

    export let mylib: MyLib

但是,您必须在每个需要它们的模块中显式导入这些类型:

import mylib from 'mylib';

import Car, MyLib from 'mylib';

【讨论】:

我按照您的建议修改了tsconfig.json,将moduleesModuleInterop 设置为您在上面指定的值。但是,当我输入ng build 时,会出现编译错误:ERROR in node_modules/typescript/lib/lib.dom.d.ts(3268,88): error TS2344: Type 'SVGElementTagNameMap[K]' does not satisfy the constraint 'Node'. 请注意,我已将index.d.ts 放在项目的根目录下,而import mylib from 'mylib' 在IDE(VS Code)中没有显示任何错误.有什么想法吗? 有兴趣观察。如果我注释掉导入语句并运行ng serve,编译错误就会消失。当服务器从ng serve 运行时,如果我取消注释导入语句,编译错误会显示在控制台中,但是,我仍然可以完全使用我的库代码!这一观察使我相信问题出在编译配置上。不知道如何解决。 mylib 中是否有任何名为Node 的接口或类型? 你是对的!有一个名为Node的接口(这是一个有Node、Edge、Graph的图库)。让我看看我能不能解决这个问题。 那么最好将来自mylib 的所有接口声明在declare module .. 范围内,而不是作为全局变量,以避免干扰其他全局类型。我更新了答案。

以上是关于如何为如下所示的 JavaScript 库编写 Typescript 定义文件?的主要内容,如果未能解决你的问题,请参考以下文章

您如何为包含多个模块/类的“复杂”香草 JavaScript 库编写工作 .d.ts 文件?

如何为现有的 Javascript 库创建 Angular 库包装器?

Solidworks如何为装配体绘制剖面视图

Jetpack Compose 如何为 LazyColumn 懒惰地获取音乐文件及其元数据

如何为明星计划包括空间

如何为word文档中的表格设置同样高度的行高?