问题
Project structure description
In my library named mylib
I have an API declarartion file src/mylib.d.ts written by hand. There is a reason for write it manually: I want to design an API first and then to implement it (while the tsc
with --declaration
flag does the opposite - it generates an API declaration from implementation).
Content of 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;
}
At the package build stage this file can be copied as dist/index.d.ts (to be referenced by "types":"./dist/index.d.ts"
setting in package.json
) or just published as @types/mylib.
The API implementation code located is in src/api.ts:
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;
This file i entry point for bundler: esbuild src/api.ts --bundle --outfile=dist/index.js --format=esm
.
As result there will be 3 files in npm tarball: dist/index.js, dist/index.d.ts and package.json
The problem
The problem is that everything declared in mylib.d.ts are independent from it's implementation. For example we can remove randomAnimal
from api.ts and project still be compiled without any errors.
My current solution to this problem is next: I add this lines to the end of src/api:
import * as api from "./api";
const test: typeof import("mylib") = api;
And then i run tsc with --noEmit
and "files": ["src/api.ts"]
options.
If there will be incoherence between declaration and implementation I will see an error.
It's pretty working solution, but the question is: are there any ways to do it better? For example, without creating additional unexported constant?
回答1:
This is a pretty strange way to do this. API (contract) first makes total sense, but instead of writing .d.ts
files, just write types an interfaces and not your implementation.
I don't think writing the .d.ts
files manually and then the implementation is a pattern that is Typescript really supports.
来源:https://stackoverflow.com/questions/64592205/ways-to-keep-a-typescript-library-api-declaration-coherent-with-its-implementati