Skip to content

Commit

Permalink
docs(examples): added some new examples and a template for more
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jan 30, 2022
1 parent cb2f49d commit 07289f6
Show file tree
Hide file tree
Showing 33 changed files with 351 additions and 19 deletions.
1 change: 1 addition & 0 deletions examples/.base/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.perseus/
12 changes: 12 additions & 0 deletions examples/.base/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
7 changes: 7 additions & 0 deletions examples/.base/README.md
Original file line number Diff line number Diff line change
@@ -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-<name>` (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)!
10 changes: 10 additions & 0 deletions examples/.base/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
</body>
</html>
17 changes: 17 additions & 0 deletions examples/.base/src/error_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use perseus::{ErrorPages, Html};
use sycamore::view;

pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
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
}
10 changes: 10 additions & 0 deletions examples/.base/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mod error_pages;
mod templates;

use perseus::define_app;
define_app! {
templates: [
crate::templates::index::get_template::<G>()
],
error_pages: crate::error_pages::get_error_pages()
}
20 changes: 20 additions & 0 deletions examples/.base/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, SsrNode, View};

#[perseus::template_rx(IndexPage)]
pub fn index_page() -> View<G> {
view! {
p { "Hello World!" }
}
}

#[perseus::head]
pub fn head() -> View<SsrNode> {
view! {
title { "Index Page" }
}
}

pub fn get_template<G: Html>() -> Template<G> {
Template::new("index").template(index_page).head(head)
}
1 change: 1 addition & 0 deletions examples/.base/src/templates/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod index;
3 changes: 1 addition & 2 deletions examples/fetching/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@

.perseus/
.perseus/
5 changes: 5 additions & 0 deletions examples/fetching/README.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions examples/fetching/src/error_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use perseus::{ErrorPages, Html};
use sycamore::view;

pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
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
}
14 changes: 4 additions & 10 deletions examples/fetching/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
mod index;
mod error_pages;
mod templates;

use perseus::define_app;
define_app! {
templates: [
index::get_template::<G>()
crate::templates::index::get_template::<G>()
],
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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use sycamore::prelude::*;
#[perseus::make_rx(IndexPageStateRx)]
pub struct IndexPageState {
server_ip: String,
browser_ip: String,
browser_ip: Option<String>,
}

#[perseus::template_rx(IndexPage)]
Expand All @@ -16,28 +16,36 @@ pub fn index_page(
) -> View<G> {
// 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
//
// We want to access the `message` `Signal`, so we'll clone it in (and then we need `move` because this has to be `'static`)
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)) }
}
}

Expand All @@ -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,
})
}
1 change: 1 addition & 0 deletions examples/fetching/src/templates/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod index;
File renamed without changes.
1 change: 1 addition & 0 deletions examples/global_state/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.perseus/
12 changes: 12 additions & 0 deletions examples/global_state/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 3 additions & 0 deletions examples/global_state/README.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 10 additions & 0 deletions examples/global_state/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
</body>
</html>
17 changes: 17 additions & 0 deletions examples/global_state/src/error_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use perseus::{ErrorPages, Html};
use sycamore::view;

pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
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
}
17 changes: 17 additions & 0 deletions examples/global_state/src/global_state.rs
Original file line number Diff line number Diff line change
@@ -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<AppState> {
Ok(AppState {
test: "Hello World!".to_string(),
})
}
13 changes: 13 additions & 0 deletions examples/global_state/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod error_pages;
mod global_state;
mod templates;

use perseus::define_app;
define_app! {
templates: [
crate::templates::index::get_template::<G>(),
crate::templates::about::get_template::<G>()
],
error_pages: crate::error_pages::get_error_pages(),
global_state_creator: crate::global_state::get_global_state_creator()
}
29 changes: 29 additions & 0 deletions examples/global_state/src/templates/about.rs
Original file line number Diff line number Diff line change
@@ -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<G> {
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<SsrNode> {
view! {
title { "About Page" }
}
}

pub fn get_template<G: Html>() -> Template<G> {
Template::new("about").template(about_page).head(head)
}
29 changes: 29 additions & 0 deletions examples/global_state/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -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<G> {
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<SsrNode> {
view! {
title { "Index Page" }
}
}

pub fn get_template<G: Html>() -> Template<G> {
Template::new("index").template(index_page).head(head)
}
2 changes: 2 additions & 0 deletions examples/global_state/src/templates/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod about;
pub mod index;
1 change: 1 addition & 0 deletions examples/unreactive/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.perseus/
12 changes: 12 additions & 0 deletions examples/unreactive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 3 additions & 0 deletions examples/unreactive/README.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 10 additions & 0 deletions examples/unreactive/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
</body>
</html>
17 changes: 17 additions & 0 deletions examples/unreactive/src/error_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use perseus::{ErrorPages, Html};
use sycamore::view;

pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
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
}
Loading

0 comments on commit 07289f6

Please sign in to comment.