javascript - Hur man slår ihop TypeScript-moduldefinitioner


Jag har ett JavaScript-modul som har en anpassad .d.ts-fil. Låt oss kalla modulen Foobar:

foobar.js

function foobar (opts) {
  this.register = plugin => {
    plugin(this)
  }

  this.decorate = (prop, value) => {
    this[prop] = value
  }

  return this
}
export default foobar

foobar.d.ts

export interface FoobarPlugin {
  (inst: FoobarInst): void
}

export interface FoobarInst {
  register(plugin: FoobarPlugin): void
  decorate(prop: string, value: any): void
}

export default function foobar (): FoobarInst

Jag har också ett plugin:

fuzzbuzz.js

function fuzzbuzz (inst) {
  inst.decorate('fuzzbuzz', true)
}

export default fuzzbuzz

fuzzbuzz.d.ts

import { FoobarInst } from '../foobar/foobar'

export default function fuzzbuzz (inst: FoobarInst): void

Jag laddar pluginen i min modul:

index.ts

import foobar from './foobar/foobar'
import fuzzbuzz from './fuzzbuzz/fuzzbuzz'

const inst = foobar()

inst.register(fuzzbuzz)

inst.fuzzbuzz // -> true

Vad behöver jag lägga till i fuzzbuzz.d.ts för att uppdatera FoobarInst-typdefinitionen?

Jag har försökt med variationer av:

declare module foobar {
  interface FoobarInst {
    fuzzbuzz: boolean
  }
}

Min tsconfig.json ser ut så här:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true
    "forceConsistentCasingInFileNames": true, 
    "resolveJsonModule": true,
  }
}

Tack för all hjälp du kan ge!

Svar #1

Det du letar efter är modulförstärkning. Jag tror att ett import-uttalande saknas i fuzzbuzz.d.ts för att låta TS känna igen att de angivna deklarationerna utökar/förstärker en redan befintlig "foobar"-modul:

fuzzbuzz.d.ts:

import { FoobarOpts } from "foobar"; // can be any import, preferrably one of "foobar" 

declare module "foobar" {
  // your module extensions
}

Angående pluginarkitektur

Pluginförlängningarna måste kunna statiskt analyseras av kompilatorn. Det betyder att du inte kan få TS att utöka foobar samtidigt som foobar.register(fuzzbuzz) anropas eller via dynamisk import.

Istället ses modulen som förstärkt när fuzzbuzz.d.ts inkluderas som indata för kompileringen antingen genom modulupplösning eller automatisk inkludering av .ts/.d.ts-filer i projektkatalogen. Därför är det vettigt att placera typförstärkningen och foobar.register(fuzzbuzz) i en modul, så att typer och körtidskod är synkroniserade. Ett minimalt exempel:

foobar.ts:

declare module "foobar" {
  // Plugin gets the options and possibly some internal "foobar" state
  type Plugin = (opts: FoobarOpts, state: {}) => void;

  interface FoobarOpts {
    foo: string;
    bar: number;
  }

  export default function foobar(opts: FoobarOpts): void;

  function register(plugin: Plugin): void;
}

my-plugin.ts:

import { register, Plugin, FoobarOpts } from "foobar";

const fuzzBuzzPlugin: Plugin = (opt: FoobarOpts, state) => {
  opt.fuzzbuzz; // fuzzbuzz available now.
};

// set type augmentation...
declare module "foobar" {
  export interface FoobarOpts {
    fuzzbuzz: boolean;
  }
}

// ... and run-time plugin extension in one module, so they go hand in hand
register(fuzzBuzzPlugin);

client.ts:

import foobar from "foobar";

foobar({ bar: 42, foo: "buh", fuzzbuzz: true }); // works with fuzzbuzz

Svar #2

Tack @ford04 för ett fantastiskt svar. Med de angivna länkarna kunde jag lösa mitt specifika mål.

Den viktiga detaljen med mitt scenario är att både foobar och fuzzbuzz är skrivna i JavaScript, och har anpassade .d.ts-definitionsfiler. Dessutom använder foobar-modulen inte declare module utan istället ett export default på den enskilda funktion som modulen ursprungligen exporterar.

Lösningskoden är:

| - node_modules \
|   - foobar \
|     - foobar.js
|     - foobar.d.ts
|     - package.json
|   - fuzzbuzz \
|     - fuzzbuzz.js
|     - fuzzbuzz.d.ts
|     - package.json
| - index.ts
| - package.json

foobar.js

function foobar (opts) {
  this.register = plugin => {
    plugin(this)
  }

  this.decorate = (prop, value) => {
    this[prop] = value
  }

  return this
}

export default foobar

foobar.d.ts

export interface FoobarPlugin {
  (inst: FoobarInst): void
}

export interface FoobarInst {
  register(plugin: FoobarPlugin): void
  decorate(prop: string, value: any): void
}

export default function foobar (): FoobarInst

foobar/package.json

{  
  "main": "foobar.js",
  "types": "foobar.d.ts",
}

fuzzbuzz.js

function fuzzbuzz (inst) {
  inst.decorate('fuzzbuzz', true)
}

export default fuzzbuzz

fuzzbuzz.d.ts

import { FoobarInst } from 'foobar'

declare module "foobar" {
  interface FoobarInst {
    fuzzbuzz: boolean
  }
}

export default function fuzzbuzz (inst: FoobarInst): void

fuzzbuzz/package.json

{
  "main": "fuzzbuzz.js",
  "types": "fuzzbuzz.d.ts",
}

index.ts

import foobar from 'foobar'
import fuzzbuzz from 'fuzzbuzz'

const inst = foobar()

inst.register(fuzzbuzz)

inst.fuzzbuzz // -> true and no type error!

Viktiga detaljer

  • Specificera typdefinitionerna i package.json

  • declare module '<library>' kommer att sammanfoga deklarationer även om <library> inte använder declare module uttryckligen (jag tror att det fortfarande betraktas som ett modul av TypeScript oavsett).

  • Denna prototypförstärkning/dekoration är benägen att orsaka fel i den bemärkelsen att bara genom att importera fuzzbuzz i index.ts kommer typerna att slås ihop... även om du inte anropar .register(fuzzbuzz). @ford04:s svar ger mer sammanhang om detta Därför är det vettigt att placera typförstärkningen och foobar.register(fuzzbuzz) i en modul, så att typer och körkod är synkroniserade.

Relaterade artiklar