type: guide title: "honua-maplibre migration target" description: "The honua-maplibre codemod target: MapLibre-native output for the layer/map/view core, widget kit registration, CLI flags, report fields and the manual gaps." resource: "https://www.npmjs.com/package/@honua/honua-migrate"

honua-maplibre migration target

The honua-maplibre codemod target rewrites a curated subset of @arcgis/core constructors directly into @honua/sdk-js/map helpers plus MapLibre GL source/layer definitions, instead of routing them through the Esri-shaped compat shims. It is the codemod option that produces MapLibre-native output for apps that want to leave the ArcGIS JS API behind end-to-end.

Canonical migration workbench

examples/migration-workbench is the qualified arcgis-migration golden journey for the complete migration experience. The repository build invokes the real honua-migrate CLI against the Honua-authored geoservices-source-app fixture and commits a deterministic report, patch, widget guidance, MapLibre assessment, generated compat target, and SHA-256 manifest. The browser only projects and revalidates those artifacts; it does not contain a second transform, accept uploads, read credentials, or perform cloud import.

Which import

A new 2D map imports createHonua from @honua/sdk-js and maplibreRenderer from @honua/sdk-js/runtime. Server attach uses HonuaClient from @honua/sdk-js/honua. An ArcGIS app uses @honua/sdk-esri-compat and @honua/honua-migrate. Widget paint registers @honua/app-platform/web-components. @honua/sdk is HonuaClient, not createHonua.

Widget kit registration

This is a required step, not a footnote. A shim that constructs a HonuaWidgetHost paints only after the application registers the kit. A shim that does not construct a host stays state-model-only whether or not a kit is registered, and it does not emit the missing-kit diagnostic.

Importing @honua/sdk-esri-compat does not load the kit. The compat entry is bundle-budgeted, and any import of the kit from that entry would pull the component set into every compat bundle. The application registers the tag.

Paint one tag through the side-effect-free registry in src/controls/registry.ts. registerComponent dynamically imports web-components/elements.js, which does not register tags on import, and calls defineHonuaWebComponent for that catalog id only. Pass the registered constructor to registerHonuaWidgetKit and omit defineHonuaWebComponents. If the kit object includes defineHonuaWebComponents, the host calls it and every tag in the kit is defined.

import { registerComponent } from "@honua/app-platform/controls";
import { registerHonuaWidgetKit } from "@honua/sdk-esri-compat";

await registerComponent("web-components.legend");
registerHonuaWidgetKit({
  HonuaLegendElement: customElements.get("honua-legend"),
});

@honua/app-platform/web-components is the widget package. Importing it, or the deprecated @honua/sdk-js/web-components forwarder, evaluates defineHonuaWebComponents() and defines every tag. Use that import when the app wants the whole kit. Use registerComponent when it wants one tag.

The honua-compat codemod in @honua/honua-migrate inserts one registerHonuaWidgetKit(() => import("@honua/sdk-js/web-components")) call when the file constructs a hosted compat widget (Legend, LayerList, TimeSlider, Search, Measurement, Editor, FeatureForm, FeatureTable, Bookmarks, BasemapGallery, BasemapToggle, Locate, Sketch, Print, Popup, Zoom, Home, ScaleBar, Compass, Fullscreen, Attribution, or Directions). That emitted import defines every tag. esri-leaflet output does not insert the call. keep-esri output stays byte-identical. Replace the emitted loader with the registerComponent call above when one tag is enough.

Register before you construct widgets. registerHonuaWidgetKit is idempotent, and passing undefined unregisters: painting shims return to state-model-only mode and any component they had already mounted is removed from its container on the next refresh.

Without a kit, a painting shim still keeps its state model — legend.items stays populated, layerList.toggle() still mutates layers, events still fire — and the container you passed stays empty. The first mount in that state emits a one-time diagnostic (honua-io/honua-sdk-js#957):

legend.eventBus.on("widget-kit.missing", (event) => {
  reportFirstRunProblem(event.payload.message);
});

The diagnostic fires once per runtime, on the first mount that finds no kit — not once per widget instance — so subscribe before constructing widgets. It re-arms whenever registerHonuaWidgetKit is called again.

@honua/sdk-esri-compat does not ship the kit. Install @honua/app-platform to register tags. FeaturesCompat, AttachmentsCompat, and ScaleRangeCompat construct hosts and are not on the stable client-compat entry.

Shim Tag
AttributionCompat honua-attribution
AttachmentsCompat honua-attachments
BasemapGalleryCompat, BasemapToggleCompat honua-basemap-control
BookmarksCompat honua-bookmarks
CompassCompat honua-compass
DirectionsCompat honua-directions
EditorCompat honua-editor
FeatureFormCompat honua-feature-editor
FeatureTableCompat honua-feature-table
FeaturesCompat honua-feature-pager
FullscreenCompat honua-fullscreen
HomeCompat honua-home
LayerListCompat honua-layer-list
LegendCompat honua-legend
LocateCompat honua-locate-control
MeasurementCompat honua-measurement
PopupCompat honua-feature-inspection
PrintCompat honua-print-export
ScaleBarCompat honua-scale-bar
ScaleRangeCompat honua-scale-range
SearchCompat honua-search
SketchCompat honua-sketch-control
TimeSliderCompat honua-time-slider
ZoomCompat honua-zoom

These container-bearing shims do not construct a host. They stay state-model-only: AreaMeasurement2DCompat, BasemapLayerListCompat, CoordinateConversionCompat, DistanceMeasurement2DCompat, ExpandCompat, FeatureCompat, FeatureTemplatesCompat, SwipeCompat, TableListCompat, TrackCompat.

How it differs from the other targets

src/migration/codemod.ts is the in-repo suite, not the npm program. @honua/sdk-js/migration forwards to @honua/honua-migrate, and the CLI is npx honua-js-migrate. The suite exposes three targets selected with --target on the CLI:

Target Output shape What it rewrites natively
honua-compat (alias honua) @honua/sdk-esri-compat shims that mimic the ArcGIS JS API surface Every kind in REWRITE_SPECS (SUPPORTED_ARCGIS_MODULE_KIND_BY_PATH) — full 2D constructor surface (layers, views, widgets, controls, tasks, support).
honua-maplibre @honua/sdk-js/map helpers + raw MapLibre style/layer objects The five kinds in HONUA_MAPLIBRE_NATIVE_KINDS: feature-layer, map-image-layer, tile-layer, map, map-view. Everything else still falls through to a manual TODO.
esri-leaflet Mixed esri-leaflet primitives + compat fallback feature-layer, map-image-layer, tile-layer natively; the remaining 2D surface via ESRI_LEAFLET_COMPAT_FALLBACK_KINDS.

honua-compat is the broadest deterministic option — it covers the full REWRITE_SPECS matrix and is the default. honua-maplibre trades surface coverage for a MapLibre-native output: it only emits native helpers for the layer/map/view core, and everything else (widgets, controls, renderers, geometry constructors, tasks) becomes a manual TODO because there is no @honua/sdk-js/map helper for it yet. esri-leaflet sits in between — three native layer kinds with a compat fallback for the rest.

Auto-migrated, assisted, and unsupported kinds

The exact set of kinds the honua-maplibre target rewrites natively is defined in src/migration/codemod.ts as HONUA_MAPLIBRE_NATIVE_KINDS and consumed by TARGET_SUPPORTED_KINDS["honua-maplibre"]:

The helper signatures live in src/map/maplibre-target.ts.

Rendering feature-service layers (custom source → geojson)

createHonuaTileServiceLayer and createHonuaMapServiceLayer emit MapLibre-native raster sources that render as soon as you add them to a map. createHonuaFeatureServiceLayer is different: its source uses the custom honua-feature-service type, which MapLibre's renderer does not understand. Adding that source to a maplibre-gl Map directly is a silent no-op — no error, no features.

To render feature-service layers, run them through the MapLibre runtime adapter exported from @honua/sdk-js/map. It fetches features via the SDK query path and produces a standard geojson source MapLibre renders natively:

import * as maplibregl from "maplibre-gl";
import { HonuaClient } from "@honua/sdk-js";
import {
  createHonuaFeatureServiceLayer,
  loadHonuaFeatureServiceGeoJson,
} from "@honua/sdk-js/map";

const client = new HonuaClient({ baseUrl: "https://gis.example.com" });
const layer = createHonuaFeatureServiceLayer({ url, outFields: ["*"] });

// Replace the custom honua-feature-service source with a fetched geojson source.
const source = await loadHonuaFeatureServiceGeoJson(client, layer.source.url);
map.addSource(layer.sourceId, source);
map.addLayer(layer.layer);

For a whole HonuaMap, registerHonuaFeatureServiceSources(map, honuaMap, client) walks every honua-feature-service source and live-patches it on the MapLibre map (adding a new geojson source or calling setData on an existing one). Both helpers live in src/map/feature-service-adapter.ts.

Every other constructor kind in REWRITE_SPECS — geometries, symbols, renderers, WebMap, SceneView, GraphicsLayer, GroupLayer, VectorTileLayer, GeoJSONLayer, WMSLayer, WFSLayer, ImageryLayer, FeatureFilter, all widgets (LayerList, Legend, Popup, Search, Sketch, Editor, TimeSlider, …), all controls (Home, Zoom, Compass, ScaleBar, Fullscreen, BasemapToggle, …), Query, OAuthInfo, IdentityManager, esriRequest, esriConfig, reactiveUtils, RouteTask, etc. — is not in HONUA_MAPLIBRE_NATIVE_KINDS. The codemod emits a manual TODO with a reason at each call site and lists the unhandled ArcGIS module under unhandledArcGisModules in the report. The runtime parity matrix (src/migration/runtime-matrix.ts::inferHonuaMapLibreRuntimeStatus) categorizes these surfaces as assisted for honua-maplibre, except for the feature-layer, map-image-layer, and the map-view.navigation-go-to capability which are tagged native.

Use the canonical fixture test/fixtures/client-maplibre-simple-app/ as a worked example — it imports Map, MapView, FeatureLayer, MapImageLayer, and TileLayer from @arcgis/core and exercises exactly the kinds that honua-maplibre can rewrite natively.

CLI flag and invocations

The codemod is selected with --target honua-maplibre on the codemod, fixtures, and demo subcommands of node dist/src/migration/cli.js. The accepted target tokens are honua (alias of honua-compat), honua-compat, honua-maplibre, and esri-leaflet — anything else is rejected by the parser.

# Dry-run the codemod against the bundled MapLibre fixture
node dist/src/migration/cli.js codemod test/fixtures/client-maplibre-simple-app \
  --target honua-maplibre \
  --report reports/maplibre-fixture-report.json

# Write the rewrite in place with inline TODOs for manual sites
node dist/src/migration/cli.js codemod test/fixtures/client-maplibre-simple-app \
  --target honua-maplibre \
  --write --annotate-todos \
  --report reports/maplibre-fixture-report.json

# Run the codemod against the hand-written parcel viewer example
node dist/src/migration/cli.js codemod examples/geoservices-source-app \
  --target honua-maplibre \
  --report reports/geoservices-source-app-maplibre-report.json

# Per-fixture readiness metrics for a single MapLibre fixture, no gating
node dist/src/migration/cli.js fixtures test/fixtures \
  --target honua-maplibre \
  --fixtures client-maplibre-simple-app \
  --report reports/maplibre-fixture-metrics.json

The shared codemod flags (--write, --annotate-todos, --report <path>, --compat-import-path <pkg>, --fail-on-manual, --fail-on-unhandled, --fail-on-blocked, --max-manual-ratio, --max-manual-intervention-ratio) all apply unchanged. With --target honua-maplibre the codemod only rewrites the five native kinds; manual TODOs and unhandled-module entries are expected to appear for any constructor outside that set, and gating flags like --fail-on-manual will fail closed against apps with widgets, renderers, or non-2D-core surfaces.

Migration report fields

The report writer is buildJsMigrationReport (src/migration/report.ts); its JsMigrationReport shape is what gets written to the path passed to --report. The fields you can key off when consuming a honua-maplibre report are:

The parity matrix tooling (matrix / runtime-matrix subcommands) also emits a honuaMapLibre summary block alongside honuaCompat and esriLeaflet with native/assisted/unsupported counts — see summarizeJsParityMatrix and summarizeJsRuntimeParity.

Manual gaps

honua-maplibre ships the native-rewrite path for the layer/map/view core; the remaining acceptance items live in docs/migration-punch-list.md and stay open until they ship. The notable ones, framed against the punch list:

For the broader migration story — including the honua-compat parity surface, the codemod gates, and the test corpus — start at docs/migration-punch-list.md and the fixture-only Esri sample corpus in docs/esri-sample-corpus.md.