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

时间:2018-11-13 22:43:32

标签: javascript angular typescript typescript-typings

我有一个旧的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';

在VS Code(IDE)中,没有抱怨(没有红线表示问题)。此外,我将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时,我看到以下消息。

ERROR in node_modules/typescript/lib/lib.dom.d.ts(3268,88): error TS2344: Type 'SVGElementTagNameMap[K]' does not satisfy the constraint 'Node'.
  Type 'SVGSVGElement | SVGCircleElement | SVGClipPathElement | SVGDefsElement | SVGDescElement | SVGElli...' is not assignable to type 'Node'.
    Type 'SVGFEColorMatrixElement' is not assignable to type 'Node'.
      Types of property 'values' are incompatible.
        Type 'SVGAnimatedNumberList' is not assignable to type 'string[]'.
          Property 'includes' is missing in type 'SVGAnimatedNumberList'.
node_modules/typescript/lib/lib.dom.d.ts(3626,85): error TS2344: Type 'SVGElementTagNameMap[K]' does not satisfy the constraint 'Node'.
  Type 'SVGSVGElement | SVGCircleElement | SVGClipPathElement | SVGDefsElement | SVGDescElement | SVGElli...' is not assignable to type 'Node'.
    Type 'SVGFEColorMatrixElement' is not assignable to type 'Node'.
node_modules/typescript/lib/lib.dom.d.ts(10405,11): error TS2430: Interface 'SVGFEColorMatrixElement' incorrectly extends interface 'SVGElement'.
  Types of property 'values' are incompatible.
    Type 'SVGAnimatedNumberList' is not assignable to type 'string[]'.
node_modules/typescript/lib/lib.dom.d.ts(14172,86): error TS2344: Type 'SVGElementTagNameMap[K]' does not satisfy the constraint 'Node'.
  Type 'SVGSVGElement | SVGCircleElement | SVGClipPathElement | SVGDefsElement | SVGDescElement | SVGElli...' is not assignable to type 'Node'.
    Type 'SVGFEColorMatrixElement' is not assignable to type 'Node'.

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

2 个答案:

答案 0 :(得分:2)

在类型声明文件中,getSomethinggetCar函数是名为mylib的命名导出的成员,但在实现中,getSomething和{{1} }本身就是模块的导出。编写声明文件的最干净方法是这样的:

getCar

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

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

答案 1 :(得分:1)

由于您提到了NPM,所以我假设使用mylib的代码使用的是CommonJS模块分辨率,并且您正在节点中运行它(或者使用的是webpack之类的东西)。

然后,此导出变体生效:

module.exports = getLib();

这会整体导出mylib对象,并且不会引入任何名为mylib的内容,因此会导入import

import {mylib} from 'mylib'. 

在运行时未定义。

如何在TypeScript中使用这种导出很复杂,but starting from TypeScript 2.7,通常的工作方式是

  1. 使用export assignment

    编写类型声明
    declare module "mylib" {
        let mylib: MyLib;
        export = mylib; 
    }
    
  2. 使用--module=commonjs--esModuleInterop=true

  3. 进行编译
  4. 将其导入为默认导出

    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';