Skip to content

Commit

Permalink
feat: made #[template_rx] support unreactive state
Browse files Browse the repository at this point in the history
This involved creating a derive macro for `UnreactiveState`, allowing
such state to work seamlessly with Perseus' usual reactive state
platform. With this and the recent global state improvements, users of
unreactive state can now use reactive global state, preloading, etc.

BREAKING CHANGE: renamed `#[template_rx]` to `#[template]` (unreactive
templates should now use `#[template(unreactive)]`)
  • Loading branch information
arctic-hen7 committed Nov 9, 2022
1 parent 9a9a1be commit 43fdf11
Show file tree
Hide file tree
Showing 53 changed files with 301 additions and 324 deletions.
6 changes: 3 additions & 3 deletions docs/0.4.x/en-US/tutorials/second-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ The first thing then is `IndexPageState`, which is our first usage of Perseus' s

Importantly, we've annotated that with `#[perseus::make_rx(IndexPageStateRx)]`, which will create a version of this `struct` that uses Sycamore's `Signal`s: a reactive version. If you're unfamiliar with Sycamore's reactivity system, you should read [this](https://sycamore-rs.netlify.app/docs/basics/reactivity) quickly before continuing.

Next, we create a function called `index_page`, which we annotate with `#[perseus::template_rx]`. That macro is used for declaring templates, and you can think of it like black box that makes things work.
Next, we create a function called `index_page`, which we annotate with `#[perseus::template]`. That macro is used for declaring templates, and you can think of it like black box that makes things work.

<details>
<summary>Details??</summary>
Expand Down Expand Up @@ -94,7 +94,7 @@ Notably, we could actually change this value at runtime if we wanted by calling

The next function we define is `get_template()`, which is fairly straightforward. It just declares a [`Template`](=struct.Template@perseus) with the necessary properties. Specifically, we define the function that actually renders the template as `index_page`, and the other two we'll get to now.

The first of those is the `head()` function, which is annotated as `#[perseus::head]` (which has similar responsibilities to `#[perseus::template_rx]`). In HTML, the language for creating views on the web, there are two main components to every page: the `<body>` and the `<head>`, the latter of which defines certain metadata, like the title, and any stylesheets you need, for example. If `index_page()` creates the body, then `head()` creates the head in this example. Notably, because the head is rendered only ahead of time, it can't be reactive. For that reason, it's passed the unreactive version of the state, and, rather than being generic over `Html`, it uses [`SsrNode`](=struct.SsrNode@perseus), which is specialized for the engine-side.
The first of those is the `head()` function, which is annotated as `#[perseus::head]` (which has similar responsibilities to `#[perseus::template]`). In HTML, the language for creating views on the web, there are two main components to every page: the `<body>` and the `<head>`, the latter of which defines certain metadata, like the title, and any stylesheets you need, for example. If `index_page()` creates the body, then `head()` creates the head in this example. Notably, because the head is rendered only ahead of time, it can't be reactive. For that reason, it's passed the unreactive version of the state, and, rather than being generic over `Html`, it uses [`SsrNode`](=struct.SsrNode@perseus), which is specialized for the engine-side.

Because this function will only ever run on the engine-side, `#[perseus::head]` implies a target-gate to the engine (i.e. `#[cfg(not(target_arch = "wasm32"))]` is implicit). This means you can use engine-side dependencies here without any extra gating.

Expand Down Expand Up @@ -123,7 +123,7 @@ With that done, we can build the second template of this app, which is much simp

This is basically a simpler version of the index template, with no state, and this template only defines a simple view and some metadata in the head.

Importantly, this illustrates that templates that don't take state don't have to have a second argument for their nonexistent state, the `#[perseus::template_rx]` macro is smart enough to handle that (and even a third argument for global state).
Importantly, this illustrates that templates that don't take state don't have to have a second argument for their nonexistent state, the `#[perseus::template]` macro is smart enough to handle that (and even a third argument for global state).

## Error pages

Expand Down
6 changes: 3 additions & 3 deletions docs/next/en-US/tutorials/second-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ The first thing then is `IndexPageState`, which is our first usage of Perseus' s

Importantly, we've annotated that with `#[perseus::make_rx(IndexPageStateRx)]`, which will create a version of this `struct` that uses Sycamore's `Signal`s: a reactive version. If you're unfamiliar with Sycamore's reactivity system, you should read [this](https://sycamore-rs.netlify.app/docs/basics/reactivity) quickly before continuing.

Next, we create a function called `index_page`, which we annotate with `#[perseus::template_rx]`. That macro is used for declaring templates, and you can think of it like black box that makes things work.
Next, we create a function called `index_page`, which we annotate with `#[perseus::template]`. That macro is used for declaring templates, and you can think of it like black box that makes things work.

<details>
<summary>Details??</summary>
Expand Down Expand Up @@ -94,7 +94,7 @@ Notably, we could actually change this value at runtime if we wanted by calling

The next function we define is `get_template()`, which is fairly straightforward. It just declares a [`Template`](=struct.Template@perseus) with the necessary properties. Specifically, we define the function that actually renders the template as `index_page`, and the other two we'll get to now.

The first of those is the `head()` function, which is annotated as `#[perseus::head]` (which has similar responsibilities to `#[perseus::template_rx]`). In HTML, the language for creating views on the web, there are two main components to every page: the `<body>` and the `<head>`, the latter of which defines certain metadata, like the title, and any stylesheets you need, for example. If `index_page()` creates the body, then `head()` creates the head in this example. Notably, because the head is rendered only ahead of time, it can't be reactive. For that reason, it's passed the unreactive version of the state, and, rather than being generic over `Html`, it uses [`SsrNode`](=struct.SsrNode@perseus), which is specialized for the engine-side.
The first of those is the `head()` function, which is annotated as `#[perseus::head]` (which has similar responsibilities to `#[perseus::template]`). In HTML, the language for creating views on the web, there are two main components to every page: the `<body>` and the `<head>`, the latter of which defines certain metadata, like the title, and any stylesheets you need, for example. If `index_page()` creates the body, then `head()` creates the head in this example. Notably, because the head is rendered only ahead of time, it can't be reactive. For that reason, it's passed the unreactive version of the state, and, rather than being generic over `Html`, it uses [`SsrNode`](=struct.SsrNode@perseus), which is specialized for the engine-side.

Because this function will only ever run on the engine-side, `#[perseus::head]` implies a target-gate to the engine (i.e. `#[cfg(not(target_arch = "wasm32"))]` is implicit). This means you can use engine-side dependencies here without any extra gating.

Expand Down Expand Up @@ -123,7 +123,7 @@ With that done, we can build the second template of this app, which is much simp

This is basically a simpler version of the index template, with no state, and this template only defines a simple view and some metadata in the head.

Importantly, this illustrates that templates that don't take state don't have to have a second argument for their nonexistent state, the `#[perseus::template_rx]` macro is smart enough to handle that (and even a third argument for global state).
Importantly, this illustrates that templates that don't take state don't have to have a second argument for their nonexistent state, the `#[perseus::template]` macro is smart enough to handle that (and even a third argument for global state).

## Error pages

Expand Down
2 changes: 1 addition & 1 deletion examples/core/basic/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::Template;
use sycamore::prelude::{view, Html, Scope, SsrNode, View};

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "About." }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/basic/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct IndexPageState {
pub greeting: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: IndexPageStateRx<'a>) -> View<G> {
view! { cx,
p { (state.greeting.get()) }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/custom_server/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::Template;
use sycamore::prelude::{view, Html, Scope, View};

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "About." }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/custom_server/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, View};

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
view! { cx,
p { "Hello World!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/freezing_and_thawing/src/templates/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use sycamore::prelude::*;

use crate::global_state::AppStateRx;

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
// This is not part of our data model, we do NOT want the frozen app
// synchronized as part of our page's state, it should be separate
Expand Down
2 changes: 1 addition & 1 deletion examples/core/freezing_and_thawing/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct IndexProps {
username: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: IndexPropsRx<'a>) -> View<G> {
// This is not part of our data model, we do NOT want the frozen app
// synchronized as part of our page's state, it should be separate
Expand Down
2 changes: 1 addition & 1 deletion examples/core/global_state/src/templates/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use sycamore::prelude::*;

use crate::global_state::AppStateRx;

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
let global_state = RenderCtx::from_ctx(cx).get_global_state::<AppStateRx>(cx);

Expand Down
2 changes: 1 addition & 1 deletion examples/core/global_state/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::global_state::AppStateRx;

// Note that this template takes no state of its own in this example, but it
// certainly could
#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
// We access the global state through the render context, extracted from
// Sycamore's context system
Expand Down
2 changes: 1 addition & 1 deletion examples/core/i18n/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::{t, Template};
use sycamore::prelude::{view, Html, Scope, View};

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { (t!("about", cx)) }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/i18n/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::{link, t, Template};
use sycamore::prelude::{view, Html, Scope, View};

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<G: Html>(cx: Scope) -> View<G> {
let username = "User";

Expand Down
2 changes: 1 addition & 1 deletion examples/core/i18n/src/templates/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub struct PostPageState {
content: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn post_page<'a, G: Html>(cx: Scope<'a>, props: PostPageStateRx<'a>) -> View<G> {
let title = props.title;
let content = props.content;
Expand Down
2 changes: 1 addition & 1 deletion examples/core/idb_freezing/src/templates/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use sycamore::prelude::*;

use crate::global_state::AppStateRx;

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
// This is not part of our data model
let freeze_status = create_signal(cx, String::new());
Expand Down
2 changes: 1 addition & 1 deletion examples/core/idb_freezing/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct IndexProps {
username: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: IndexPropsRx<'a>) -> View<G> {
// This is not part of our data model
let freeze_status = create_signal(cx, String::new());
Expand Down
2 changes: 1 addition & 1 deletion examples/core/index_view/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::Template;
use sycamore::prelude::{view, Html, Scope, View};

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "About." }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/index_view/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, View};

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "Hello World!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/js_interop/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use sycamore::prelude::{view, Scope, View};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
view! { cx,
// We'll use JS to change this message manually
Expand Down
2 changes: 1 addition & 1 deletion examples/core/plugins/src/templates/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use perseus::Template;
use sycamore::prelude::{view, Html, Scope, SsrNode, View};

// This page will actually be replaced entirely by a plugin!
#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "About." }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/plugins/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, SsrNode, View};

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "Hello World!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/preload/src/templates/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use perseus::{Html, Template};
use sycamore::prelude::{view, Scope};
use sycamore::view::View;

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "Check out your browser's network DevTools, no new requests were needed to get to this page!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/preload/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::Template;
use sycamore::prelude::{view, Html, Scope, SsrNode, View};

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<G: Html>(cx: Scope) -> View<G> {
// We can't preload pages on the engine-side
#[cfg(target_arch = "wasm32")]
Expand Down
2 changes: 1 addition & 1 deletion examples/core/router_state/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, SsrNode, View};

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "Hello World!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/router_state/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use perseus::prelude::*;
use perseus::router::RouterLoadState;
use sycamore::prelude::*;

#[perseus::template_rx]
#[perseus::template]
pub fn router_state_page<G: Html>(cx: Scope) -> View<G> {
let load_state = RenderCtx::from_ctx(cx).router.get_load_state(cx);
// This uses Sycamore's `create_memo` to create a state that will update
Expand Down
2 changes: 1 addition & 1 deletion examples/core/rx_state/src/templates/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use perseus::{Html, Template};
use sycamore::prelude::{view, Scope};
use sycamore::view::View;

#[perseus::template_rx]
#[perseus::template]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "Try going back to the index page, and the state should still be the same!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/rx_state/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct IndexPageState {
// This macro will make our state reactive *and* store it in the page state
// store, which means it'll be the same even if we go to the about page and come
// back (as long as we're in the same session)
#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: IndexPageStateRx<'a>) -> View<G> {
view! { cx,
p { (format!("Greetings, {}!", state.username.get())) }
Expand Down
2 changes: 1 addition & 1 deletion examples/core/set_headers/src/templates/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ struct PageState {
greeting: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<'a, G: Html>(cx: Scope<'a>, state: PageStateRx<'a>) -> View<G> {
view! { cx,
p { (state.greeting.get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct PageState {
pub message: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn amalgamation_page<'a, G: Html>(cx: Scope<'a>, state: PageStateRx<'a>) -> View<G> {
view! { cx,
p { (format!("The message is: '{}'", state.message.get())) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub struct PageState {
content: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn build_paths_page<'a, G: Html>(cx: Scope<'a>, state: PageStateRx<'a>) -> View<G> {
let title = state.title;
let content = state.content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct PageState {
pub greeting: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn build_state_page<'a, G: Html>(cx: Scope<'a>, state: PageStateRx<'a>) -> View<G> {
view! { cx,
p { (state.greeting.get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct PageState {
content: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn incremental_generation_page<'a, G: Html>(cx: Scope<'a>, state: PageStateRx<'a>) -> View<G> {
let title = state.title;
let content = state.content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct PageState {
ip: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn request_state_page<'a, G: Html>(cx: Scope<'a>, state: PageStateRx<'a>) -> View<G> {
view! { cx,
p {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct PageState {
pub time: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn revalidation_page<'a, G: Html>(cx: Scope<'a>, state: PageStateRx<'a>) -> View<G> {
view! { cx,
p { (format!("The time when this page was last rendered was '{}'.", state.time.get())) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct PageState {
pub time: String,
}

#[perseus::template_rx]
#[perseus::template]
pub fn revalidation_and_incremental_generation_page<'a, G: Html>(
cx: Scope<'a>,
state: PageStateRx<'a>,
Expand Down
2 changes: 1 addition & 1 deletion examples/core/static_content/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, SsrNode, View};

#[perseus::template_rx]
#[perseus::template]
pub fn index_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "Hello World!" }
Expand Down
11 changes: 3 additions & 8 deletions examples/core/unreactive/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
use perseus::{Html, SsrNode, Template};
use sycamore::prelude::{view, Scope, View};
use perseus::prelude::*;
use sycamore::prelude::*;

// With the old template macro, we have to add the Sycamore `#[component(...)]`
// annotation manually and we get unreactive state passed in Additionally,
// global state is not supported at all So there's no way of persisting state
// between templates
#[perseus::template]
#[sycamore::component]
#[perseus::template(unreactive)]
pub fn about_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
p { "About." }
Expand Down
21 changes: 12 additions & 9 deletions examples/core/unreactive/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
use perseus::{Html, RenderFnResultWithCause, SsrNode, Template};
use perseus::prelude::*;
use serde::{Deserialize, Serialize};
use sycamore::prelude::{view, Scope, View};
use sycamore::prelude::*;

// Without `#[make_rx(...)]`, we have to manually derive `Serialize` and
// `Deserialize`
#[derive(Serialize, Deserialize)]
// We derive `UnreactiveState` too, which actually creates a pseudo-reactive
// wrapper for this unreactive type, allowing it to work with Perseus;
// rather strict state platform (this is just a marker trait though)
#[derive(Serialize, Deserialize, Clone, UnreactiveState)]
pub struct IndexPageState {
pub greeting: String,
}

// With the old template macro, we have to add the Sycamore `#[component(...)]`
// annotation manually and we get unreactive state passed in Additionally,
// global state is not supported at all So there's no way of persisting state
// between templates
#[perseus::template]
#[sycamore::component]
// By adding `unreactive` in brackets, we tell Perseus to expect something with
// `UnreactiveState` derived.
// Otherwise, you can do everything in this macro that you can do with a
// reactive template! Caching, preloading, reactive global state, etc. are all
// supported.
#[perseus::template(unreactive)]
pub fn index_page<G: Html>(cx: Scope, state: IndexPageState) -> View<G> {
view! { cx,
p { (state.greeting) }
Expand Down
Loading

0 comments on commit 43fdf11

Please sign in to comment.