RFC: streamlit_folium_vnext — Streamlit Components v2 rewrite - #306
RFC: streamlit_folium_vnext — Streamlit Components v2 rewrite#306blackary wants to merge 16 commits into
Conversation
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>
- 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>
|
Is the thought here that we’d maintain parallel versions for a while, before moving to v2 permanently? |
|
@randyzwitch not necessarily, I think it might make sense to either:
I mostly wanted to do it as a scratch implementation to make it simpler than trying to rewrite piece-by-piece. |
- 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>
|
@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. |
|
@randyzwitch @hansthen Here's an (AI-generated, but accurate, I believe) summary of this new POC version of the plugin https://refined-github-html-preview.kidonng.workers.dev/blackary/streamlit-folium/raw/84441d74b650e479fb39984a8cbef1061310775a/docs/plugin-registry-architecture.html Here's the demo app https://folium-ccv2.streamlit.app/ |
|
@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 |
|
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. |
|
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 |
Summary
streamlit_folium_vnext, a ground-up rewrite of the Folium integration built on Streamlit Custom Components v2 (CCv2). It lives alongside the existingstreamlit_foliumpackage and does not change any existing APIs.center,zoom,bounds) is emitted immediately on first load — no pan or zoom required.draw.created/draw.edited/draw.deletedevents.state={"clicks": [], "drawn_features": []}to automatically persist click history and drawn features across reruns viast.session_state, without any manual wiring.FoliumMap → MapSpeccompiler 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
Test plan
pytest tests/test_vnext_package.py tests/test_vnext_e2e.py tests/test_vnext_frontend.pystreamlit run vnext_demo_app/Home.pykind: "marker"with tooltipkind: "circle_marker"kind: "geojson"with feature propertiesdraw.createdevent fires with GeoJSONclickevent fires withkind: "drawn"and the shape's GeoJSON.... Generated with Cortex Code