¿Cómo agrego definiciones de Typescript para los complementos de Leaflet?
Me gustaría usar el complemento easy-buttons con TypeScript https://github.com/CliffCloud/Leaflet.EasyButton/blob/master/src/easy-button.js pero no viene con anotaciones de TypeScript.
Respuesta #1
Paso 1 - resaltar los errores
El primer paso es usar el código de ejemplo tal como está, sin anotaciones de Typescript, y los errores comenzarán a aparecer en VS Code.
// sample.ts
L.easyBar([
L.easyButton('fa-file', function(btn, map){ }),
L.easyButton('fa-save', function(btn, map){ }),
L.easyButton('fa-edit', function(btn, map){ }),
L.easyButton('fa-dot-circle-o', function(btn, map){ })
]).addTo(map);
Para lo cual creamos un archivo llamado 'easy-button.d.ts' y lo referimos en nuestro archivo de Typescript.
// sample.ts
import "./easy-button"
L.easyBar([
L.easyButton('fa-file', function(btn, map){ }),
L.easyButton('fa-save', function(btn, map){ }),
L.easyButton('fa-edit', function(btn, map){ }),
L.easyButton('fa-dot-circle-o', function(btn, map){ })
]).addTo(map);
Y no hay nada en easy-button.d.ts
// easy-button.d.ts
// empty for now
El error dice
error TS2339: Property 'easyBar' does not exist on type 'typeof L'.
error TS2339: Property 'easyButton' does not exist on type 'typeof L'.
Lo cual es bastante justo porque aún no hemos definido estos.
Si se refiere a la definición de easyBar y easyButton aquí y aquí, encontrará que hay un poco de magia ocurriendo en las declaraciones originales de Javascript. Parece que estas dos funciones no toman ningún argumento, pero en realidad sí lo hacen.
L.easyButton = function(/* args will pass automatically */){
var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments);
return new (Function.prototype.bind.apply(L.Control.EasyButton, args));
};
Esta función va a llamar a new en la clase L.Control.EasyButton. Los parámetros son algo crípticos, pero puedes inferirlos de esta línea que indica:
initialize: function(icon, onClick, title, id)
Paso 2 - agregar los tipos
// easy-button.d.ts
declare namespace L {
function easyBar();
function easyButton();
}
y ahora estamos un poco más cerca:
error TS2346: Supplied parameters do not match any signature of call target
y eso es bastante obvio porque proporcionamos 2 parámetros 'fa-edit' y un callback a easyButton pero no declaramos ninguno en nuestros argumentos. Nuestro segundo intento ahora se ve así:
// easy-button.d.ts
declare namespace L {
function easyBar(buttons: any[]);
function easyButton(icon: string, onClick: (btn: any, map: any)=>void);
}
y ahora todas las advertencias de Typescript han desaparecido. Pero hay más que se puede hacer. Por un lado, easyButton en realidad toma 4 argumentos. Eso es fácil de arreglar: observa cómo los argumentos opcionales tienen un sufijo ?:
// easy-button.d.ts
declare namespace L {
function easyBar(buttons: any[]);
function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string);
}
Paso 3 - proporcionar valores de retorno
El método easyButton en realidad devuelve una instancia de L.Control.EasyButton. Actualmente, la definición de Typescript implica que easyButton devuelve un tipo any. ¡No queremos eso! Typescript solo es útil cuando proporcionamos tipos.
declare namespace L {
function easyBar(buttons: Control.EasyButton[]): Control.EasyBar;
function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string) : Control.EasyButton;
namespace Control {
class EasyButton { };
class EasyBar { };
}
}
Typescript vuelve a proporcionar advertencias útiles:
error TS2339: Property 'addTo' does not exist on type 'EasyBar'.
Esto se debe a que EasyBar es una subclase de L.Control, necesitamos traer esa definición a nuestro archivo de definiciones.
declare namespace L {
function easyBar(buttons: Control.EasyButton[]): Control.EasyBar;
function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string) : Control.EasyButton;
namespace Control {
class EasyButton extends L.Control { }
class EasyBar extends L.Control { }
}
}
Paso 4 - proporcionar argumentos de constructor a EasyButton y EasyBar
Si intentas instanciar un nuevo EasyButton, la finalización de código sugiere que deberías pasar un objeto L.ControlOptions para configurarlo. En realidad, necesitamos definir nuestras propias opciones.
declare namespace L {
function easyBar(buttons: Control.EasyButton[], options?: EasyBarOptions): Control.EasyBar;
function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string) : Control.EasyButton;
interface EasyBarOptions {
position?: ControlPosition
id?: string
leafletClasses?: boolean
}
interface EasyButtonOptions {
position?: ControlPosition
id?: string
type?: 'replace'|'animate'
states?: any
leafletClasses?: boolean
tagName?: string
}
namespace Control {
class EasyButton extends L.Control {
constructor(options?: EasyButtonOptions)
}
class EasyBar extends L.Control {
constructor(options?: EasyBarOptions)
}
}
}
Las cosas se ven mejor ahora en la finalización de código:
Sin embargo, hice trampa con la opción states. La declaré como any. En realidad, debería ser
interface EasyButtonOptions {
position?: ControlPosition
id?: string
type?: 'replace'|'animate'
states?: EasyButtonState[]
leafletClasses?: boolean
tagName?: string
}
interface EasyButtonState {
stateName: string
onClick: () => void
title: string
icon: string
}
Paso 5 - agregar sugerencias de jsdoc
Typescript proporcionará comentarios útiles a los usuarios de este complemento. Aquí hay un ejemplo de cómo podríamos proporcionar documentación para easyButton
/**
* Creates a easyButton
* @param icon e.g. fa-globe
* @param onClick the button click handler
* @param label on the button
* @param an id to tag the button with
* @example
* var helloPopup = L.popup().setContent('Hello World!');
*
* L.easyButton('fa-globe', function(btn, map){
* helloPopup.setLatLng(map.getCenter()).openOn(map);
* }).addTo( YOUR_LEAFLET_MAP );
*/
function easyButton(
icon: string,
onClick: (btn: Control.EasyButton, map: L.Map) => void,
title?: string,
id?: string): Control.EasyButton;
Paso 6 Hacer modificaciones para aumentar las definiciones de tipos de folletos
(A partir de Leaflet 1.2.0) Elimine la declaración de espacio de nombres:
declare namespace L {
y reemplazarlo con una ampliación de módulo:
import * as L from 'leaflet'
declare module 'leaflet' {
tu código de prueba ahora debería verse así:
import * as L from 'leaflet'
import 'easy-button'
Paso 7 integrar en las fuentes originales del proyecto
Abre node_modules\leaflet-easybutton\package.json y añade la siguiente línea debajo de la entrada style:
"main": "src/easy-button.js",
"style": "src/easy-button.css",
"typings": "src/easy-button.d.ts",
Mueve nuestro easy-button.d.ts a node_modules/leaflet-easybutton/src, y prueba que todo siga funcionando.
¡Luego envía una solicitud de extracción para que todos puedan beneficiarse del trabajo!
Respuesta #2
En realidad, lo hace ahora. Solo deberías
import * as L from 'leaflet';
import 'leaflet-easybutton/src/easy-button';

