Comparando JavaScript con un ejemplo de DTS
Patrones Comunes de CommonJS
Un módulo que utiliza patrones CommonJS usa module.exports para describir los valores exportados. Por ejemplo, aquí hay un módulo que exporta una función y una constante numérica:
jsconst maxInterval = 12;function getArrayLength(arr) {return arr.length;}module.exports = {getArrayLength,maxInterval,};
Esto se puede describir con el siguiente .d.ts:
tsexport function getArrayLength(arr: any[]): number;export const maxInterval: 12;
El playground de TypeScript puede mostrarte el .d.ts equivalente para el código JavaScript. Puedes probarlo tú mismo aquí.
La sintaxis de .d.ts se asemeja intencionalmente a la sintaxis de Módulos ES.
Los Módulos ES fueron ratificados por TC39 en 2015 como parte de ES2015 (ES6), aunque han estado disponibles a través de transpiladores durante mucho tiempo. Sin embargo, si tienes una base de código JavaScript que usa Módulos ES:
jsexport function getArrayLength(arr) {return arr.length;}
Esto tendría el siguiente equivalente .d.ts:
tsexport function getArrayLength(arr: any[]): number;
Exportaciones por Defecto
En CommonJS puedes exportar cualquier valor como la exportación por defecto, por ejemplo, aquí hay un módulo de expresión regular:
jsmodule.exports = /hello( world)?/;
Que puede ser descrito por el siguiente .d.ts:
tsdeclare const helloWorld: RegExp;export = helloWorld;
O un número:
jsmodule.exports = 3.142;
tsdeclare const pi: number;export = pi;
Un estilo de exportación en CommonJS es exportar una función. Debido a que una función también es un objeto, se pueden agregar campos adicionales y se incluyen en la exportación.
jsfunction getArrayLength(arr) {return arr.length;}getArrayLength.maxInterval = 12;module.exports = getArrayLength;
Que puede ser descrito con:
tsdeclare function getArrayLength(arr: any[]): number;declare namespace getArrayLength {declare const maxInterval: 12;}export = getArrayLength;
Consulta Módulo: Funciones para obtener detalles sobre cómo funciona eso, y la página de referencia de Módulos.
Manejo de Múltiples Importaciones de Consumo
Hay muchas formas de importar un módulo en el código de consumo moderno:
tsconst fastify = require("fastify");const { fastify } = require("fastify");import fastify = require("fastify");import * as Fastify from "fastify";import { fastify, FastifyInstance } from "fastify";import fastify from "fastify";import fastify, { FastifyInstance } from "fastify";
Cubrir todos estos casos requiere que el código JavaScript realmente soporte todos estos patrones. Para soportar muchos de estos patrones, un módulo CommonJS necesitaría parecerse a algo como:
jsclass FastifyInstance {}function fastify() {return new FastifyInstance();}fastify.FastifyInstance = FastifyInstance;// Permite { fastify }fastify.fastify = fastify;// Permite compatibilidad estricta con módulos ESfastify.default = fastify;// Establece la exportación predeterminadamodule.exports = fastify;
Tipos en Módulos
Puede que desees proporcionar un tipo para código JavaScript que no existe
jsfunction getArrayMetadata(arr) {return {length: getArrayLength(arr),firstObject: arr[0],};}module.exports = {getArrayMetadata,};
Esto se puede describir con:
tsexport type ArrayMetadata = {length: number;firstObject: any | undefined;};export function getArrayMetadata(arr: any[]): ArrayMetadata;
Este ejemplo es un buen caso para usar genéricos y proporcionar información de tipo más rica:
tsexport type ArrayMetadata<ArrType> = {length: number;firstObject: ArrType | undefined;};export function getArrayMetadata<ArrType>(arr: ArrType[]): ArrayMetadata<ArrType>;
Ahora el tipo del arreglo se propaga al tipo ArrayMetadata.
Los tipos que se exportan pueden luego ser reutilizados por los consumidores de los módulos usando import o import type en código TypeScript o importaciones JSDoc.
Espacios de nombres en el código del módulo
Describir la relación en tiempo de ejecución del código JavaScript puede ser complicado. Cuando la sintaxis tipo ES Module no dispone de suficientes herramientas para describir las exportaciones, puedes usar namespaces.
Por ejemplo, puede que tengas tipos lo bastante complejos y prefieras agruparlos dentro de tu .d.ts:
ts// Esto representa la clase de JavaScript que estaría disponible en tiempo de ejecuciónexport class API {constructor(baseURL: string);getInfo(opts: API.InfoRequest): API.InfoResponse;}// Este namespace se fusiona con la clase API y permite tener, tanto para los consumidores// como para este archivo, tipos anidados en sus propias secciones.declare namespace API {export interface InfoRequest {id: string;}export interface InfoResponse {width: number;height: number;}}
Para entender cómo funcionan los espacios de nombres en archivos .d.ts, lee la guía avanzada de .d.ts.
Uso global opcional
Puedes usar export as namespace para declarar que tu módulo estará disponible en el ámbito global en contextos UMD:
tsexport as namespace moduleName;
Ejemplo de referencia
Para darte una idea de cómo encajan todas estas piezas, aquí tienes un .d.ts de referencia para comenzar un nuevo módulo:
ts// Definiciones de tipos para [~EL NOMBRE DE LA LIBRERÍA~] [~NÚMERO DE VERSIÓN OPCIONAL~]// Proyecto: [~EL NOMBRE DEL PROYECTO~]// Definiciones por: [~TU NOMBRE~] <[~UNA URL PARA TI~]>/*~ Este es el archivo de plantilla del módulo. Deberías renombrarlo a index.d.ts*~ y colocarlo en una carpeta con el mismo nombre que el módulo.*~ Por ejemplo, si estuvieras escribiendo un archivo para "super-greeter", este*~ archivo debería llamarse 'super-greeter/index.d.ts'*//*~ Si este módulo es un módulo UMD que expone una variable global 'myLib' cuando*~ se carga fuera de un entorno de cargador de módulos, declara esa variable global aquí.*~ De lo contrario, elimina esta declaración.*/export as namespace myLib;/*~ Si este módulo exporta funciones, decláralas de este modo. */export function myFunction(a: string): string;export function myOtherFunction(a: number): number;/*~ Puedes declarar tipos que estén disponibles al importar el módulo */export interface SomeType {name: string;length: number;extras?: string[];}/*~ Puedes declarar propiedades del módulo usando const, let o var */export const myField: number;
Estructura de archivos de la biblioteca
La disposición de tus archivos de declaración debe reflejar la estructura de la biblioteca.
Una biblioteca puede constar de varios módulos, como por ejemplo:
myLib+---- index.js+---- foo.js+---- bar+---- index.js+---- baz.js
Estos se podrían importar como:
jsvar a = require("myLib");var b = require("myLib/foo");var c = require("myLib/bar");var d = require("myLib/bar/baz");
Por lo tanto, tus archivos de declaración deberían ser:
@types/myLib+---- index.d.ts+---- foo.d.ts+---- bar+---- index.d.ts+---- baz.d.ts
Pruebas de tus tipos
Si planeas enviar estos cambios a DefinitelyTyped para que todos también los usen, te recomendamos:
- Crea una nueva carpeta en
node_modules/@types/[libname]- Crea un
index.d.tsen esa carpeta y copia el ejemplo en él- Observa dónde falla tu uso del módulo y comienza a completar el index.d.ts
- Cuando estés satisfecho, clona DefinitelyTyped/DefinitelyTyped y sigue las instrucciones del README
De lo contrario:
- Crea un nuevo archivo en la raíz de tu árbol de código fuente:
[libname].d.ts- Agrega
declare module "[libname]" { }- Agrega la plantilla dentro de las llaves del declare module y observa dónde falla tu uso