From 07289f63722c154d4945471711e5b674f3ef2354 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Sun, 30 Jan 2022 15:04:09 +1100 Subject: [PATCH] docs(examples): added some new examples and a template for more --- examples/.base/.gitignore | 1 + examples/.base/Cargo.toml | 12 +++++ examples/.base/README.md | 7 +++ examples/.base/index.html | 10 +++++ examples/.base/src/error_pages.rs | 17 +++++++ examples/.base/src/lib.rs | 10 +++++ examples/.base/src/templates/index.rs | 20 +++++++++ examples/.base/src/templates/mod.rs | 1 + examples/fetching/.gitignore | 3 +- examples/fetching/README.md | 5 +++ examples/fetching/src/error_pages.rs | 17 +++++++ examples/fetching/src/lib.rs | 14 ++---- .../fetching/src/{ => templates}/index.rs | 21 ++++++--- examples/fetching/src/templates/mod.rs | 1 + examples/fetching/{ => static}/message.txt | 0 examples/global_state/.gitignore | 1 + examples/global_state/Cargo.toml | 12 +++++ examples/global_state/README.md | 3 ++ examples/global_state/index.html | 10 +++++ examples/global_state/src/error_pages.rs | 17 +++++++ examples/global_state/src/global_state.rs | 17 +++++++ examples/global_state/src/lib.rs | 13 ++++++ examples/global_state/src/templates/about.rs | 29 ++++++++++++ examples/global_state/src/templates/index.rs | 29 ++++++++++++ examples/global_state/src/templates/mod.rs | 2 + examples/unreactive/.gitignore | 1 + examples/unreactive/Cargo.toml | 12 +++++ examples/unreactive/README.md | 3 ++ examples/unreactive/index.html | 10 +++++ examples/unreactive/src/error_pages.rs | 17 +++++++ examples/unreactive/src/lib.rs | 10 +++++ examples/unreactive/src/templates/index.rs | 44 +++++++++++++++++++ examples/unreactive/src/templates/mod.rs | 1 + 33 files changed, 351 insertions(+), 19 deletions(-) create mode 100644 examples/.base/.gitignore create mode 100644 examples/.base/Cargo.toml create mode 100644 examples/.base/README.md create mode 100644 examples/.base/index.html create mode 100644 examples/.base/src/error_pages.rs create mode 100644 examples/.base/src/lib.rs create mode 100644 examples/.base/src/templates/index.rs create mode 100644 examples/.base/src/templates/mod.rs create mode 100644 examples/fetching/README.md create mode 100644 examples/fetching/src/error_pages.rs rename examples/fetching/src/{ => templates}/index.rs (72%) create mode 100644 examples/fetching/src/templates/mod.rs rename examples/fetching/{ => static}/message.txt (100%) create mode 100644 examples/global_state/.gitignore create mode 100644 examples/global_state/Cargo.toml create mode 100644 examples/global_state/README.md create mode 100644 examples/global_state/index.html create mode 100644 examples/global_state/src/error_pages.rs create mode 100644 examples/global_state/src/global_state.rs create mode 100644 examples/global_state/src/lib.rs create mode 100644 examples/global_state/src/templates/about.rs create mode 100644 examples/global_state/src/templates/index.rs create mode 100644 examples/global_state/src/templates/mod.rs create mode 100644 examples/unreactive/.gitignore create mode 100644 examples/unreactive/Cargo.toml create mode 100644 examples/unreactive/README.md create mode 100644 examples/unreactive/index.html create mode 100644 examples/unreactive/src/error_pages.rs create mode 100644 examples/unreactive/src/lib.rs create mode 100644 examples/unreactive/src/templates/index.rs create mode 100644 examples/unreactive/src/templates/mod.rs diff --git a/examples/.base/.gitignore b/examples/.base/.gitignore new file mode 100644 index 0000000000..9405098b45 --- /dev/null +++ b/examples/.base/.gitignore @@ -0,0 +1 @@ +.perseus/ diff --git a/examples/.base/Cargo.toml b/examples/.base/Cargo.toml new file mode 100644 index 0000000000..ea6e27f4e4 --- /dev/null +++ b/examples/.base/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "perseus-example-base" +version = "0.3.2" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +perseus = { path = "../../packages/perseus", features = [ "hydrate" ] } +sycamore = "0.7" +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/examples/.base/README.md b/examples/.base/README.md new file mode 100644 index 0000000000..6e97fb83a3 --- /dev/null +++ b/examples/.base/README.md @@ -0,0 +1,7 @@ +# Base Example + +This isn't an example per se, rather it's a template for future examples. If you want to create a new example, you should copy this directory and modify it to suit your needs, its purpsoe is just to create a universal minimal boilerplate from which examples can work. + +After copying this, you'll need to change this README to describe your example, and you'll need to change the name of your example in `Cargo.toml` to `perseus-example-` (right now, it's `perseus-example-base`, leabving it as this will cause a compilation error). + +If you need some help with creating your example, feel free to pop over to our [Discord channel](https://discord.com/invite/GNqWYWNTdp)! diff --git a/examples/.base/index.html b/examples/.base/index.html new file mode 100644 index 0000000000..edc8a66246 --- /dev/null +++ b/examples/.base/index.html @@ -0,0 +1,10 @@ + + + + + + + +
+ + diff --git a/examples/.base/src/error_pages.rs b/examples/.base/src/error_pages.rs new file mode 100644 index 0000000000..d472912966 --- /dev/null +++ b/examples/.base/src/error_pages.rs @@ -0,0 +1,17 @@ +use perseus::{ErrorPages, Html}; +use sycamore::view; + +pub fn get_error_pages() -> ErrorPages { + let mut error_pages = ErrorPages::new(|url, status, err, _| { + view! { + p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) } + } + }); + error_pages.add_page(404, |_, _, _, _| { + view! { + p { "Page not found." } + } + }); + + error_pages +} diff --git a/examples/.base/src/lib.rs b/examples/.base/src/lib.rs new file mode 100644 index 0000000000..876ad309a7 --- /dev/null +++ b/examples/.base/src/lib.rs @@ -0,0 +1,10 @@ +mod error_pages; +mod templates; + +use perseus::define_app; +define_app! { + templates: [ + crate::templates::index::get_template::() + ], + error_pages: crate::error_pages::get_error_pages() +} diff --git a/examples/.base/src/templates/index.rs b/examples/.base/src/templates/index.rs new file mode 100644 index 0000000000..34faa5c796 --- /dev/null +++ b/examples/.base/src/templates/index.rs @@ -0,0 +1,20 @@ +use perseus::{Html, Template}; +use sycamore::prelude::{view, SsrNode, View}; + +#[perseus::template_rx(IndexPage)] +pub fn index_page() -> View { + view! { + p { "Hello World!" } + } +} + +#[perseus::head] +pub fn head() -> View { + view! { + title { "Index Page" } + } +} + +pub fn get_template() -> Template { + Template::new("index").template(index_page).head(head) +} diff --git a/examples/.base/src/templates/mod.rs b/examples/.base/src/templates/mod.rs new file mode 100644 index 0000000000..33edc959c9 --- /dev/null +++ b/examples/.base/src/templates/mod.rs @@ -0,0 +1 @@ +pub mod index; diff --git a/examples/fetching/.gitignore b/examples/fetching/.gitignore index 19f4c83304..9405098b45 100644 --- a/examples/fetching/.gitignore +++ b/examples/fetching/.gitignore @@ -1,2 +1 @@ - -.perseus/ \ No newline at end of file +.perseus/ diff --git a/examples/fetching/README.md b/examples/fetching/README.md new file mode 100644 index 0000000000..1f775bcd5c --- /dev/null +++ b/examples/fetching/README.md @@ -0,0 +1,5 @@ +# Fetching Example + +This examples demonstrates how to interact with a server with Perseus and fetch data on both the server and in the browser. Specifically, this uses `ureq` on the server-side (with Perseus' inbuilt caching mechanism to speed up development builds) and `reqwasm` on the client-side. + +On the server-side, this will simply fetch the server's IP address, and on the client-side it will fetch the message at `/.perseus/static/message.txt`, automatically served by Perseus from `static/message.txt`. The reason for fetching a file on the same site is to prevent issues with CORS, as documented in the book. diff --git a/examples/fetching/src/error_pages.rs b/examples/fetching/src/error_pages.rs new file mode 100644 index 0000000000..d472912966 --- /dev/null +++ b/examples/fetching/src/error_pages.rs @@ -0,0 +1,17 @@ +use perseus::{ErrorPages, Html}; +use sycamore::view; + +pub fn get_error_pages() -> ErrorPages { + let mut error_pages = ErrorPages::new(|url, status, err, _| { + view! { + p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) } + } + }); + error_pages.add_page(404, |_, _, _, _| { + view! { + p { "Page not found." } + } + }); + + error_pages +} diff --git a/examples/fetching/src/lib.rs b/examples/fetching/src/lib.rs index 7c92fec791..876ad309a7 100644 --- a/examples/fetching/src/lib.rs +++ b/examples/fetching/src/lib.rs @@ -1,16 +1,10 @@ -mod index; +mod error_pages; +mod templates; use perseus::define_app; define_app! { templates: [ - index::get_template::() + crate::templates::index::get_template::() ], - error_pages: perseus::ErrorPages::new(|url, status, err, _| { - sycamore::view! { - p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) } - } - }), - static_aliases: { - "/message" => "message.txt" - } + error_pages: crate::error_pages::get_error_pages() } diff --git a/examples/fetching/src/index.rs b/examples/fetching/src/templates/index.rs similarity index 72% rename from examples/fetching/src/index.rs rename to examples/fetching/src/templates/index.rs index 978e1ebab7..69507d720b 100644 --- a/examples/fetching/src/index.rs +++ b/examples/fetching/src/templates/index.rs @@ -4,7 +4,7 @@ use sycamore::prelude::*; #[perseus::make_rx(IndexPageStateRx)] pub struct IndexPageState { server_ip: String, - browser_ip: String, + browser_ip: Option, } #[perseus::template_rx(IndexPage)] @@ -16,7 +16,8 @@ pub fn index_page( ) -> View { // This will only run in the browser // `reqwasm` wraps browser-specific APIs, so we don't want it running on the server - if G::IS_BROWSER { + // If the browser IP has already been fetched (e.g. if we've come here for the second time in the same session), we won't bother re-fetching + if G::IS_BROWSER && browser_ip.get().is_none() { // Spawn a `Future` on this thread to fetch the data (`spawn_local` is re-exported from `wasm-bindgen-futures`) // Don't worry, this doesn't need to be sent to JavaScript for execution // @@ -24,20 +25,27 @@ pub fn index_page( perseus::spawn_local(cloned!(browser_ip => async move { // This interface may seem weird, that's because it wraps the browser's Fetch API // We request from a local path here because of CORS restrictions (see the book) - let body = reqwasm::http::Request::get("/message") + let body = reqwasm::http::Request::get("/.perseus/static/message.txt") .send() .await .unwrap() .text() .await .unwrap(); - browser_ip.set(body); + browser_ip.set(Some(body)); })); } + // If the future hasn't finished yet, we'll display a placeholder + // We use the wacky `&*` syntax to get the content of the `browser_ip` `Signal` and then we tell Rust to take a reference to that (we can't move it out because it might be used later) + let browser_ip_display = match &*browser_ip.get() { + Some(ip) => ip.to_string(), + None => "fetching".to_string(), + }; + view! { p { (format!("IP address of the server was: {}", server_ip.get())) } - p { (format!("The message at `/message` is: {}", browser_ip.get())) } + p { (format!("The message is: {}", browser_ip_display)) } } } @@ -63,9 +71,8 @@ pub async fn get_build_state( false, ) .await?; - // We'll start with a placeholder for the browser's IP, which will be fetched on the client-side Ok(IndexPageState { server_ip: body, - browser_ip: "fetching...".to_string(), + browser_ip: None, }) } diff --git a/examples/fetching/src/templates/mod.rs b/examples/fetching/src/templates/mod.rs new file mode 100644 index 0000000000..33edc959c9 --- /dev/null +++ b/examples/fetching/src/templates/mod.rs @@ -0,0 +1 @@ +pub mod index; diff --git a/examples/fetching/message.txt b/examples/fetching/static/message.txt similarity index 100% rename from examples/fetching/message.txt rename to examples/fetching/static/message.txt diff --git a/examples/global_state/.gitignore b/examples/global_state/.gitignore new file mode 100644 index 0000000000..9405098b45 --- /dev/null +++ b/examples/global_state/.gitignore @@ -0,0 +1 @@ +.perseus/ diff --git a/examples/global_state/Cargo.toml b/examples/global_state/Cargo.toml new file mode 100644 index 0000000000..bcbe95346e --- /dev/null +++ b/examples/global_state/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "perseus-example-global-state" +version = "0.3.2" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +perseus = { path = "../../packages/perseus", features = [ "hydrate" ] } +sycamore = "0.7" +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/examples/global_state/README.md b/examples/global_state/README.md new file mode 100644 index 0000000000..638c3a161d --- /dev/null +++ b/examples/global_state/README.md @@ -0,0 +1,3 @@ +# Global State Example + +This example demonstrates using global state to generate state that all pages have access to at build time. This state will then be made automatically reactive, and it can be modified easily. diff --git a/examples/global_state/index.html b/examples/global_state/index.html new file mode 100644 index 0000000000..edc8a66246 --- /dev/null +++ b/examples/global_state/index.html @@ -0,0 +1,10 @@ + + + + + + + +
+ + diff --git a/examples/global_state/src/error_pages.rs b/examples/global_state/src/error_pages.rs new file mode 100644 index 0000000000..d472912966 --- /dev/null +++ b/examples/global_state/src/error_pages.rs @@ -0,0 +1,17 @@ +use perseus::{ErrorPages, Html}; +use sycamore::view; + +pub fn get_error_pages() -> ErrorPages { + let mut error_pages = ErrorPages::new(|url, status, err, _| { + view! { + p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) } + } + }); + error_pages.add_page(404, |_, _, _, _| { + view! { + p { "Page not found." } + } + }); + + error_pages +} diff --git a/examples/global_state/src/global_state.rs b/examples/global_state/src/global_state.rs new file mode 100644 index 0000000000..9c84a671ff --- /dev/null +++ b/examples/global_state/src/global_state.rs @@ -0,0 +1,17 @@ +use perseus::{state::GlobalStateCreator, RenderFnResult}; + +pub fn get_global_state_creator() -> GlobalStateCreator { + GlobalStateCreator::new().build_state_fn(get_build_state) +} + +#[perseus::make_rx(AppStateRx)] +pub struct AppState { + pub test: String, +} + +#[perseus::autoserde(global_build_state)] +pub async fn get_build_state() -> RenderFnResult { + Ok(AppState { + test: "Hello World!".to_string(), + }) +} diff --git a/examples/global_state/src/lib.rs b/examples/global_state/src/lib.rs new file mode 100644 index 0000000000..46585045a9 --- /dev/null +++ b/examples/global_state/src/lib.rs @@ -0,0 +1,13 @@ +mod error_pages; +mod global_state; +mod templates; + +use perseus::define_app; +define_app! { + templates: [ + crate::templates::index::get_template::(), + crate::templates::about::get_template::() + ], + error_pages: crate::error_pages::get_error_pages(), + global_state_creator: crate::global_state::get_global_state_creator() +} diff --git a/examples/global_state/src/templates/about.rs b/examples/global_state/src/templates/about.rs new file mode 100644 index 0000000000..7b5617dbd4 --- /dev/null +++ b/examples/global_state/src/templates/about.rs @@ -0,0 +1,29 @@ +use perseus::{Html, Template}; +use sycamore::prelude::{view, SsrNode, View}; + +use crate::global_state::AppStateRx; + +// This template needs global state, but doesn't have any state of its own, so the first argument is the unit type `()` (which the macro will detect) +#[perseus::template_rx(AboutPage)] +pub fn about_page(_: (), global_state: AppStateRx) -> View { + let test = global_state.test; + let test_2 = test.clone(); + view! { + // The user can change the global state through an input, and the changes they make will be reflected throughout the app + p { (test.get()) } + input(bind:value = test_2) + + a(href = "") { "Index" } + } +} + +#[perseus::head] +pub fn head() -> View { + view! { + title { "About Page" } + } +} + +pub fn get_template() -> Template { + Template::new("about").template(about_page).head(head) +} diff --git a/examples/global_state/src/templates/index.rs b/examples/global_state/src/templates/index.rs new file mode 100644 index 0000000000..7235c87897 --- /dev/null +++ b/examples/global_state/src/templates/index.rs @@ -0,0 +1,29 @@ +use perseus::{Html, Template}; +use sycamore::prelude::{view, SsrNode, View}; + +use crate::global_state::AppStateRx; + +// This template needs global state, but doesn't have any state of its own, so the first argument is the unit type `()` (which the macro will detect) +#[perseus::template_rx(AboutPage)] +pub fn index_page(_: (), global_state: AppStateRx) -> View { + let test = global_state.test; + let test_2 = test.clone(); + view! { + // The user can change the global state through an input, and the changes they make will be reflected throughout the app + p { (test.get()) } + input(bind:value = test_2) + + a(href = "about") { "About" } + } +} + +#[perseus::head] +pub fn head() -> View { + view! { + title { "Index Page" } + } +} + +pub fn get_template() -> Template { + Template::new("index").template(index_page).head(head) +} diff --git a/examples/global_state/src/templates/mod.rs b/examples/global_state/src/templates/mod.rs new file mode 100644 index 0000000000..9b9cf18fc5 --- /dev/null +++ b/examples/global_state/src/templates/mod.rs @@ -0,0 +1,2 @@ +pub mod about; +pub mod index; diff --git a/examples/unreactive/.gitignore b/examples/unreactive/.gitignore new file mode 100644 index 0000000000..9405098b45 --- /dev/null +++ b/examples/unreactive/.gitignore @@ -0,0 +1 @@ +.perseus/ diff --git a/examples/unreactive/Cargo.toml b/examples/unreactive/Cargo.toml new file mode 100644 index 0000000000..ccd2678778 --- /dev/null +++ b/examples/unreactive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "perseus-example-unreactive" +version = "0.3.2" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +perseus = { path = "../../packages/perseus", features = [ "hydrate" ] } +sycamore = "0.7" +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/examples/unreactive/README.md b/examples/unreactive/README.md new file mode 100644 index 0000000000..4a65ff3853 --- /dev/null +++ b/examples/unreactive/README.md @@ -0,0 +1,3 @@ +# Unreactive Example + +This example shows you how to use the old `#[template(...)]` macro that doesn't make use of Perseus' reactive state platform. There are very few cases in which you'll actually use this, as it doesn't play well with more modern features like HSR and global state, so this example is mostly for legacy reference. diff --git a/examples/unreactive/index.html b/examples/unreactive/index.html new file mode 100644 index 0000000000..edc8a66246 --- /dev/null +++ b/examples/unreactive/index.html @@ -0,0 +1,10 @@ + + + + + + + +
+ + diff --git a/examples/unreactive/src/error_pages.rs b/examples/unreactive/src/error_pages.rs new file mode 100644 index 0000000000..d472912966 --- /dev/null +++ b/examples/unreactive/src/error_pages.rs @@ -0,0 +1,17 @@ +use perseus::{ErrorPages, Html}; +use sycamore::view; + +pub fn get_error_pages() -> ErrorPages { + let mut error_pages = ErrorPages::new(|url, status, err, _| { + view! { + p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) } + } + }); + error_pages.add_page(404, |_, _, _, _| { + view! { + p { "Page not found." } + } + }); + + error_pages +} diff --git a/examples/unreactive/src/lib.rs b/examples/unreactive/src/lib.rs new file mode 100644 index 0000000000..876ad309a7 --- /dev/null +++ b/examples/unreactive/src/lib.rs @@ -0,0 +1,10 @@ +mod error_pages; +mod templates; + +use perseus::define_app; +define_app! { + templates: [ + crate::templates::index::get_template::() + ], + error_pages: crate::error_pages::get_error_pages() +} diff --git a/examples/unreactive/src/templates/index.rs b/examples/unreactive/src/templates/index.rs new file mode 100644 index 0000000000..83e09ed214 --- /dev/null +++ b/examples/unreactive/src/templates/index.rs @@ -0,0 +1,44 @@ +use perseus::{Html, RenderFnResultWithCause, SsrNode, Template}; +use serde::{Deserialize, Serialize}; +use sycamore::prelude::{view, View}; + +// Without `#[make_rx(...)]`, we have to manually derive `Serialize` and `Deserialize` +#[derive(Serialize, Deserialize)] +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(IndexPage)] +#[sycamore::component(IndexPage)] +pub fn index_page(state: IndexPageState) -> View { + view! { + p { (state.greeting) } + } +} + +pub fn get_template() -> Template { + Template::new("index") + .build_state_fn(get_build_state) + .template(index_page) + .head(head) +} + +#[perseus::head] +pub fn head(_props: IndexPageState) -> View { + view! { + title { "Index Page" } + } +} + +#[perseus::autoserde(build_state)] +pub async fn get_build_state( + _path: String, + _locale: String, +) -> RenderFnResultWithCause { + Ok(IndexPageState { + greeting: "Hello World!".to_string(), + }) +} diff --git a/examples/unreactive/src/templates/mod.rs b/examples/unreactive/src/templates/mod.rs new file mode 100644 index 0000000000..33edc959c9 --- /dev/null +++ b/examples/unreactive/src/templates/mod.rs @@ -0,0 +1 @@ +pub mod index;