Dealing with Mapbox hidden symbols (3. Augmenting types)

2022-09-24

thumbnail

I have been working on a utility library for Mapbox GL JS, that deals with symbols hidden by another symbol on the screen. This is the third blog post of the series that will walk you through the development of the library.

Background

In the last two blog posts of this series, I showed,

In this blog post, we tackle a remaining issue that some types in Mapbox GL JS (mapbox-gl-js) were not available for TypeScript.

The library (mapbox-collision-boxes) is available on my GitHub repository.

TypeScript?

TypeScript is a variant of JavaScript, which provides powerful typing features. Please refer to the official website of TypeScript for more details. This page[1] can help understand the difference between TypeScript and JavaScript. I have chosen TypeScript for the implementation language of mapbox-collision-boxes.

Is mapbox-gl-js typed?

Yes, but mapbox-gl-js is typed with Flow which is another flavor of JavaScript for typing. Unfortunately, type definitions on Flow are not compatible with TypeScript. So we need external type definitions of mapbox-gl-js for TypeScript. Thanks to the hard work of contributors from the TypeScript community, these types are available as @types/mapbox-gl*.

* The latest version of @types/mapbox-gl was 2.7.5 when I was writing this blog post, and it was based on mapbox-gl-js version 2.7 while the then latest version was 2.10. However, I have not faced any issues related to the version difference so far.

What @types/mapbox-gl missing

@types/mapbox-gl does not expose some properties and types on which mapbox-collision-boxes depends. For instance, the class Map (@types/mapbox-gl/index.d.ts#L201-L602) omits the property style: Style. @types/mapbox-gl neither defines the class Bucket nor SymbolBucket.

How can we circumvent it?

One may be inclined to give up type checks, but this should be a last resort.

We can use module augmentation to enhance existing type definitions of mapbox-gl-js. "Module augmentation" is a slightly advanced TypeScript feature. Please refer to the TypeScript documentation for more details.

For instance, the following snippet adds the property style: Style to the class Map:

import { Map, Style } from 'mapbox-gl';

declare module 'mapbox-gl' {
    interface Map {
        style: Style;
    }
}

After the above declaration, you can access Map#style in a type-safe manner.

For Bucket and SymbolBucket, we can just add interfaces for them since they are simply missing in @types/mapbox-gl:

export interface Bucket {}

export interface SymbolBucket extends Bucket {
  bucketInstanceId: number;
  // ... other properties
}

I also have introduced a utility function that tests if a given Bucket is a SymbolBucket (mapbox-collision-boxes/private/mapbox-types.ts#L232-L234), which uses another TypeScript feature "type predicates".

export function isSymbolBucket(bucket: Bucket): bucket is SymbolBucket {
  return (bucket as SymbolBucket).symbolInstances !== undefined;
}

You can find minimum definitions necessary to implement mapbox-collision-boxes on my GitHub repository.

Wrap-up

In this short blog post, I have shown how to extend existing types in mapbox-gl-js (@types/mapbox-gl) while introducing the following TypeScript features,

mapbox-collision-boxes is available on my GitHub repository.

Reference

  1. TypeScript for the New Programmer - https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html