Search code examples
javascripttypescriptleafletpolylinees6-modules

How do I import extensions to an ES6/Typescript module


I'm trying to convert a simple Javascript prototype written in a single .html file with embedded <script> tags to a module compiled with Typescript.

It's using Leaflet which I was happily able to get installed via

npm install leaflet

npm install --save @types/leaflet

importing via

import * as L from 'leaflet';

and using via eg.

var map = L.map('map').setView([-43.4175044, 172.185657], 8);

However I also want to use this https://rawgit.com/jieter/Leaflet.encoded/master/Polyline.encoded.js Javascript file which provides some extensions to the main Leaflet L object.

I've tried importing this via

import 'https://rawgit.com/jieter/Leaflet.encoded/master/Polyline.encoded.js'

However when I attempt to use it eg.

 var coordinates = L.Polyline.fromEncoded(encoded).getLatLngs();

I get the following error:

error TS2339: Property 'fromEncoded' does not exist on type 'typeof Polyline'.

How can I get this working? Is it just a matter of providing types support for these extensions? If so, how can I do that?


Solution

  • It's tricky due to how polyline-encoded works: this plugin extends Leaflet. So if we want it to work exactly as in JavaScript, we need to extend the leaflet types and its 1550 lines! Even more problematic, every time we want to update Leaflet, we need to check whether its types have been updated and merge them with the polyline-encoded types!

    Another potential issue: in your code, Leaflet is used in a ES6 module but polyline-encoded is based on an IIFE that changes the current Leaflet object L, mixing old and new JavaScript ways. I'm curious to know if it works.

    Anyway, a safer option that I see (but didn't test yet):

    • Define new types → see Leaflet.encoded.d.ts below, to add to your project.
    • Force-cast L as the extended type defined in Leaflet.encoded.d.ts: Lx.L;.
    • Use Lx instead of L for every use of the polyline-encoded extensions.

    Your code adapted:

    import * as L from 'leaflet';
    import 'https://rawgit.com/jieter/Leaflet.encoded/master/Polyline.encoded.js';
    
    const Lx = L as any as Lx.L;
    
    const map = L.map('map').setView([-43.4175044, 172.185657], 8);
    
    const coordinates = Lx.Polyline.fromEncoded('...').getLatLngs();
    

    Leaflet.encoded.d.ts:

    // Type definitions for Leaflet polyline-encoded 0.0.8
    // Project: https://github.com/jieter/Leaflet.encoded
    // Definitions by: Romain Deneau <https://github.com/rdeneau>
    // TypeScript Version: 2.5
    
    import * as Leaflet from 'leaflet';
    
    export as namespace Lx;
    
    export interface L {
        PolylineUtil: PolylineUtil;
        Polyline: Polyline;
        Polygon: Polygon;
    }
    
    // -- PolylineUtil plugin ---------------------------------
    
    export interface PolylineUtilOptions {
        precision: number;
        factor: number;
        dimension: number;
    }
    
    export type LatLngTuple = [number, number];
    
    export interface PolylineUtil {
        /**
         * Decode the string `encoded` to an array of `[lat, lng]`-arrays.
         */
        decode(encoded: string, options?: number|PolylineUtilOptions): LatLngTuple[];
    
        /**
         * Encode an array of `L.LatLng` objects, or an array of arrays.
         */
        encode(points: Leaflet.LatLng[]|LatLngTuple[], options?: number|PolylineUtilOptions): string;
    }
    
    // -- Polyline/Polygon extensions -------------------------
    
    export class Polyline extends Leaflet.Polyline {
        /**
         * Return an encoded string for the current Polyline.
         */
        encodePath(): string;
    
        /**
         * Construct a new `L.Polyline` from a string, with optional `options` object.
         * Backslashes in strings should be properly escaped.
         */
        fromEncoded(encoded: string, options?: Leaflet.PolylineOptions): Leaflet.Polyline;
    }
    
    export class Polygon extends Leaflet.Polygon {
        /**
         * Return an encoded string for the current Polygon.
         */
        encodePath(): string;
    
        /**
         * Construct a new `L.Polygon` from a string, with optional `options` object.
         * Backslashes in strings should be properly escaped.
         */
        fromEncoded(encoded: string, options?: Leaflet.PolylineOptions): Leaflet.Polygon;
    }
    

    If it's working, this types can even be shared, submitting them to the DefinitelyTyped repository.