Skip to content

Conversation

@chrisgervang
Copy link
Collaborator

@chrisgervang chrisgervang commented Dec 3, 2025

Summary

This PR adds a comprehensive basemap-browser example application for testing deck.gl with multiple basemap providers across different frameworks and rendering modes.

Screenshot 2025-12-02 at 6 13 37 PM

Features

  • Multiple basemap providers: Google Maps, Mapbox, MapLibre, and MapLibre Globe
  • Framework coverage: Both Pure JS and React implementations for each provider
  • Interleaved mode testing: Toggle between shared GL context (interleaved) and separate contexts (overlaid)
  • Real-time metrics panel:
    • Device Pixel Ratio monitoring
    • Canvas dimensions (width x height)
    • Client size
    • WebGL drawing buffer size
  • Isolation: Pure JS implementations mount directly to DOM. Multiple React roots pattern for separation.

Use Case

This application makes it easy to test the resize/DPR bug fix across all 16+ test scenarios:

  • 3 map providers × 2 frameworks × 2 interleaved modes
  • Plus globe variants for Maplibre with proper projection ordering

Architecture Highlights

  • Two separate React roots: Control panel and map area are independent
  • Pure JS isolation: Pure JS code mounts directly via .mount() functions, no React in call stack
  • React components: Separate components in examples-react/ directory
  • Clean state management: Proper cleanup when switching between examples

Technical Details

  • Label rendering: Uses beforeId: 'watername_ocean' for MapLibre and slot: 'middle' for Mapbox
  • Continuous polling: Updates canvas metrics every 100ms for real-time feedback

Note

Introduces a TypeScript/React basemap-browser example to test deck.gl across Google Maps, Mapbox, and MapLibre (incl. globe) in Pure JS and React with interleaved toggle and live DPR/canvas metrics.

  • Examples • New app examples/basemap-browser
    • UI/Controls: src/control-panel.tsx adds example selector, interleaved toggle, and live metrics (DPR, canvas/client size, drawing buffer) with 100ms polling; layout defined in index.html.
    • Implementations:
      • Pure JS: src/examples-pure-js/{google-maps,mapbox,maplibre}.ts mount/unmount helpers; MapLibre globe projection support; Mapbox/Google Maps token handling.
      • React: src/examples-react/{google-maps-component,mapbox-component,maplibre-component}.tsx with MapboxOverlay/GoogleMapsOverlay integration and globe projection ordering.
    • Example registry: src/examples/index.ts defines provider×framework configs (Google Maps, Mapbox, MapLibre, MapLibre Globe) matching get-started setups.
    • Shared layers/constants: src/layers.ts provides airport layers with interleaved placement (slot/beforeId); src/constants.ts exports shared URLs/styles.
    • App entry: src/index.tsx manages dual React roots (controls/map), switches examples with cleanup.
    • Project setup: package.json (vite start script, deps), tsconfig.json, and README.md with usage and env var notes.

Written by Cursor Bugbot for commit 7255ab9. This will update automatically on new commits. Configure here.

- Comprehensive test application for deck.gl with multiple basemap providers
- Supports Google Maps, Mapbox, MapLibre, and MapLibre Globe
- Both Pure JS and React implementations for complete coverage
- Interleaved mode toggle for testing shared vs separate GL contexts
- Real-time metrics panel showing DPR, canvas dimensions, and drawing buffer
- Complete isolation between Pure JS and React implementations
- Fixed globe projection in interleaved mode (set projection before overlay)
- Labels render above deck.gl layers in interleaved mode using beforeId/slot
- TypeScript throughout with full type safety
- Covers all 16 test scenarios from resize/DPR bug fix
- Pure JS code now mounts directly to DOM with no React wrapper
- Control panel is separate React root that calls callback function
- Map area is either Pure JS mount OR separate React root
- Complete isolation between Pure JS and React implementations
- Removed app.tsx, map-container.tsx, pure-js-container.tsx
- Created control-panel.tsx standalone component
- Created examples-react/ directory with separate components:
  - google-maps-component.tsx
  - mapbox-component.tsx
  - maplibre-component.tsx
- Updated index.html with side-by-side layout (#controls and #map divs)
- index.tsx orchestrates two separate React roots
- No more React wrapping Pure JS code - true isolation achieved
if (example) {
onExampleChange(example, interleaved);
}
}, []); // Only on mount
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Duplicate useEffect causes double initialization on mount

Two useEffect hooks both call onExampleChange on initial mount. In React, all useEffect hooks run on mount regardless of their dependency array. The first effect (lines 69-75) with empty deps runs on mount, but the second effect (lines 77-83) also runs on mount because useEffects always execute on initial render. This causes the example to be loaded twice when the component first mounts, potentially resulting in double API calls, visual flickering, or resource waste. The first useEffect is redundant since the second one already handles the initial mount.

Additional Locations (1)

Fix in Cursor Fix in Web

overlay.finalize();
overlay = null;
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Race condition when cleanup called during async loading

The mount function starts an async operation via loadGoogleMapsAPI and returns a cleanup function immediately. The cleanup function only finalizes overlay, but overlay remains null until the promise resolves. If cleanup is called before the API finishes loading (e.g., user quickly switches examples), the cleanup does nothing, yet the async .then() callback continues and creates a map on the container which may now be in use by a different example. There's no cancellation flag to prevent the stale async operation from completing.

Fix in Cursor Fix in Web

- Defer cleanup and remounting to next tick using setTimeout
- Prevents synchronous unmount during React render phase
- Fixes 'Attempted to synchronously unmount a root' warning
- Fixes 'Failed to execute removeChild' error
- Extracted mountExample function for cleaner separation
@coveralls
Copy link

coveralls commented Dec 3, 2025

Coverage Status

coverage: 91.136%. remained the same
when pulling 7255ab9 on basemap-browser
into e716a4c on master.

Copy link
Collaborator

@felixpalmer felixpalmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Given that we have a screenshot widget now, would it be possible to somehow run through all the variants and display a set of thumbnails as a sort of smoke test? Alternatively, could we somehow include these in our automated tests?

@chrisgervang
Copy link
Collaborator Author

display a set of thumbnails as a sort of smoke test?

I think this is sort of possible!

We could add the line of code from the screenshot widget that captures the canvas, and collect screenshots. Similar to a render test it'd go one by one through the examples to collect the results. This would really only be applicable to interleaved renders since those can capture both basemap and deck.

Additionally, we could add a maplibre-based render test for the globe and Mercator variants (the others would require API keys to run the tests, which doesn't sound great)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants