autumn_plugin_react/lib.rs
1//! React islands for Autumn apps: React 19 components inside Maud + htmx
2//! pages.
3//!
4//! 1. Build your components with a bundler. The entry file registers them
5//! (the README has a version that also checks the global):
6//!
7//! ```js
8//! import { createElement, version } from "react";
9//! import { flushSync } from "react-dom";
10//! import { createRoot } from "react-dom/client";
11//! import { Counter } from "./Counter.jsx";
12//!
13//! (window.autumnReact ??= []).push({
14//! createElement, createRoot, flushSync, version, components: { Counter },
15//! });
16//! ```
17//!
18//! 2. Embed the build output as a `PluginAssets` bundle and install it:
19//!
20//! ```rust,no_run
21//! use autumn_plugin_react::{Island, ReactPlugin, react_bundle, react_script};
22//! use autumn_web::assets::PluginAssets;
23//! use autumn_web::prelude::*;
24//!
25//! static ISLANDS: PluginAssets = PluginAssets::from_files(
26//! "app-islands",
27//! &[("islands.js", b"/* esbuild output */")],
28//! );
29//!
30//! #[get("/")]
31//! async fn index() -> AutumnResult<Markup> {
32//! let counter = Island::new("Counter")
33//! .props(&serde_json::json!({ "start": 3 }))?
34//! .fallback(html! { p { "Count: 3" } });
35//! Ok(html! {
36//! html {
37//! head { (react_script()) (react_bundle(&ISLANDS)) }
38//! body { (counter) }
39//! }
40//! })
41//! }
42//!
43//! # async fn run() {
44//! autumn_web::app()
45//! .plugin(ReactPlugin::new().bundle(&ISLANDS))
46//! .routes(routes![index])
47//! .run()
48//! .await;
49//! # }
50//! ```
51//!
52//! The loader (`react-islands.js`) mounts each island with `createRoot`. It
53//! mounts islands that htmx swaps in and unmounts islands that htmx swaps
54//! out. A `data-react-props` change renders the same root again, so React
55//! keeps the state. It uses no inline script and no `eval`, so it works
56//! under the default CSP (`script-src 'self'`).
57//!
58//! # Security
59//!
60//! The loader mounts each `data-react-island` element in the page. If your
61//! app shows user HTML, the sanitizer must remove `data-react-*`, `hx-*`
62//! and `data-hx-*` attributes. `data-react-ignore` alone is not sufficient:
63//! an htmx out-of-band swap can move an element out of it. Do not spread
64//! untrusted props onto DOM elements.
65
66mod assets;
67mod island;
68mod plugin;
69mod tags;
70mod update;
71
72pub use assets::{ASSETS_NAMESPACE, LOADER_JS, REACT_ASSETS};
73pub use island::{AttrError, Island, JsonKind, MountWhen, PropsError};
74pub use plugin::{PLUGIN_NAME, ReactPlugin};
75pub use tags::{react_bundle, react_script};
76pub use update::{PROPS_EVENT, PropsUpdate};