Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrating from @types/mapbox-gl to first-class TypeScript typings #13203

Open
stepankuzmin opened this issue Jun 18, 2024 · 25 comments
Open

Migrating from @types/mapbox-gl to first-class TypeScript typings #13203

stepankuzmin opened this issue Jun 18, 2024 · 25 comments
Labels
types Issues related to the public TypeScript typings

Comments

@stepankuzmin
Copy link
Contributor

stepankuzmin commented Jun 18, 2024

Migrating from @types/mapbox-gl to first-class TypeScript typings

The GL JS v3.5.0 release marks a significant transition for GL JS, moving from Flow to TypeScript. While we have maintained backward compatibility where possible, the community typings @types/mapbox-gl are not fully compatible with the new first-class typings. Users relying on the community typings may experience breaking changes. This guide will help you migrate to the new first-class typings and resolve common issues.

Please feel free to comment on this issue if you encounter difficulties during migration. Share your experiences and suggestions to support one another.

Updating GL JS

Install the latest version of GL JS and remove the @types/mapbox-gl dependency.

npm install [email protected]
npm uninstall @types/mapbox-gl

Run the TypeScript compiler (tsc) to check for the errors.

Common issues

The migration should be straightforward since there's no need to change how you interact with the API; only the types have changed. However, you may encounter some common issues.

Dangling @types/mapbox-gl

Ensure you're using the latest version of GL JS and have removed the @types/mapbox-gl dependency.

Deprecated Features

Community typings provided features deprecated since v1 and v2, such as the optimizeForTerrain map option, tiledata and tiledataloading events, zoom and property functions, certain exports like the mapboxgl.Control. Please refer to the compatibility test suite in test/build/typings/compatibility-test.ts, used to test first-class typings compatibility with the community typings. Tests incompatible with mapbox-gl typings are marked with @ts-expect-error - incompatible.

Naming Conventions

Community typings' naming convention slightly differs from those provided with GL JS:

  • Style-Spec JSON objects in GL JS are suffixed with *Specification (e.g., StyleSpecification, LayerSpecification), whereas in @types/mapbox-gl, there's no suffix (e.g., Style, AnyLayer).
  • GL JS doesn't provide separate layout and paint property types like FillLayout and FillPaint. Instead, use TypeScript Indexed Access Type FillLayerSpecification['layout'] for layout properties and FillLayerSpecification['paint'] for paint properties.
  • Source implementation types in GL JS are exported without the Impl suffix. For example, VectorSourceImpl is exported as VectorSource.
  • Community typings' Any* types follow the same pattern: AnySourceData becomes SourceSpecification in GL JS, AnyLayer becomes LayerSpecification, and AnySourceImpl becomes Source.

Note: We've created aliases where possible (e.g., MapboxOptions as an alias to MapOptions) and marked these aliases as @deprecated in the first-class typings (this might be visible in your editor, e.g., with strikethrough style in VSCode IntelliSense). However, due to potential collisions, we couldn't create aliases for all cases. For example, we couldn't alias Source to SourceSpecification because GL JS already exports Source (same as AnySourceImpl in community typings). We recommend using the new naming convention, but aliases will help you migrate smoothly. Please note that these deprecated types will be removed in future releases.

@stepankuzmin stepankuzmin pinned this issue Jun 19, 2024
@stepankuzmin stepankuzmin changed the title Upgrading to GL JS v3.5.0 Migrating from @types/mapbox-gl to the Mapbox GL JS types Jun 19, 2024
@stepankuzmin stepankuzmin changed the title Migrating from @types/mapbox-gl to the Mapbox GL JS types Migrating from @types/mapbox-gl to first-class TypeScript typings Jun 19, 2024
@stepankuzmin stepankuzmin changed the title Migrating from @types/mapbox-gl to first-class TypeScript typings Migrating from @types/mapbox-gl to first-class TypeScript typings Jun 19, 2024
@slavanga
Copy link

Some learnings from my migration:

  • Replaced some deprecated types with no issues
  • Had to install @types/geojson to keep types like GeoJSON.FeatureCollection['features'] working
  • Custom event types (e.g. flystart or flyend) need to be casted when binding them: mapRef.current.on('flystart' as mapboxgl.MapEvent, () => {})

In the code below I'm getting a TS error for the base key. Any pointers how to fix or refactor that?

Object literal may only specify known properties, and 'base' does not exist in type 'ExpressionSpecification | { type: "exponential"; stops: [number, number][]; } | { type: "interval"; stops: [number, number][]; } | { type: "exponential"; stops: [...][]; property: string; default?: number | undefined; } | ... 5 more ... | { ...; }'.ts(2353)
mapRef.current?.addLayer({
  id: 'unclustered-point',
  type: 'circle',
  source: SOURCE_ID,
  filter: ['!', ['has', 'point_count']],
  paint: {
    'circle-radius': {
      base: 1.5, // <-- TS error here
      stops: [
        [4, 8],
        [10, 16],
      ],
    },
    'circle-color': circleColor,
  },
});

@stepankuzmin
Copy link
Contributor Author

Hi @slavanga,

Thanks for sharing your learnings! The error in your case is caused by using the deprecated zoom function syntax. While the older syntax is still supported, the type we use for data-driven expressions doesn’t include it. You can refer to this comparison as an example of migrating to the newer syntax:

FunctionExpression
// make circles larger as the user
// zooms from z12 to z22
'circle-radius': {
    'base': 1.75,
    'stops': [[12, 2], [22, 180]]
},
// make circles larger as the user 
// zooms from z12 to z22
'circle-radius': [
    "interpolate",
    ["exponential", 1.75],
    ["zoom"],
    12, 2,
    22, 180
],

In your case, it should be:

'circle-radius': [
    'interpolate',
    ['exponential', 1.5],
    ['zoom'],
    4, 8,
    10, 16
]

I understand that it may not be convenient, so in the stable release, we could extend the type we provide to include the older syntax, making the migration process smoother for everyone.

@slavanga
Copy link

@stepankuzmin Got it! Thank you for the detailed explanation. I actually think the new syntax is more clear.

@driera
Copy link

driera commented Jul 5, 2024

Hi, I'm starting the migration to v3.5 and so far all deprecated types are easy to update, except one, which I'm having problems with:

MapboxGeoJSONFeature has been deprecated in favor of GeoJSONFeature, but typescript complains that it's not being exported. Any idea?

@happy-turtle
Copy link

happy-turtle commented Jul 5, 2024

I started the migration today and it went mostly well, but I got one small issue: At one point we use CustomLayerInterface, but this interface is not exported. As the specification for Map.addLayer looks like this Map$1.addLayer(layer: LayerSpecification | CustomLayerInterface, beforeId?: string), I would assume the CustomLayerInterface should be exported?

Similarly EasingOptions is not exported for e.g. flyTo().

@vbraun
Copy link

vbraun commented Jul 5, 2024

Some more feedback

  1. MapboxGeoJSONFeature has been deprecated in favor of GeoJSONFeature, but the latter doesn't have the source field. In the typings it is called QueryFeature but that is not exported. So if you need to name it and access the source field you have to do something fugly like

    type QueryFeature = ReturnType<Map['queryRenderedFeatures']>[0];
    
  2. GeolocateControl.on does not support the 'trackuserlocationstart' and 'trackuserlocationend' events, fails with

    Argument of type '"trackuserlocationstart"' is not assignable to parameter of type 'MapEvent'
    

    Really GeolocateControl.on should extend Evented.on with the additional event types.

  3. In general the map event typings are worse than the community-provided typings. You can't express that the click event has a point field, and the idle event does not. Ok you get a star for actually having first-party typings, but pretty please have a good look at the community-provided ones for improvements.

@airbreather
Copy link

airbreather commented Jul 5, 2024

For Angular applications that use the ever-stagnating ngx-mapbox-gl package which provides Angular wrappers around the Mapbox types, you must now set skipLibCheck to true in your TypeScript config, in addition to the legacy peer deps workaround that you have had to do for a while now.

@chunlampang
Copy link

chunlampang commented Jul 8, 2024

Some types are not exported, such as Marker Options, ControlPosition, EasingOptions, StyleImageInterface

@chunlampang
Copy link

const scale = new mapboxgl.ScaleControl({ maxWidth: 80 });
map.addControl(scale, controlsPosition);

Argument of type 'ScaleControl' is not assignable to parameter of type 'IControl'. Types of property '_setLanguage' are incompatible.

@stepankuzmin
Copy link
Contributor Author

Thanks for the feedback. We appreciate your patience while we enhance the developer experience. We are actively working on improving TypeScript support, and the upcoming patch release will address some of the issues highlighted here. Stay tuned for updates.

@samuelcole
Copy link

samuelcole commented Jul 8, 2024

three things (and some of this could be ReactMapGL feedback):

map.on("click", (e: mapboxgl.MapMouseEvent) => {

seems like typescript should be able to tell which event string goes to which event type, so having that set up would be nice

onLoad={(e) => setMap(e.target as Map)}

a little annoying that the target is unknown

map.addLayer({
  id: "containedZipCodes",
  type: "fill",
  source: {
  ~~~~~~
    type: "vector",
    url: "mapbox://mapbox.boundaries-pos4-v4",
  },
  slot: "bottom",
  "source-layer": "boundaries_postal_4",
  paint: getPaint(allZipCodes),
  });

Type '{ type: string; url: string; }' is not assignable to type 'string'

when i change it to the string "mapbox://mapbox.boundaries-pos4-v4" it does not work, while this object does, so i suspect the type is wrong

i'll add // @ts-expect-error -- source needs to define a vector object while i wait for a fix for that

@chunlampang
Copy link

new mapboxgl.Popup().on('close', () => {});

Argument of type '"close"' is not assignable to parameter of type 'MapEvent'.

@jeremy-wl-app-lite
Copy link

I get this error in node_modules\mapbox-gl\dist\mapbox-gl.d.ts

Property 'tileID' in type 'ImageSource' is not assignable to the same property in base type 'ISource'. Type 'CanonicalTileID | null | undefined' is not assignable to type 'CanonicalTileID | undefined'. Type 'null' is not assignable to type 'CanonicalTileID | undefined'.

ImageSource.titleId: CanonicalTileID | null | undefined;
ISource.tileID?: CanonicalTileID;

@olivvybee
Copy link

olivvybee commented Jul 11, 2024

The types in 3.5.1 are disallowing passing a number as the padding option in map.fitBounds():

map.fitBounds(bounds, {
    maxZoom: 12,
    padding: 80,
//  ~~~~~~~
//  Type 'number' is not assignable to type 'PaddingOptions'. ts(2322)
//  (property) padding?: PaddingOptions | undefined
});

According to the API reference this should still be supported (and my brief skim of the code confirms this)

@chkl
Copy link

chkl commented Jul 17, 2024

I encountered a few types that are used for arguments on public methods but are not exported:

  • CustomLayerInterface
  • QueryFeature
  • FeatureSelector

@stepankuzmin stepankuzmin added the types Issues related to the public TypeScript typings label Jul 17, 2024
@stepankuzmin
Copy link
Contributor Author

Hi everyone. We've just published the GL JS v3.5.2 patch release, which includes TypeScript API improvements such as strongly typed Map event listeners, improved type narrowing, and explicit exports for some previously missed types. Please try out the new version and let us know how it works for you.

Thanks again for the feedback. I'll keep this issue open for any new findings.

@orosmatthew
Copy link

I believe type CircleLayerSpecification.filter should be type FilterSpecification | ExpressionSpecification instead of just FilterSpecification

@ezzatron
Copy link

@stepankuzmin A couple of remaining type issues in 3.5.2:

Screenshot 2024-07-19 at 09 12 21 Screenshot 2024-07-19 at 09 12 45

@chunlampang
Copy link

chunlampang commented Aug 9, 2024

map.addLayer({
  'id': 'tower',
  'type': 'model',
  'source': 'model',
  'layout': {
    'model-id': ['get', 'model-uri']
  },
  'paint': {
    'model-cast-shadows': true,
  }
});

Type 'boolean' is not assignable to type '[string, ...any[]]'.ts(2322)
(property) "model-cast-shadows"?: ExpressionSpecification | undefined

@chunlampang
Copy link

map.addLayer({
  'id': 'tower',
  'type': 'model',
  'source': 'model',
  'layout': {
    'model-id': ['get', 'model-uri']
  },
  'paint': {
    'model-rotation':  [0.0, 0.0, ['get', 'rotate']],
  }
});

Type '[number, number, string[]]' is not assignable to type 'DataDrivenPropertyValueSpecification<[number, number, number]> | undefined'.
Type '[number, number, string[]]' is not assignable to type 'ExpressionSpecification | [number, number, number]'.ts(2322)
(property) "model-rotation"?: DataDrivenPropertyValueSpecification<[number, number, number]> | undefined

@chunlampang
Copy link

map.setLights([
  {
    "id": "directional",
    "type": "directional",
    "properties": {
      "cast-shadows": true,
    }
  }
])

Type 'boolean' is not assignable to type '[string, ...any[]]'.ts(2322)
(property) "cast-shadows"?: ExpressionSpecification | undefined

@Wouter125
Copy link

Wouter125 commented Aug 28, 2024

Spotted a small little mistake when updating from 3.4.0 to 3.6.0 and using the mapbox-gl types. TransformRequestFunction seems to be deprecated and replaced by RequestTransformFunction. But RequestTransformFunction is not exposed inside the package causing type issues. Any chance RequestTransformFunction can get exposed on 3.6.1?

Apart from that seems like the Map type references to itself or something because as soon as I try to pass it as an argument through one of my functions; Type instantiation is excessively deep and possibly infinite.ts(2589). It's not a major issue and understandable from the Map perspective, but maybe it can be addressed.

@MrDiablon
Copy link

Hello,

It's look like a mistake is made for the function addLayer

My code:

map?.addLayer(
      {
        id,
        layout: {
          'line-join': 'round',
          'line-cap': 'round',
        },
        paint: {
          'line-color': ['get', 'color'],
          'line-width': 6,
        },
        source: {
          type: 'geojson',
          data: {
            type: 'FeatureCollection',
            features: features(),
          },
        } as unknown as mapboxgl.GeoJSONSource,
        type: 'line',
      },
      'lines',
    )
  }

I got the next error:

image

I check the documentation and we can use an object and it's required when the type isn't custom or background like in my case

@darkbasic
Copy link

Apart from that seems like the Map type references to itself or something because as soon as I try to pass it as an argument through one of my functions; Type instantiation is excessively deep and possibly infinite.ts(2589). It's not a major issue and understandable from the Map perspective, but maybe it can be addressed.

This is pretty annoying, any chance to fix it?

@darkbasic
Copy link

I also don't quite understand why new mapboxgl.Map() returns Map$1 instead of mapboxgl.Map:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
types Issues related to the public TypeScript typings
Projects
None yet
Development

No branches or pull requests