ferrovue 0.7.1

Vue templates compiled to Rust render functions: server-side rendering with no JavaScript at run time
Documentation
Vue components in a page that Dioxus renders.

A [Dioxus](https://dioxuslabs.com) app rendered on the server, with `dioxus-ssr` or as a fullstack
app, can show ferrovue's components: an island goes into `rsx!` like any other node, and the
browser hydrates it with Vue as it would on a page ferrovue wrote. ferrovue supports **Dioxus 0.7**.

Turn on the `dioxus` feature. It depends on `dioxus-core` alone, with no default features:

```sh
cargo add ferrovue --features dioxus
```

# Islands in `rsx!`

[`Html`](crate::Html) is a Dioxus `IntoDynNode`, so a generated `island()` or `html()` goes straight
into `rsx!` between braces. [`Html::to_element`](crate::Html::to_element) is the same as an
`Element`. This is the page from the repository's `examples/dioxus` (`src/main.rs`), whose tests
render it with `dioxus-ssr`:

```rust
# mod generated {
# pub mod counter {
#     use std::borrow::Cow;
#     use ferrovue as fv;
#     pub const NAME: &str = "Counter";
#     #[derive(Debug, Clone, serde::Serialize)]
#     pub struct Props<'a> { pub label: Cow<'a, str>, pub start: i64 }
#     impl<'a> Props<'a> {
#         pub fn new(label: impl Into<Cow<'a, str>>, start: i64) -> Self {
#             Props { label: label.into(), start }
#         }
#     }
#     pub fn render(out: &mut String, props: &Props<'_>) {
#         out.push_str("<button type=\"button\">");
#         fv::escape_into(out, &props.label);
#         out.push_str(": ");
#         fv::push_int(out, props.start);
#         out.push_str("</button>");
#     }
#     pub fn island<'p, 'a>(props: &'p Props<'a>) -> fv::Html<'p, Props<'a>> {
#         fv::Html::island(NAME, props, render)
#     }
# }
# }
# mod page {
# use super::generated;
use dioxus::prelude::*;
use generated::counter;

#[component]
fn Page(title: String, start: i64) -> Element {
    let clicks = counter::Props::new("Clicks", start);
    rsx! {
        main {
            h1 { "{title}" }
            {counter::island(&clicks)}
        }
        script { r#type: "module", src: "/assets/main.js" }
    }
}
# pub fn render(title: &str, start: i64) -> String {
#     let mut dom = VirtualDom::new_with_props(Page, PageProps { title: title.to_owned(), start });
#     dom.rebuild_in_place();
#     dioxus_ssr::render(&dom)
# }
# }
# fn main() {
#     assert_eq!(
#         page::render("Dioxus & Vue", 3),
#         concat!(
#             "<main><h1>Dioxus &#38; Vue</h1>",
#             r#"<div data-island="Counter" data-props="{&#34;label&#34;:&#34;Clicks&#34;,&#34;start&#34;:3}">"#,
#             r#"<button type="button">Clicks: 3</button></div></main>"#,
#             r#"<script type="module" src="/assets/main.js"></script>"#,
#         )
#     );
# }
```

The client's script hydrates the island with `mountIslands` from `ferrovue`, as on any other
page: see [`islands_and_hydration`](crate::guide::islands_and_hydration).

# What Dioxus writes

An island is its own element: the `<div data-island data-props>` that `island()` writes, with the
component's markup inside it as `dangerous_inner_html`. Dioxus writes that markup as it is, so what
the client hydrates is byte for byte what ferrovue rendered, which is what Vue's server renderer
writes; Dioxus adds nothing inside the element, in a fullstack render either.

Dioxus writes the element's attributes itself. The values are the same, so the client reads the same
props, but Dioxus spells the five escaped characters as numeric references (`&#34;` for `"`, `&#38;`,
`&#39;`, `&#60;`, `&#62;`) where Vue writes `&quot;`, `&amp;`, `&#39;`, `&lt;` and `&gt;`. A
fullstack render, which Dioxus hydrates in the browser too, adds its own `data-node-hydration`
attribute to the element. Neither changes what Vue hydrates: the props are read from the parsed
attribute, and Vue mounts on the element's content and leaves the element itself alone.

```rust
# mod counter {
# // @generated by ferrovue from components/Counter.vue. Do not edit: change the `.vue` file and run
# // `ferrovue`.
#
# use std::borrow::Cow;
#
# use ferrovue as fv;
#
# /// The component's name, as `data-island` carries it.
# pub const NAME: &str = "Counter";
#
# /// The props `Counter.vue` declares.
# #[derive(Debug, Clone, serde::Serialize)]
# #[cfg_attr(test, derive(serde::Deserialize))]
# pub struct Props<'a> {
#     #[serde(rename = "label")]
#     pub label: Cow<'a, str>,
#     #[serde(rename = "start")]
#     pub start: i64,
# }
#
# impl<'a> Props<'a> {
#     /// Props with its required fields.
#     pub fn new(label: impl Into<Cow<'a, str>>, start: i64) -> Self {
#         Props { label: label.into(), start }
#     }
#
# }
#
#
# /// Write the component's server render into `out`.
# pub fn render(out: &mut String, props: &Props<'_>) {
#     out.reserve(39 + props.label.len());
#     let s_count = props.start;
#     out.push_str("<button type=\"button\">");
#     fv::escape_into(out, &props.label);
#     out.push_str(": ");
#     fv::push_int(out, s_count);
#     out.push_str("</button>");
# }
#
# /// The component's markup, for a maud page that shows it without hydrating it.
# pub fn html<'p, 'a>(props: &'p Props<'a>) -> fv::Html<'p, Props<'a>> {
#     fv::Html::markup(props, render)
# }
#
# /// The component as an island the client hydrates.
# pub fn island<'p, 'a>(props: &'p Props<'a>) -> fv::Html<'p, Props<'a>> {
#     fv::Html::island(NAME, props, render)
# }
# }
// Counter.vue: defineProps<{ label: string; start: number }>(), rendering a <button>.
let props = counter::Props::new("Clicks", 3);
let markup = counter::html(&props).into_string();
assert_eq!(markup, r#"<button type="button">Clicks: 3</button>"#);

// dioxus-ssr: the island element, with exactly the component's markup inside it.
let island = dioxus_ssr::render_element(counter::island(&props).to_element());
assert_eq!(
    island,
    format!(r#"<div data-island="Counter" data-props="{{&#34;label&#34;:&#34;Clicks&#34;,&#34;start&#34;:3}}">{markup}</div>"#)
);

// A fullstack render: Dioxus's hydration id on the element, and still nothing inside it.
let mut renderer = dioxus_ssr::Renderer::new();
renderer.pre_render = true;
let island = renderer.render_element(counter::island(&props).to_element());
assert!(island.ends_with(&format!(r#" data-node-hydration="0">{markup}</div>"#)));

// What ferrovue writes: the same element, the same props, the same markup.
assert_eq!(
    counter::island(&props).into_string(),
    format!(r#"<div data-island="Counter" data-props="{{&quot;label&quot;:&quot;Clicks&quot;,&quot;start&quot;:3}}">{markup}</div>"#)
);
```

The crate's conformance suite renders the islands of its fixtures into a Dioxus page, plainly and
as a fullstack server does, and holds the markup inside each one to the HTML Vue recorded.

# Every byte as ferrovue writes it

To have an island written exactly as [`Html::into_string`](crate::Html::into_string) writes it,
attributes included, give an element of the page's own the whole island as its content:

```rust
# mod counter {
#     use std::borrow::Cow;
#     use ferrovue as fv;
#     pub const NAME: &str = "Counter";
#     #[derive(Debug, Clone, serde::Serialize)]
#     pub struct Props<'a> { pub label: Cow<'a, str>, pub start: i64 }
#     impl<'a> Props<'a> {
#         pub fn new(label: impl Into<Cow<'a, str>>, start: i64) -> Self {
#             Props { label: label.into(), start }
#         }
#     }
#     pub fn render(out: &mut String, props: &Props<'_>) {
#         out.push_str("<button type=\"button\">");
#         fv::escape_into(out, &props.label);
#         out.push_str(": ");
#         fv::push_int(out, props.start);
#         out.push_str("</button>");
#     }
#     pub fn island<'p, 'a>(props: &'p Props<'a>) -> fv::Html<'p, Props<'a>> {
#         fv::Html::island(NAME, props, render)
#     }
# }
# fn main() {
# use dioxus::prelude::*;
# fn totals() -> Element {
# let total = counter::Props::new("Total", 6);
rsx! {
    section { dangerous_inner_html: counter::island(&total).into_string() }
}
# }
# let mut dom = VirtualDom::new(totals);
# dom.rebuild_in_place();
# assert_eq!(
#     dioxus_ssr::render(&dom),
#     concat!(
#         r#"<section><div data-island="Counter" data-props="{&quot;label&quot;:&quot;Total&quot;,&quot;start&quot;:6}">"#,
#         r#"<button type="button">Total: 6</button></div></section>"#,
#     )
# );
# }
```

That is also the way to put the markup of a component that is not an island, from `html()`, into an
element of the page's own. Such markup has no element of its own, and Dioxus has no node for raw
HTML outside an element, so [`Html::to_element`](crate::Html::to_element) puts it in a `<div>` with
no attributes. The client never hydrates `html()` markup, so the `<div>` matters only to the page's
layout and styles.

Either way, the element the markup is put in must be one the HTML parser leaves it in: a component
whose root is an `<li>` or a `<tr>` belongs, through `dangerous_inner_html`, in a `<ul>` or a
`<tbody>` of the page's own; inside a `<div>` the parser would move it.

# In a fullstack app

A fullstack app renders each component on the server and again in the browser, where Dioxus
hydrates the page. The island is a Dioxus element like any other, so the component that shows it
must build it from the same props on both sides, as it would any other element. Dioxus hydrates the
element's attributes and leaves its content alone; `mountIslands` then hydrates that content with
Vue.

From then on the content belongs to Vue. If the Dioxus component renders again with different props
for the island, Dioxus sets the element's `data-props` and its content anew, replacing what Vue
mounted; render each island from values that stay the same for the life of the page, and let Vue
own whatever changes in it.

# Pinia state

The client starts its stores from the state the server rendered with, read from a
`<script type="application/json">` before the islands hydrate (see [`pinia`](crate::guide::pinia)).
[`ferrovue::dioxus::state_script`](crate::dioxus::state_script) is
[`state_script_into`](crate::state_script_into) as an element: put it in the page's `rsx!`, before
the client's script. Its content is the same bytes `state_script_into` writes:

```rust
let state = serde_json::json!({ "prefs": { "theme": "</script>" } });
let script = dioxus_ssr::render_element(ferrovue::dioxus::state_script("__pinia", &state));
assert_eq!(
    script,
    r#"<script type="application/json" id="__pinia">{"prefs":{"theme":"\u003c/script\u003e"}}</script>"#
);
```

A page whose head or body is a template of the server's own, outside `rsx!`, can write
`state_script_into` there instead, as raw HTML.

# Streaming

Holes ([`streaming`](crate::guide::streaming)) cut a page that ferrovue renders into pieces sent in
the order they are ready. A Dioxus element is written whole, so a component rendered as one must not
be given a [`hole()`](crate::hole): the marker would be written into the page as it is. A Dioxus
page streams through Dioxus's own suspense boundaries, each ferrovue component inside them rendered
whole.