|
| 1 | +# Plugins |
| 2 | + |
| 3 | +`ts-maps` is designed so most extension points do not need a plugin |
| 4 | +system at all — you can drop in custom layers, custom services, |
| 5 | +or custom controls using the same APIs the built-ins use. This page is |
| 6 | +an orientation for plugin authors and a pointer to the community |
| 7 | +registry. |
| 8 | + |
| 9 | +## Extension surfaces |
| 10 | + |
| 11 | +### Custom layers |
| 12 | + |
| 13 | +Any class that implements the `Layer` contract can be `addTo(map)`'d. |
| 14 | +For GPU-rendered overlays on top of the tile panes, prefer |
| 15 | +`CustomLayerInterface` — it hooks into the same render loop as the |
| 16 | +built-in vector-tile renderer: |
| 17 | + |
| 18 | +```ts |
| 19 | +map.addCustomLayer({ |
| 20 | + id: 'wind', |
| 21 | + type: 'custom', |
| 22 | + onAdd(map, gl) { /* compile shaders */ }, |
| 23 | + render(gl, projectionMatrix) { /* draw frame */ }, |
| 24 | + onRemove(map, gl) { /* release resources */ }, |
| 25 | +}) |
| 26 | +``` |
| 27 | + |
| 28 | +See [concepts/3d.md](./concepts/3d.md) for the full interface. |
| 29 | + |
| 30 | +### Custom sources |
| 31 | + |
| 32 | +For non-tile data, hook into the style-spec source registry by |
| 33 | +calling `map.addSource(id, { type: 'geojson', data: … })` or |
| 34 | +`{ type: 'vector', tiles: [...] }`. If you need a bespoke tile format, |
| 35 | +extend `GridLayer` or `TileLayer` directly — they handle the pan / |
| 36 | +zoom bookkeeping so your plugin only needs a `createTile(coords, done)`. |
| 37 | + |
| 38 | +### Custom services |
| 39 | + |
| 40 | +The `services` interfaces (`GeocoderProvider`, `DirectionsProvider`, |
| 41 | +`IsochroneProvider`, `MatrixProvider`) are public: |
| 42 | + |
| 43 | +```ts |
| 44 | +import type { GeocoderProvider } from 'ts-maps/services' |
| 45 | + |
| 46 | +class MyProvider implements GeocoderProvider { |
| 47 | + name = 'my-provider' |
| 48 | + async search(query: string) { /* ... */ } |
| 49 | + async reverse(center) { /* ... */ } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +Hand the instance to whichever consumer expects a provider (e.g. your |
| 54 | +own geocoder control). |
| 55 | + |
| 56 | +### Custom controls |
| 57 | + |
| 58 | +Any object with `onAdd(map)` / `onRemove(map)` can be passed to |
| 59 | +`map.addControl(ctrl, position)`. The four built-in controls |
| 60 | +(`ZoomControl`, `ScaleControl`, `AttributionControl`, `LayersControl`) |
| 61 | +are reference implementations. |
| 62 | + |
| 63 | +### Custom renderers |
| 64 | + |
| 65 | +The WebGL path is pluggable — `map.setRenderer('webgl' | 'canvas2d' | 'svg')` |
| 66 | +picks the backend for style-spec layers. If you want an entirely |
| 67 | +different backend, subclass `Renderer` and register it via the same |
| 68 | +setter; the map will rewire source hosts on the next paint. |
| 69 | + |
| 70 | +## Publishing a plugin |
| 71 | + |
| 72 | +Plugins are just npm packages. Follow these conventions so they're |
| 73 | +discoverable: |
| 74 | + |
| 75 | +- **Name:** `ts-maps-plugin-<name>` (unscoped) or `@<scope>/ts-maps-<name>`. |
| 76 | +- **Keywords:** include `ts-maps`, `ts-maps-plugin`, and the relevant |
| 77 | + extension surface (`geocoder`, `directions`, `layer`, `control`). |
| 78 | +- **Peer:** `"peerDependencies": { "ts-maps": "^0.2.0" }`. |
| 79 | +- **Readme:** add a "Requires" section listing any external services |
| 80 | + (tile endpoints, API keys, etc.). |
| 81 | + |
| 82 | +## Plugin registry |
| 83 | + |
| 84 | +Plugins that meet the naming convention above auto-surface in the npm |
| 85 | +search: <https://www.npmjs.com/search?q=ts-maps-plugin>. A curated list |
| 86 | +lives at <https://ts-maps.dev/plugins>. |
| 87 | + |
| 88 | +Submissions welcome — open a PR against `docs/plugins.md` with a short |
| 89 | +description and a link to your package. |
| 90 | + |
| 91 | +## Best practices |
| 92 | + |
| 93 | +- **Keep runtime deps to zero** if you can. `ts-maps` core has none; |
| 94 | + plugins that inherit that discipline are easier to ship. |
| 95 | +- **Declare `ts-maps` as a peer**, not a dependency — otherwise apps |
| 96 | + end up with two copies. |
| 97 | +- **Use subpath imports** (`ts-maps/style-spec`, `ts-maps/services`, …) |
| 98 | + so bundlers only pull in the slice you actually use. |
| 99 | +- **Fail soft.** If your plugin wraps a third-party API, handle 4xx / |
| 100 | + 5xx responses gracefully and surface them as a rejected Promise — |
| 101 | + don't throw synchronously from a `render()` callback. |
0 commit comments