如何使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.js、dist/index.d.ts和package.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不解析此路径。