Estoy tratando de escribir una biblioteca Typescript que me gustaría poder incluir cuando apunte tanto al navegador como a Node. Tengo dos problemas: hacer referencia a tipos específicos de la plataforma en el cuerpo del código y la inclusión de esos tipos en las declaraciones .d.ts
generadas que acompañan al JS transpilado.
En el primer caso, quiero escribir algo como
if (typeof window === "undefined") { // Do some Node-y fallback thing } else { // Do something with `window` }
Esto falla al compilar si no "dom"
en mi opción de compilador lib
(es decir, si solo digo lib: ["es2016"]
en tsconfig
), porque la window
global no está definida. (Usar window
es solo un ejemplo de algo fuera de lib.dom.d.ts
, también puede ser fetch
o Response
o Blob
, etc.) El punto es que el código ya debería estar seguro en tiempo de ejecución comprobando la existencia del objeto global antes de usarlo, es el lado del tipo que no puedo entender.
En el segundo caso, recibo un error al intentar incluir la biblioteca después de que se compila. Puedo construir la biblioteca usando "dom"
en la opción lib
, y el resultado resultante incluye tipeos con, por ejemplo declare export function foo(x: string | Blob): void
. El problema es que, si el código de consumo no incluye una definición para Blob
(sin lib "dom"
), no se compila, aunque en realidad solo llama a foo
con una string
(¡o no usa foo
en absoluto!).
No quiero que mi biblioteca (o el consumidor) intente contaminar el espacio de nombres global con window
falsas o declaraciones de Blob
si puedo evitarlo. Han aparecido más bibliotecas isométricas, pero no he encontrado un buen ejemplo de TypeScript a seguir. (Si es un tema demasiado complejo para SO, aún así agradecería mucho una referencia a la documentación o un artículo/publicación de blog).
Creo que este es un caso clásico de abstracción y sencillo. Es decir, codifica contra una interfaz IPlatform
y hace referencia a esa interfaz en su código. La interfaz, a su vez, oculta todas las implementaciones específicas de la plataforma.
También puede exponer API de manera adicional para que los consumidores puedan inicializar fácilmente la "plataforma", generalmente con el objeto "global" apropiado. Emplee la inyección de dependencia para inyectar la instancia correcta (específica de la plataforma) de IPlatform
en su código. Esto debería reducir severamente la bifurcación en su código y mantener su código más limpio. No tiene que contaminar su código con declaraciones falsas con ese enfoque, como ha señalado en su pregunta.
Opcionalmente, también puede exportar la instancia de IPlatform
desde su paquete para que los consumidores también puedan beneficiarse de eso.
El segundo problema que mencionas:
El problema es que, si el código de consumo no incluye una definición para Blob (sin lib "dom"), no se compila, aunque en realidad solo llama a foo con una cadena (¡o no usa foo en absoluto!).
Creo que esto se puede contrarrestar fácilmente instalando @types/node
como devDependency en el lado del consumidor. Eso debería tener una huella relativamente pequeña, ya que no se agregará al paquete de un consumidor.
Desafortunadamente, no hay una gran manera de hacer esto actualmente.
El enfoque sugerido por los miembros del equipo de TypeScript es aprovechar la función de fusión de interfaz de TypeScript y las interfaces de "declaración directa" en el ámbito global que potencialmente pueden fusionarse con las declaraciones de la misma interfaz en el entorno de consumo.
Un ejemplo común de esto podría ser el tipo Buffer integrado en Node.js. Una biblioteca que opera tanto en contextos de navegador como de Node.js puede indicar que maneja un Buffer si se le proporciona uno, pero las capacidades de Buffer no son importantes para las declaraciones.
export declare function printStuff(str: string): void; /** * NOTE: Only works in Node.js */ export declare function printStuff(buff: Buffer): void;
Una técnica para evitar esto es "declarar hacia adelante" Buffer con una interfaz vacía en el alcance global que luego se puede fusionar.
declare global { interface Buffer {} } export declare function printStuff(str: string): void; /** * NOTE: Only works in Node.js */ export declare function printStuff(buff: Buffer): void;
Este enfoque tiene algunos problemas, como se detalla en el problema vinculado anteriormente:
El equipo de TypeScript ha propuesto una nueva característica llamada "Declaraciones de tipo de marcador de posición" para abordar estos problemas, pero no parece estar en la hoja de ruta actualmente.