Skip to content

Lazy-load MapboxMap child components - #566

Closed
titouanmathis wants to merge 3 commits into
mainfrom
feat/mapbox-lazy-children
Closed

Lazy-load MapboxMap child components#566
titouanmathis wants to merge 3 commits into
mainfrom
feat/mapbox-lazy-children

Conversation

@titouanmathis

Copy link
Copy Markdown
Contributor

Follow-up optimization for @studiometa/ui-mapbox.

📚 Description

MapboxMap statically imported all 11 children (markers, popups, controls, source, layer, images, cluster), so importing the map dragged the whole family's wrapper code into its chunk even for a page that only uses, say, a marker. This registers each child with a dynamic-import loader instead, so js-toolkit fetches a child's code only when a matching element exists on the page.

  • resolveWhenMapboxMapIsLoaded now accepts a loader factory () => import(...) (or a class, for backward-compat) and awaits the dynamic import after the map's load.
  • Each config.components entry is now resolveWhenMapboxMapIsLoaded(() => import('./X.js').then((m) => m.X)).

⚖️ Honest impact

This is a pay-per-use win on the child wrapper code, not the heavyweight:

  • mapbox-gl (~230 kB) is loaded by MapboxMap regardless — unaffected.
  • The @mapbox/mapbox-gl-geocoder peer was already lazy (dynamic import inside MapboxGeocoder).
  • Measured (esbuild, mapbox-gl external): a "MapboxMap + one marker" page goes from an 11.4 kB up-front child chunk to ~2.5 kB (entry + the marker chunk), with the cluster (~4 kB min), geocoder, controls, etc. never fetched. Total across all chunks grows slightly from split boilerplate — paid only by a page using every child.

🔧 StoreLocator hardening (required)

With lazy children, the cluster mounts only after its chunk is network-fetched, which outlasts StoreLocator's old microtask-bound wiring poll. The poll is replaced with the map's sourcedata signal (fires when the cluster adds its source, at any latency) plus a real-time retry (~5 s budget), both torn down once wiring settles or on destroy.

✅ Verification

Build, lint, npm run test (689 pass incl. a new resolver spec) all pass. Real-browser verification of the StoreLocator timing under network-latency chunk loading is being done via the playground stories (jsdom can't reproduce it) — results to follow on this PR.

❓ Type of change

  • 👌 Enhancement (performance)

🤖 Generated with Claude Code

Register MapboxMap's children with dynamic-import loaders instead of
static imports, so each child's code is code-split and fetched only when
a matching element exists on the page — the map no longer drags the whole
family (~22 KB of wrapper code) into its chunk. mapbox-gl is loaded by
MapboxMap regardless and is unaffected.

- resolveWhenMapboxMapIsLoaded now accepts a loader factory (or a class,
  for backward compatibility) and awaits the dynamic import after the map
  has loaded.
- Harden StoreLocator's cluster wiring for the resulting network-delayed
  child mount: replace the microtask-bound poll with the map's `sourcedata`
  signal plus a real-time retry (250ms x 20 ~= 5s), both torn down once
  wiring settles or on destroy.

Browser verification of the StoreLocator timing under real network latency
is a follow-up (jsdom can't reproduce it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeZwHwo3d9rYsCxJUQqTep
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.10169% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.07%. Comparing base (1063333) to head (1d7a8ac).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
packages/ui-mapbox/MapboxMap.ts 0.00% 11 Missing ⚠️
packages/ui-mapbox/StoreLocator.ts 77.50% 7 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #566      +/-   ##
============================================
- Coverage     86.19%   86.07%   -0.13%     
  Complexity      145      145              
============================================
  Files           139      139              
  Lines          4354     4401      +47     
  Branches        817      824       +7     
============================================
+ Hits           3753     3788      +35     
- Misses          528      539      +11     
- Partials         73       74       +1     
Flag Coverage Δ
unittests 86.07% <66.10%> (-0.13%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/ui-mapbox/utils.ts 92.30% <100.00%> (+31.43%) ⬆️
packages/ui-mapbox/StoreLocator.ts 87.70% <77.50%> (-2.71%) ⬇️
packages/ui-mapbox/MapboxMap.ts 65.62% <0.00%> (-34.38%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review

Risk: Low — no blocking issues; safe to merge aside from nits.

This MR lazy-loads MapboxMap child components through dynamic imports and updates resolveWhenMapboxMapIsLoaded to resolve loaders only after the map is ready. It also reworks StoreLocator child wiring from microtask polling to a sourcedata signal plus timed retries so late-mounted cluster and geocoder children can be discovered after map-load.


Review usage: 299,785 in (242,688 cached) / 8,192 out tokens — $0.3263 (cloudflare-ai-gateway/gpt-5.4, thinking: medium)

Reviewed by @weareikko/code-review v0.9.4 for commit e4003b3.

Previous review runs

Previous run archived 2026-08-01T11:10:48Z

Code Review

Risk: Low — No blocking issues found in the lazy-loading and StoreLocator wiring changes; safe to merge.

This MR replaces MapboxMap's static child registrations with lazy loaders gated behind resolveWhenMapboxMapIsLoaded, so child wrapper code is fetched only when matching elements are present after the map loads. It also reworks StoreLocator's post-load child wiring to use sourcedata plus a bounded real-time retry window, and adds resolver coverage plus timer-settling updates to the StoreLocator tests.


Review usage: 163,053 in (129,536 cached) / 6,917 out tokens — $0.2199 (cloudflare-ai-gateway/gpt-5.4, thinking: medium)

Reviewed by @weareikko/code-review v0.9.4 for commit ace367b.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Export Size

@studiometa/ui

Name Size Diff
default 1.42 kB +660 B (+86.50%) 🔺
Unchanged

@studiometa/ui

Name Size Diff
AbstractCarouselChild 747 B -
AbstractCarouselComponent 156 B -
AbstractFrameTrigger 1.74 kB -
AbstractPrefetch 366 B -
AbstractScrollAnimation 3.87 kB -
AbstractSliderChild 702 B -
Accordion 1.78 kB -
AccordionItem 1.66 kB -
Action 1.11 kB -
AnchorNav 3.88 kB -
AnchorNavLink 3.77 kB -
AnchorNavTarget 125 B -
AnchorScrollTo 2.56 kB -
animationScrollWithEase 763 B -
Carousel 4.09 kB -
CarouselBtn 900 B -
CarouselDrag 709 B -
CarouselItem 954 B -
CarouselWrapper 595 B -
CircularMarquee 506 B -
ClickOutside 160 B -
Cursor 650 B -
DataBind 3.64 kB -
DataComputed 3.72 kB -
DataEffect 3.7 kB -
DataModel 3.75 kB -
DataScope 1.35 kB -
Dialog 2.43 kB -
Draggable 1.61 kB -
Fetch 2.4 kB -
FetchShopifyPartial 2.85 kB -
FetchShopifySection 2.62 kB -
Figure 1.81 kB -
FigureShopify 2.08 kB -
FigureTwicpics 2.34 kB -
FigureVideo 1.98 kB -
FigureVideoTwicpics 2.56 kB -
Frame 3.47 kB -
FrameAnchor 1.84 kB -
FrameForm 1.92 kB -
FrameLoader 1.45 kB -
FrameTarget 1.75 kB -
FrameTriggerLoader 1.46 kB -
Hoverable 904 B -
Indexable 1.02 kB -
InView 157 B -
InViewOnce 206 B -
LargeText 678 B -
LazyInclude 322 B -
Menu 2.33 kB -
MenuBtn 140 B -
MenuList 1.9 kB -
Modal 2.11 kB -
ModalWithTransition 2.22 kB -
Panel 2.51 kB -
PrefetchWhenOver 408 B -
PrefetchWhenVisible 417 B -
ScrollAnimation 4 kB -
ScrollAnimationChild 4.12 kB -
ScrollAnimationChildWithEase 4.72 kB -
ScrollAnimationParent 4.18 kB -
ScrollAnimationTarget 4.06 kB -
ScrollAnimationTimeline 4.12 kB -
ScrollAnimationWithEase 4.6 kB -
ScrollReveal 1.63 kB -
Sentinel 129 B -
Slider 3.65 kB -
SliderBtn 928 B -
SliderCount 753 B -
SliderDots 1.97 kB -
SliderDrag 269 B -
SliderItem 958 B -
SliderProgress 1.06 kB -
Sticky 771 B -
Tabs 1.38 kB -
Target 86 B -
Timer 496 B -
TimerProgress 627 B -
Track 1.64 kB -
TrackContext 340 B -
TrackShopify 1.72 kB -
Transition 1.41 kB -
viewTransition 295 B -
ViewTransition 738 B -
withDeprecation 199 B -
withIndex 968 B -
withScrollAnimationDebug 2.03 kB -
withTransition 1.39 kB -

The child components each have a default export, and js-toolkit's resolver
unwraps `module.default`, so the `.then((m) => m.X)` in the lazy loaders is
redundant. Register each child as `() => import('./X.js')`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeZwHwo3d9rYsCxJUQqTep
The 0.3.11 release fixes the multi-chunk dependency-emit collision
(studiometa/playground#73) that broke docs_build with:
Conflict: Multiple assets emit different content to the same filename
static/deps/@studiometa/ui-mapbox/index.js

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeZwHwo3d9rYsCxJUQqTep
@titouanmathis

Copy link
Copy Markdown
Contributor Author

The docs_build blocker is resolved. ✅

The build was failing at webpack seal time with:

Conflict: Multiple assets emit different content to the same filename static/deps/@studiometa/ui-mapbox/index.js

Root cause was in @studiometa/playground (its dependency-emit plugin renamed every code-split chunk to a fixed index.js). This PR's lazy-loading of MapboxMap's children is what made @studiometa/ui-mapbox code-split and surfaced it.

Fixed upstream in studiometa/playground#73 (merged), released as @studiometa/playground@0.3.11.

This branch now pins @studiometa/playground to 0.3.11 (commit 1d7a8ac). Verified locally:

  • npm run docs:build — passes, the collision is gone
  • npm run test — 689 passed (3 skipped)

@titouanmathis

Copy link
Copy Markdown
Contributor Author

Superseded by #567, which re-architects the whole @studiometa/ui-mapbox family to be declarative and dynamic-DOM-native. Children are now independently registered and resolve their map on mount, so the lazy-child-resolution approach here is subsumed — lazy loading is just the existing importWhen* helpers + per-component subpaths. Closing in favour of #567.

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.

1 participant