# GeoLibre Components Plugin Architecture: How It Wraps maplibre-gl-components Controls

> Discover the GeoLibre Components plugin architecture. Learn how it efficiently wraps maplibre-gl-components controls using a three-layer system for dynamic loading, caching, and lifecycle management.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: architecture
- Published: 2026-08-15

---

**The GeoLibre Components plugin uses a three-layer architecture—dynamic loader, constructor cache, and control manager—to lazily import, memoize, and lifecycle-manage UI controls from the maplibre-gl-components library.**

The **Components plugin** in GeoLibre serves as the integration bridge between the framework's store-driven architecture and the rich set of UI widgets provided by **maplibre-gl-components**. Located at [`packages/plugins/src/plugins/maplibre-components.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-components.ts), this plugin implements a sophisticated lazy-loading system that keeps initial bundle sizes small while providing resilient error handling and full state persistence capabilities.

## Three-Layer Architecture Overview

The **maplibre-gl-components wrapper architecture** separates concerns into distinct layers:

| Layer | Responsibility | Key Mechanism |
|-------|---------------|-------------|
| **Dynamic loader** | Runtime import of external packages | `defaultLoadComponentsModules()` with `Promise.all([import(...)])` |
| **Constructor cache** | Single-import guarantee across session | `componentsConstructorsPromise` memoisation |
| **Control manager** | Instantiation, mounting, lifecycle, state | `createComponentsControl()`, `mountComponentsControl()`, `activate`/`deactivate` |

## Dynamic Loading and Chunk Resilience

The plugin defers loading **maplibre-gl-components** until activation, using dynamic `import()` expressions. This enables **code-splitting** and protects against stale deployment artifacts.

```typescript
// packages/plugins/src/plugins/maplibre-components.ts (lines 38-46)
const defaultLoadComponentsModules = (): Promise<ComponentsModules> =>
  Promise.all([
    import("maplibre-gl-components"),
    import("maplibre-gl-splat").catch(() => null), // graceful fallback
  ]);

```

The **optional splat package** fails silently to `null`, preventing total UI failure when a chunk is unavailable. Failed primary imports throw a user-facing error with clear recovery instructions rather than cryptic module loader exceptions.

## Constructor Memoisation Pattern

The `getComponentsConstructors()` function implements a **singleton promise pattern** that guarantees exactly one network request per session while allowing retry after transient failures:

```typescript
// packages/plugins/src/plugins/maplibre-components.ts (lines 64-73)
export const getComponentsConstructors = (): Promise<ComponentsConstructors> => {
  componentsConstructorsPromise ??= loadComponentsModules()
    .then(([components, splat]) => {
      if (!components) {
        throw new Error(
          "The map controls could not be loaded … reload the page."
        );
      }
      const { AddVectorControl, BookmarkControl, … } = components;
      // Prefer dedicated splat exports, fall back to bundled copy
      const GaussianSplatControlClass = (splat?.GaussianSplatControl ??
        components.GaussianSplatControl) as GaussianSplatControlConstructor;
      …
      return { AddVectorControl, BookmarkControl, … };
    })
    .catch((e) => {
      componentsConstructorsPromise = null; // never cache failures
      throw e;
    });
  return componentsConstructorsPromise;
};

```

**Critical implementation detail**: The `.catch()` block resets `componentsConstructorsPromise` to `null`, ensuring that a failed load can be retried on the next activation attempt rather than returning the rejected promise indefinitely.

## Control Creation and GeoLibre Integration

Each control receives **typed options objects** (e.g., `ADD_VECTOR_OPTIONS`, `COG_RASTER_OPTIONS`, `PMTILES_OPTIONS`) that merge upstream defaults with GeoLibre-specific configurations:

- **CSS class prefixing**: all controls receive `"geolibre-"` prefixed class names
- **Position mapping**: grid and individual control positions derived from `componentsControlPosition`, `cogRasterControlPosition`, etc.
- **Store access**: the `app` instance passed through options enables callbacks into GeoLibre's layer management and dialog systems

The `ControlGrid` container is instantiated via `createComponentsControl()` at lines 146-150:

```typescript
const createComponentsControl = async (app: GeoLibreAppAPI): Promise<ControlGrid | null> => {
  const { ControlGrid: ControlGridClass } = await getComponentsConstructors();
  if (!pluginActive) return null;
  return new ControlGridClass(getComponentsOptions(app));
};

```

## Mounting and Lifecycle Management

**Activation** triggers async construction and map attachment:

```typescript
activate: (app) => {
  pluginActive = true;
  if (componentsControl) return mountComponentsControl(app);
  createAndMountComponentsControl(app);
},

```

**Deactivation** performs coordinated cleanup:

- **Revision bump**: `componentsControlRevision += 1` invalidates stale control references
- **Teardown cascade**: individual `teardown*` helpers remove each control from the map and detach listeners
- **State preservation**: control states are extracted before destruction for subsequent restoration

The full deactivation logic spans lines 177-187 and 186-199, handling edge cases like partially-initialized grids and interdependent control lifecycles.

## State Persistence for Panel Controls

Five controls implement **serializable state interfaces** for project save/restore:

- `BookmarkControl` – saved view locations
- `ViewStateControl` – map position/zoom/bearing
- `ColorbarGuiControl` – legend colorbar configuration
- `LegendGuiControl` – layer legend visibility and content
- `HtmlGuiControl` – custom HTML overlay content

The plugin attaches `setState` methods to these control instances (lines 292-305), which are invoked from store-derived restoration logic such as `restoreVisibleLayers` (lines 61-77).

## Test Harness and Injectable Loaders

The architecture supports **dependency injection for testing** through `__setComponentsModuleLoaderForTests`:

```typescript
import { __setComponentsModuleLoaderForTests } from "@geolibre/plugins";

__setComponentsModuleLoaderForTests(() => Promise.reject(new Error("forced error")));
await getComponentsConstructors(); // will reject
__setComponentsModuleLoaderForTests(null); // restore real loader

```

The dedicated test file [`tests/components-constructors-loader.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/components-constructors-loader.test.ts) validates error recovery, ensuring that preload failures result in clean fallback behavior rather than poisoned singleton state.

## Typical Integration Patterns

### Basic plugin activation

```typescript
import { maplibreComponentsPlugin } from "@geolibre/plugins";

app.addPlugin(maplibreComponentsPlugin);

```

### Translating control labels

```typescript
import { setBookmarkLabels } from "@geolibre/plugins";

setBookmarkLabels({
  captureStateLabel: "Inclure l'état complet du calque",
  exportLabel: "Exporter",
});

```

The `setBookmarkLabels` function (lines 35-42) mutates an internal `bookmarkLabels` object that `BookmarkControl` reads at instantiation time.

## Supporting Infrastructure

| File | Purpose |
|------|---------|
| [`packages/plugins/src/plugins/map-projection-utils.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/map-projection-utils.ts) | Enforces Mercator projection before component loading |
| [`packages/plugins/src/plugins/shared-deck-overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/shared-deck-overlay.ts) | Shared Deck.gl overlay for STAC search, Gaussian splat controls |
| [`packages/plugins/src/plugins/internal-layers.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/internal-layers.ts) | Excludes helper layers from ControlGrid UI enumeration |

## Summary

- **Three-layer architecture**: dynamic loader → constructor cache → control manager separates network, memoisation, and DOM concerns
- **Resilient imports**: optional packages fail gracefully; primary failures are recoverable and user-informative
- **Singleton promise pattern**: guarantees single network request with retry-after-failure semantics
- **Full lifecycle management**: activation, deactivation, and state persistence integrated with GeoLibre's store
- **Testable design**: injectable loaders and resettable promise state enable comprehensive unit testing

## Frequently Asked Questions

### How does the Components plugin avoid bundling maplibre-gl-components in the main application?

The plugin uses **dynamic `import()` expressions** in `defaultLoadComponentsModules()` rather than static ES module imports. This causes bundlers to split the dependency into a separate chunk that is only fetched when `activate()` runs, keeping the initial GeoLibre bundle minimal.

### What happens if maplibre-gl-components fails to load due to a network error or stale deployment?

The `getComponentsConstructors()` catch block resets `componentsConstructorsPromise` to `null` and throws a descriptive error message. Users see actionable text ("reload the page") rather than technical module loader failures, and the next plugin activation attempt triggers a fresh import rather than returning the cached rejection.

### Can I use the Components plugin with a custom build of maplibre-gl-components?

Yes. The plugin exposes `__setComponentsModuleLoaderForTests()` which can be repurposed to inject any compatible module loader. Replace the default `defaultLoadComponentsModules` with a function returning your custom package exports, maintaining the `[ComponentsModules, SplatModule | null]` tuple contract.

### Which controls support state persistence across project saves?

The plugin implements state interfaces for **BookmarkControl**, **ViewStateControl**, **ColorbarGuiControl**, **LegendGuiControl**, and **HtmlGuiControl**. Each receives a `setState` method attachment that GeoLibre's store invokes during project restoration workflows.