Skip to content

RFC: streamlit_folium_vnext — Streamlit Components v2 rewrite - #306

Draft
blackary wants to merge 16 commits into
randyzwitch:masterfrom
blackary:ccv2-poc
Draft

RFC: streamlit_folium_vnext — Streamlit Components v2 rewrite#306
blackary wants to merge 16 commits into
randyzwitch:masterfrom
blackary:ccv2-poc

Conversation

@blackary

Copy link
Copy Markdown
Collaborator

Summary

  • Introduces streamlit_folium_vnext, a ground-up rewrite of the Folium integration built on Streamlit Custom Components v2 (CCv2). It lives alongside the existing streamlit_folium package and does not change any existing APIs.
  • Zero-flicker map rendering: map instances are cached in JS module scope and re-attached on every rerender without rebuilding the DOM or re-fetching tiles.
  • View state (center, zoom, bounds) is emitted immediately on first load — no pan or zoom required.
  • Rich click events for map background, markers, circle markers, GeoJSON features, and drawn shapes — each click carries the clicked object's identity and geometry.
  • Leaflet.Draw integration with draw.created / draw.edited / draw.deleted events.
  • Optional accumulated state API: pass state={"clicks": [], "drawn_features": []} to automatically persist click history and drawn features across reruns via st.session_state, without any manual wiring.
  • Multi-map support: each map is independently keyed with no cross-talk.
  • A FoliumMap → MapSpec compiler walks the folium layer tree (markers, circle markers, GeoJSON, feature groups, draw plugin, layer control) and produces a JSON spec that the TypeScript/Vite frontend renders via Leaflet.

New structure

streamlit_folium_vnext/
  api.py                  # st_folium_vnext(), st_leaflet(), compile_folium()
  compiler/               # folium → MapSpec compiler
  component/mount.py      # CCv2 component registration
  frontend/src/index.ts   # TypeScript/Vite CCv2 renderer (Leaflet)
  models/                 # MapSpec, MapNode dataclasses

vnext_demo_app/           # 6-page Streamlit demo app
  pages/1_Basic_Map.py
  pages/2_Click_Events.py
  pages/3_Drawing.py
  pages/4_GeoJSON.py
  pages/5_Multiple_Maps.py
  pages/6_Accumulated_State.py

Test plan

  • Run pytest tests/test_vnext_package.py tests/test_vnext_e2e.py tests/test_vnext_frontend.py
  • Start the demo app: streamlit run vnext_demo_app/Home.py
  • Verify no flicker when panning/zooming on Basic Map
  • Verify center/zoom/bounds update without requiring interaction
  • Verify center/zoom/bounds still show correctly after navigating away and back
  • Click a marker → event shows kind: "marker" with tooltip
  • Click a circle marker → event shows kind: "circle_marker"
  • Click a GeoJSON feature → event shows kind: "geojson" with feature properties
  • Draw a shape → draw.created event fires with GeoJSON
  • Click a drawn shape → click event fires with kind: "drawn" and the shape's GeoJSON
  • Verify accumulated state accumulates clicks and drawn features across reruns
  • Verify both Tokyo and London maps on Multiple Maps page work independently

.... Generated with Cortex Code

randyzwitch and others added 3 commits March 31, 2026 09:07
Greenfield implementation of a Folium/Leaflet integration built on
Streamlit Custom Components v2 (CCv2), living alongside the existing
package as `streamlit_folium_vnext`.

Key features:
- Zero-flicker: map instances are cached in JS module scope and
  re-attached on rerender without rebuilding
- View state (center/zoom/bounds) emitted immediately on load, not
  only after user interaction
- Click events for map background, markers, circle markers, GeoJSON
  features, and drawn shapes -- each carrying the clicked object's data
- Leaflet.Draw integration with draw.created / draw.edited /
  draw.deleted events
- Optional accumulated state API: pass state={"clicks": [],
  "drawn_features": []} to persist events across reruns via
  st.session_state without manual wiring
- Multi-map support: each map keyed independently, no cross-talk
- FoliumMap to MapSpec compiler that walks the folium layer tree and
  produces a JSON spec rendered by the frontend

New files:
- streamlit_folium_vnext/ -- Python package (api, compiler, component, models)
- streamlit_folium_vnext/frontend/ -- TypeScript/Vite CCv2 frontend
- vnext_demo_app/ -- 6-page Streamlit demo app
- tests/test_vnext_*.py -- unit + integration tests

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
blackary and others added 2 commits March 31, 2026 09:37
- Replace auto-discovery page routing with st.navigation() + st.Page()
  in a single app.py entry point with Material icons and grouped sections
- Rename page files (drop numeric prefixes), remove per-page set_page_config
- Add slots=True to MapNode, MapSpec, MapEvent, CompileContext dataclasses
- Replace if/elif kind dispatch with match statement in compile_folium_map
- Add full type annotations (obj, context: CompileContext -> MapNode) to
  all compiler plugin functions

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
@randyzwitch

Copy link
Copy Markdown
Owner

Is the thought here that we’d maintain parallel versions for a while, before moving to v2 permanently?

@blackary

blackary commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator Author

@randyzwitch not necessarily, I think it might make sense to either:

  • just hard-switch over to a new one with a breaking version release
  • release it as a new package

I mostly wanted to do it as a scratch implementation to make it simpler than trying to rewrite piece-by-piece.

blackary and others added 4 commits April 1, 2026 16:05
- Add docs/plugin-registry-architecture.html: end-to-end comparison of
  streamlit-folium (CCv1) vs streamlit-folium-vnext (CCv2) covering
  payload format, Python compilation, CDN loading, re-renders, events,
  and a marker-cluster case study
- Remove examples/ folder changes (superseded by vnext_demo_app/)

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
- Add dependencies (streamlit, folium) and path source for
  streamlit-folium to vnext_demo_app/pyproject.toml
- Add vnext_demo_app/uv.lock so the app can be installed/run from
  its own folder without the root workspace
- Remove [tool.uv.workspace] from root pyproject.toml

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
- Remove the parent-package dependency from vnext_demo_app so the
  app no longer relies on installing streamlit-folium from ..
- Add a symlink to streamlit_folium_vnext inside the demo app folder
  so the component code and built JS bundle are available when
  deploying the app from that subdirectory
- Refresh the demo app lockfile

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
@randyzwitch

Copy link
Copy Markdown
Owner

@blackary I don't think it makes sense to make it a new package. One thing that the Julia community did when making changes like this was to bundle them into the same major version, adding a deprecation notice. Then, immediately release a major version bump without the old code in it.

That way, people can use the old one while they figure out the new one, then make a major version change to make it permanent.

@blackary

blackary commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator Author

@hansthen

hansthen commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

@blackary This is amazing work! I really like your compiler idea to render Folium nodes.

Would it make sense though if upstream Folium already provided support for the compilation step? Then Folium itself could be the plugin registry.

I had been toying with a similar idea for Folium itself to solve some quirks in Folium. (But I was not competent or persistent enough to actually get it to work). The issue in Folium is that we cannot dynamically render some elements. E.g. we have separate classes for Popup and for GeoJsonPopup. My intuition is that your compilation idea would also be able to solve that issue.

@hansthen

hansthen commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

There are some other ideas that I would like to steal from you. For instance, your way of generating id's makes much more sense to me than the native method in Folium.

@blackary

blackary commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator Author

FULL DISCLOSURE -- this code was generated with Cortex Code, Snowflake's coding agent. That did get put in the PR description footer, but wanted to make sure it was clear. :)

I like the idea of Folium being the plugin registry, and handling the compilation -- I just really love that this solution doesn't involve generating and then monkeypatching and then eval-ing the js 😁 So, if we can push the compilation into Folium, that seems great to me.

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.

3 participants