如何使Typescript库API声明与其实现保持一致? 项目结构说明

如何使Typescript库API声明与其实现保持一致? 项目结构说明,typescript,api-design,Typescript,Api Design,在名为mylib的库中,我有一个手工编写的API声明文件src/mylib.d.ts。手动编写它有一个原因:我想先设计一个API,然后实现它(而tscwith--declaration标志的作用正好相反,它从实现中生成一个API声明) src/mylib.d.ts的内容: declare module "mylib" { export interface Animal { walk(): void; } export class Dog implement

在名为
mylib
的库中,我有一个手工编写的API声明文件src/mylib.d.ts。手动编写它有一个原因:我想先设计一个API,然后实现它(而
tsc
with
--declaration
标志的作用正好相反,它从实现中生成一个API声明)

src/mylib.d.ts的内容

declare module "mylib" {
  export interface Animal {
    walk(): void;
  }
  export class Dog implements Animal {
    constructor(name: string);
    walk(run?: string): void;
    bow(): void;
  }

  export function randomAnimal(): Animal;
  export const version: string;
}
import * as mylib from "mylib";
import { Cat, Mouse } from "./other-animals"
import { logger } from "./logger"

export class Dog implements mylib.Dog {
  walk(run?: string): void {
    logger(`I'm a dog and i ${run ? "run" : "walk"}.`);
  }
  bow(): void {
    logger("bow-wow!");
  }
}

export function randomAnimal(name?: string): mylib.Animal {
  if (name) console.log("you passed name param. It wasn't documented but ok");
  // logger(mylib.version); //if you decomment this line, the bundler will
  // fail with error because "mylib" module doesnt really exists yet. It's OK because in
  // library source code i reference `mylib.d.ts` only for type imports.
  // Or we can just add "paths": { "baseUrl": "src", "mylib": ["./api.ts"] } to tsconfig.json so
  // bundler will use it to resolve module.
  return new Dog();
}

export const version = "1.0.0";
export const undocumentedVar = 123;
import * as api from "./api";
const test: typeof import("mylib") = api;

在包构建阶段,可以将此文件复制为dist/index.d.ts(由
的“类型”引用):
package.json
中的“/dist/index.d.ts”
设置)或作为@types/mylib发布

API实现代码位于src/API.ts

declare module "mylib" {
  export interface Animal {
    walk(): void;
  }
  export class Dog implements Animal {
    constructor(name: string);
    walk(run?: string): void;
    bow(): void;
  }

  export function randomAnimal(): Animal;
  export const version: string;
}
import * as mylib from "mylib";
import { Cat, Mouse } from "./other-animals"
import { logger } from "./logger"

export class Dog implements mylib.Dog {
  walk(run?: string): void {
    logger(`I'm a dog and i ${run ? "run" : "walk"}.`);
  }
  bow(): void {
    logger("bow-wow!");
  }
}

export function randomAnimal(name?: string): mylib.Animal {
  if (name) console.log("you passed name param. It wasn't documented but ok");
  // logger(mylib.version); //if you decomment this line, the bundler will
  // fail with error because "mylib" module doesnt really exists yet. It's OK because in
  // library source code i reference `mylib.d.ts` only for type imports.
  // Or we can just add "paths": { "baseUrl": "src", "mylib": ["./api.ts"] } to tsconfig.json so
  // bundler will use it to resolve module.
  return new Dog();
}

export const version = "1.0.0";
export const undocumentedVar = 123;
import * as api from "./api";
const test: typeof import("mylib") = api;

此文件是bundler的i入口点:
esbuild src/api.ts--bundle--outfile=dist/index.js--format=esm

因此,npm tarball中将有3个文件:dist/index.jsdist/index.d.tspackage.json

问题 问题在于mylib.d.ts中声明的所有内容都独立于它的实现。例如,我们可以从api.ts中删除
randomAnimal
,项目仍然可以编译,没有任何错误

我目前对此问题的解决方案是下一步:我将这一行添加到src/api的末尾:

declare module "mylib" {
  export interface Animal {
    walk(): void;
  }
  export class Dog implements Animal {
    constructor(name: string);
    walk(run?: string): void;
    bow(): void;
  }

  export function randomAnimal(): Animal;
  export const version: string;
}
import * as mylib from "mylib";
import { Cat, Mouse } from "./other-animals"
import { logger } from "./logger"

export class Dog implements mylib.Dog {
  walk(run?: string): void {
    logger(`I'm a dog and i ${run ? "run" : "walk"}.`);
  }
  bow(): void {
    logger("bow-wow!");
  }
}

export function randomAnimal(name?: string): mylib.Animal {
  if (name) console.log("you passed name param. It wasn't documented but ok");
  // logger(mylib.version); //if you decomment this line, the bundler will
  // fail with error because "mylib" module doesnt really exists yet. It's OK because in
  // library source code i reference `mylib.d.ts` only for type imports.
  // Or we can just add "paths": { "baseUrl": "src", "mylib": ["./api.ts"] } to tsconfig.json so
  // bundler will use it to resolve module.
  return new Dog();
}

export const version = "1.0.0";
export const undocumentedVar = 123;
import * as api from "./api";
const test: typeof import("mylib") = api;

然后我用
--noEmit
文件“:[“src/api.ts”]
选项运行tsc

如果声明和实现之间存在不一致,我将看到一个错误


这是一个非常有效的解决方案,但问题是:有没有办法做得更好?例如,不创建额外的未报告常量?

这是一种非常奇怪的方法。API(契约)首先是完全有意义的,但不是编写
.d.ts
文件,而是编写类型和接口,而不是您的实现


我不认为手动编写
.d.ts
文件,然后实现是Typescript真正支持的模式。

看看vscode示例:。这个文件肯定是手写的。然后按原样发布到@types。是的,为已经存在的.js文件编写定义是有意义的。您所尝试的工作流程不是一个得到良好支持或预期的工作流程。请您就如何更改当前项目结构给出建议。如果您正在编写typescript,只需编写typescript即可。您可以通过先编写接口和类型来避免编写实现。从.ts生成相应的.d.ts文件的问题是,我主动使用tsconfig path设置来避免从代码中的“../../../../../”表达式导入。bundler在生成javascript代码时解析此路径,但在生成.d.ts文件时tsc不解析此路径。