16

JS迁移到TS:为第三方NPM模块(非TS开发),写一个声明文件

作者:克里斯.托马森
Apr 13, 2017

假如,你有一个由多个NPM包组成的APP,对”常规JS项目“来说,这不是一个问题。TS的最大优势就是静态类型检查,为了更好地利用这个优势,我们需要在”从第三方NPM包中引入代码“时,为这些第三方NPM包增加类型声明。
TS利用声明文件,来使编译器理解模块中的变量类型和函数签名。

然而,如何为这些第三方NPM包增加声明文件的方式已经变了。因此,你从网上找到的那些方法,可能是过时的

方法一、供应商已经给你写好类型声明文件

TS的2.2版本中,有了一个更加直接的方法来为这些流行的NPM包增加声明文件。你所要做的是:

npm install --save-dev @types/module
// for example:
npm install --save lodash
npm install --save-dev @types/lodash

npm包管理器将自动在’node_modules/@types'模块路径下,根据这些模块的子路径创建一个叫’index.d.ts’的文件。这个文件(即’index.d.ts)内部不包含任何业务代码。仅仅是一个用来描述组件接口,比如类定义、类型用的。尽管增加了这个文件,这个模块在使用时,你还是得老老实实地import进来。

注释:其实这些第三方厂商,在了解TS的趋势后,在代码库编写了‘类型声明文件’, 默认安装的时候不下载。但是可以通过@types/[module]的方式去代码库主动下载。
方法二、供应商偷懒

那如果是那些没有声明文件(即该包的供应商没有编写‘类型声明模块’)的模块怎么办?

不可避免的,你的项目中总有写npm包,其供应商本没有为该包编写类型声明文件。如果要在TS文件中利用静态类型检测的好处,你就不得不在项目中自己编写这个包的声明文件。

我花了数个小时来解决这个问题。TS文档也没有跟我们说怎么样来解决这个问题,网上找到的方法很多都是旧版的解决方法。我今天写个文档,是希望有人能通过我写的东西,在面临相同问题时,能节约一点时间。

配置

首先,打开tsconfig.json这个文件,有一个叫typeRoots的属性,这个属性是用来定义”哪里去检索声明文件“。默认的,这个属性是不设置的;而且,在不设置的时候,这个属性是直接去搜索node_modules/@types下找声明文件。因为他只会在node_modules文件夹路径下找,而这个路径是用来存放包的,你的业务代码不会放在里面。

因此,第一步就是在我们的项目中,增加一个文件夹用来存储我们的声明文件。在这个项目中,我们将使用”@types“作为存放路径,当然,你想用什么名字就什么名字。
tsconfig.json

{
  "compilerOptions": {
    "outDir": "./built",
    "allowJs": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "target": "es6",
    "module": "commonjs"
  },
  "include": [
    "./src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

这个配置中,noImplicitAny被谁知成ture,这意味着你必须明确地增加类型声明。如果你有一个需要迁移很多包的大型项目,你应该把它关掉。
̶I̶t̶ ̶a̶l̶s̶o̶ ̶a̶d̶d̶s̶ ̶t̶y̶p̶e̶R̶o̶o̶t̶s̶:̶ ̶[̶”̶@̶t̶y̶p̶e̶s̶”̶,̶ ̶”̶.̶/̶@̶t̶y̶p̶e̶s̶”̶]̶ ̶.̶ ̶T̶h̶i̶s̶ ̶t̶e̶l̶l̶s̶ ̶t̶h̶e̶ ̶T̶y̶p̶e̶S̶c̶r̶i̶p̶t̶ ̶c̶o̶m̶p̶i̶l̶e̶r̶ ̶t̶o̶ ̶l̶o̶o̶k̶ ̶f̶o̶r̶ ̶.̶d̶.̶t̶s̶ ̶f̶i̶l̶e̶s̶ ̶i̶n̶ ̶b̶o̶t̶h̶ ̶n̶o̶d̶e̶_̶m̶o̶d̶u̶l̶e̶s̶/̶@̶t̶y̶p̶e̶s̶ ̶a̶s̶ ̶w̶e̶l̶l̶ ̶a̶s̶ ̶o̶u̶r̶ ̶c̶u̶s̶t̶o̶m̶ ̶d̶i̶r̶e̶c̶t̶o̶r̶y̶ ̶.̶/̶@̶t̶y̶p̶e̶s̶.̶ Note that all the original JavaScript source files were moved into srcto facilitate TypeScript compiling.

注意,所有的原生代码被迁移到src路径下,便于TS解释器编译。
2018-02-01更新:在最新版本的TS中,不需要在tsconfig.json中单独为typeRoots设置值。
具体步骤

现在,我们能够创建我们自定义的声明文件。在这个例子中,我将展示如何为一个叫做dir-obj的NPM包编写一个声明文件,这是一个我在项目中实际碰到并解决的例子。

步骤一、模拟场景创建

让我们先创建一个项目

mkdir ~/dev/myproject
cd ~/dev/myproject
mkdir src
mkdir built
vim tsconfig.json
{
  "compilerOptions": {
    "outDir": "./built",
    "module": "commonjs",
    "target": "es6",
    "noImplicitAny": true,
    "sourceMap": false
  },
  "include": [
    "src/**/*"
  ]
}

vim src/index.ts

import * as dirObj from 'dir-obj';
const project = dirObj.readDirectory(__dirname + '/..', {
  fileTransform: (file: dirObj.File) => {
    return file.fullpath;
  }
});
console.log(JSON.stringify(project, null, 2));

这个简单的文件,将实现”读取项目结构,并输出每个文件的全路径“
在根目录下,终端中输入一下代码,把TS文件编译成ES5文件:

tsc -p .

-p指令用来告诉tsc编译器,在当前路径寻找tsconfig.json文件
警告!无法找到模块声明文件

src/index.ts(1,25): error TS7016: Could not find a declaration file for module 'dir-obj'. '/Users/chris/dev/personal/typescript-examples/node_modules/dir-obj/index.js' implicitly has an 'any' type.
步骤二、创建声明文件

在当前的设置中,ts编译器不能静态检测我们的代码是否类型安全,因此,我们要增加声明文件。

mkdir src/@types
mkdir src/@types/dir-obj
vim src/@types/dir-obj/index.d.ts

这里我们在src路径下创建了@types的文件夹,以便文件在编译时被自动地引入。
我们将为dir-obj新增一个声明文件,你的声明文件必须建在npm包同名的文件夹中。即,上图中dir-obj文件为包的同名文件夹。
创建声明文件

/// <reference types="node" />

declare module 'dir-obj' {
  import { Stats } from "fs";

  export interface readOptions {
    filter?: RegExp | Filter,
    dirTransform?: DirTransform,
    fileTransform?: FileTransform
  }

  export type Filter = (file: File) => boolean;
  export type DirTransform = (file: File, value: any) => any;
  export type FileTransform = (file: File) => any;

  export function readDirectory(dir: string, options?: readOptions): object;

  export class File {
    key: string;
    readonly path: string;
    readonly fullpath: string;
    readonly ext: string;
    readonly name: string;
    readonly basename: string;

    constructor(dir: string, file: string);

    readonly attributes: Stats;
    readonly isDirectory: boolean;
    readonly isRequirable: boolean;
  }
}

我们在文件的开头写上”declare module ‘dir-obj’“,以明确地陈述这个声明文件所要声明的npm包。
声明文件剩下的内容中,是一系列原文件中同名的函数和类。不同的是,我们给这些同名类和函数增加了类型信息。需要指出的是,如何解读JS原包和如何写一个类型定义,不在本文档的范畴。但是,还是希望对你在正确的道路上有所帮助。
再次发送”编译项目“指令

tsc -p .

最终,再也没有报编译错误。


有杯葡萄
282 声望4 粉丝

我的提问,合理的回答感谢费2元,采纳的回答感谢费5元。感谢!