Developing Plugins for MapLibre Interoperability

Categories: community-post
Authors: austyle

Considering that MapLibre is an open-source fork of the Mapbox rendering library, many of the Mapbox plugins can be used in conjunction with MapLibre without any issue in JavaScript. However, since MapLibre is a TypeScript library in the same spirit, if you wish to use some of the popular Mapbox plugins in your TypeScript application you will likely run into the TypeScript compiler throwing a lot of type inference errors. This is because these plugins were developed with Mapbox solely in mind.

To quickly step over these compiler complaints you may reach for the all powerful @ts-ignore to do something like this when using @mapbox/mapbox-gl-draw:

 1import maplibregl from "maplibre-gl";
 2import MapboxDraw from "@mapbox/mapbox-gl-draw";
 3
 4import "maplibre-gl/dist/maplibre-gl.css";
 5import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css";
 6
 7const map = new maplibregl.Map({ ...someConfig });
 8const drawCtl = new MapboxDraw();
 9
10// @ts-ignore
11map.addControl(drawCtl, "top-left");

Currently, we need to do this because when using TypeScript the plugin makes the assumption that it should receive reference to a Mapbox Map object rather than a MapLibre Map object. So, we must explicitly tell the TypeScript compiler to ignore this static type check. This should be a very scary band-aid to developers because if the two implementations of the map objects diverge into different directions over time it can break compatibility with this plugin. Additionally if that were to happen, no warnings or errors will surface because we are suppressing the complaint from the compiler with the all powerful @ts-ignore.

In order to avoid what could be an illusive bug in the future and to add compatibility for both MapLibre and Mapbox libraries, there is a solution! We at MapLibre recommend updating existing plugins and building new plugins to support both libraries. Here are some of the ways we can achieve that.

Both recommendations include using TypeScript’s implementation of Intersection Types which combines multiple types into one allowing you to add together existing types to get a single type that has all the features you need. The first example below shows how to accomplish this using default imports from the mapping libraries and the second example outlines how to accomplish the same effect with named imports.

Example 1: Intersection Types with Default Imports

1// Mapbox GL Geocoder
2// GitHub: https://github.com/mapbox/mapbox-gl-geocoder
3// Types: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/mapbox__mapbox-gl-geocoder/index.d.ts
4
5import mapboxgl = require("mapbox-gl");
6import maplibregl = require("maplibre-gl");
7
8type MapGl = mapboxgl & maplibregl;

Example 2: Intersection Types with Named Imports

 1// Mapbox GL Draw
 2// GitHub: https://github.com/mapbox/mapbox-gl-draw
 3// Types: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/mapbox__mapbox-gl-draw/index.d.ts
 4
 5import { IControl as MapboxIControl, Map as MapboxMap } from 'mapbox-gl';
 6import { IControl as MaplibreIControl, Map as MaplibreMap } from 'maplibre-gl';
 7
 8type IControl = MapboxIControl & MaplibreIControl;
 9type Map = MapboxMap & MaplibreMap;
10
11declare class PluginName implements IControl {
12    ...
13    onAdd(map: Map): HTMLElement;
14    onRemove(map: Map): any;
15    ...
16}