如何为如下所示的 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 库,我编写该库与 AMD
和 CommonJS
一起使用。我忘记了影响我以这种方式编写库的原始来源,但我的 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
时,我看到了下面的消息。
“有趣”的是,如果我注释掉 import
然后运行 ng build
或 ng serve
,编译错误就会消失。当应用程序处于实时重新加载模式时,我可以简单地取消注释import
,尽管出现编译错误,但我仍然可以在应用程序中使用库的所有功能。
【问题讨论】:
【参考方案1】:在您的类型声明文件中,getSomething
和 getCar
函数是名为 mylib
的命名导出的成员,但在实现中,getSomething
和 getCar
本身是模块的导出。编写声明文件的最简洁方式如下:
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
,将module
和esModuleInterop
设置为您在上面指定的值。但是,当我输入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 库包装器?