developmentseed / developmentseed/deck.gl-raster

esm.sh-bundled @developmentseed/deck.gl-geotiff loses proj4 projection classes (utm, lcc, …); breaks CDN / no-build-step consumers

Open
#560 10 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
228
Forks
29
Avg merge
12h 27m
Merged PRs (30d)
4

Description

## Summary

I'm having problems using @developmentseed/deck.gl-geotiff from esm.sh
\- Tyler

***(The following was written by Claude Code and reviewed by Tyler)***

Loading `@developmentseed/deck.gl-geotiff@0.7.0` from a CDN that aggressively tree-shakes (esm.sh's `?bundle-deps`) drops proj4's non-fundamental projection classes from the bundle, so any COG whose CRS isn't `merc` / `longlat` / `tmerc` fails at the first proj4(sourceProjection, "EPSG:4326") call with:

`Error: Could not get projection name from: [object Object]`

## Repro

```

import { COGLayer } from "https://esm.sh/@developmentseed/deck.gl-geotiff@0.7.0?bundle-deps";

// Any WGS84 UTM COG — e.g. the same Sentinel-2 TCI used in cog-basic.
const url = "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/18/T/WL/2026/1/S2B_18TWL_20260101_0_L2A/TCI.tif";

// No `epsgResolver` supplied — uses the package default (epsg.io PROJJSON
// + wkt-parser), same as examples/cog-basic.
new COGLayer({ id: "cog", geotiff: url });

```

Result: the layer initializes, fetches metadata, calls `epsgResolver(32618)`, gets back a definition with `projName: "utm"`, hands it to p`roj4(...)`. proj4 throws — its `Projection.projections` registry doesn't have an entry for "utm".

## Why local builds work and CDN doesn't

The [examples/cog-basic](https://github.com/developmentseed/deck.gl-raster/tree/main/examples/cog-basic) app handles UTM, NZTM, EPSG:2056, etc. with no custom `epsgResolver` and no extra setup. Vite + Rollup keep proj4's projection-class side effects, so the locally-bundled proj4 has a complete `Projection.projections` registry.

esm.sh's `?bundle-deps` runs a more aggressive tree-shaker: from its POV, only `proj4`'s default-exported function is referenced from `@developmentseed/proj` and `cog-layer.ts`, so it eliminates the projection-class registrations as dead code.

I verified this is the failure mode by:

- Logging the def object returned from a custom resolver: `{ projName: "utm", zone: 18, datumCode: "WGS84", units: "m" }` — well-formed, but bundled proj4 still rejects it.
- Switching to `?bundle-deps&external=proj4` plus an HTML import map pointing both the consumer page and the bundled module at the same full `proj4` instance — works. Confirms the issue is the proj4 inside the bundle, not the def object.
- Substituting an equivalent `tmerc` def (`projName: "tmerc"`) for UTM — works, because `tmerc` is a fundamental projection that bundled proj4 retains. (Verified visually correct against the cog-basic example for EPSG:32618.)

## Why this matters

CDN consumers can't supply an HTML `` in every host environment. In particular:

- Embedded contexts (anywidget widgets, Observable, sandboxed iframes) can't reach the parent page's `<head>`.
- Inline `data:` URL iframes have null origins; an importmap inside doesn't help with workers, and authoring one per embed is awkward.

A no-build-step CDN flow is the simplest possible adoption path. The package works perfectly in a real build — but right now it silently fails on most projected CRSs when loaded from esm.sh.

## Suggested fixes (in increasing order of footprint)

1. One-line side-effect import in `packages/proj/src/index.ts`:

`import "proj4/lib/projections/index.js";`

This is a side-effect-only import; tree-shakers preserve it. It pulls in the same projection registrations proj4's main entry does, but in a form bundlers can't eliminate.

2. Explicit registration (smaller bundle than #1):

```
import { Projection } from "proj4/lib/Proj.js";
import utm from "proj4/lib/projections/utm.js";
import lcc from "proj4/lib/projections/lcc.js";
import aea from "proj4/lib/projections/aea.js";
import stere from "proj4/lib/projections/stere.js";
// …whichever set the package commits to supporting.
[utm, lcc, aea, stere /* … */].forEach((p) => Projection.projections.add(p));
```

3. Re-export proj4 from the public API so downstream consumers can register custom CRSs against the same instance (and bundlers can see the reference):

`export { default as proj4 } from "proj4";`

Useful regardless of the projections fix.

# Environment

- @developmentseed/deck.gl-geotiff@0.7.0
- @developmentseed/proj@0.7.0
- esm.sh build (?bundle-deps)
- Chrome 132 / Firefox 128 — both reproduce
- proj4@2.11.0 (the version esm.sh resolves)

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.