Skip to content

Commit 766dd44

Browse files
committed
docs(examples): added js interop example
1 parent 191d8c9 commit 766dd44

File tree

10 files changed

+173
-0
lines changed

10 files changed

+173
-0
lines changed

examples/core/js_interop/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
target_engine/
3+
target_wasm/

examples/core/js_interop/Cargo.toml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "perseus-example-js-interop"
3+
version = "0.4.0-beta.3"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] }
10+
sycamore = "=0.8.0-beta.7"
11+
serde = { version = "1", features = ["derive"] }
12+
serde_json = "1"
13+
14+
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
15+
fantoccini = "0.17"
16+
17+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
18+
tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] }
19+
# This is an internal convenience crate that exposes all integrations through features for testing
20+
perseus-integration = { path = "../../../packages/perseus-integration", default-features = false }
21+
22+
[target.'cfg(target_arch = "wasm32")'.dependencies]
23+
wasm-bindgen = "0.2"
24+
25+
[lib]
26+
name = "lib"
27+
path = "src/lib.rs"
28+
crate-type = [ "cdylib", "rlib" ]
29+
30+
[[bin]]
31+
name = "perseus-example-js-interop"
32+
path = "src/lib.rs"
33+
34+
[package.metadata.wasm-pack.profile.release]
35+
wasm-opt = [ "-Oz" ]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "my-app"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
perseus = { version = "=0.4.0-beta.3", features = [ "hydrate" ] }
10+
sycamore = "=0.8.0-beta.7"
11+
serde = { version = "1", features = ["derive"] }
12+
serde_json = "1"
13+
14+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
15+
tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] }
16+
perseus-warp = { version = "=0.4.0-beta.3", features = [ "dflt-server" ] }
17+
18+
[target.'cfg(target_arch = "wasm32")'.dependencies]
19+
wasm-bindgen = "0.2"
20+
21+
[lib]
22+
name = "lib"
23+
path = "src/lib.rs"
24+
crate-type = [ "cdylib", "rlib" ]
25+
26+
[[bin]]
27+
name = "my-app"
28+
path = "src/lib.rs"

examples/core/js_interop/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Basic Example
2+
3+
This is a basic example of Perseus that shows the fundamentals of a slightly more advanced Perseus app. This is considered a core example because it not only contains end-to-end tests for Perseus itself, it's also the site of the development of the default Perseus engine. For that reason, the `.perseus/` directory is checked into Git here, and this is used as the single source of truth for the default engine. The reason for developing it here is to provide the context of an actual usage of Perseus, which makes a number of things easier. Then, some scripts bridge the gap to make the engine integrate into the CLI (you shouldn't have to worry about this when working in the Perseus repo as long as you're using the `bonnie dev example ...` script).
4+
5+
Note that this example used to be more complex, illustrating features such as setting custom headers and hosting static content, though demonstrations of these have since been moved to independent examples.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Changes some text in a specific HTML element
2+
// If possible, try to never use JS, `web-sys` will hopefully cover everything you want to do
3+
// But, sometimes, it's unavoidable, so Perseus supports interop easily
4+
export function changeMessage() {
5+
document.getElementById("message").innerHTML = "Message from JS!";
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use perseus::{ErrorPages, Html};
2+
use sycamore::view;
3+
4+
pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
5+
let mut error_pages = ErrorPages::new(|cx, url, status, err, _| {
6+
view! { cx,
7+
p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) }
8+
}
9+
});
10+
error_pages.add_page(404, |cx, _, _, _, _| {
11+
view! { cx,
12+
p { "Page not found." }
13+
}
14+
});
15+
16+
error_pages
17+
}

examples/core/js_interop/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
mod error_pages;
2+
mod templates;
3+
4+
use perseus::{Html, PerseusApp};
5+
6+
#[perseus::main(perseus_integration::dflt_server)]
7+
pub fn main<G: Html>() -> PerseusApp<G> {
8+
PerseusApp::new()
9+
.template(crate::templates::index::get_template)
10+
.error_pages(crate::error_pages::get_error_pages)
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use perseus::{Html, Template};
2+
use sycamore::prelude::{view, Scope, View};
3+
#[cfg(target_arch = "wasm32")]
4+
use wasm_bindgen::prelude::wasm_bindgen;
5+
6+
#[perseus::template_rx]
7+
pub fn index_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
8+
view! { cx,
9+
// We'll use JS to change this message manually
10+
p(id = "message") { "Hello World!" }
11+
button(id = "change-message", on:click = |_| {
12+
#[cfg(target_arch = "wasm32")]
13+
change_message()
14+
}) { "Change message with JS" }
15+
}
16+
}
17+
18+
pub fn get_template<G: Html>() -> Template<G> {
19+
Template::new("index").template(index_page)
20+
}
21+
22+
// Of course, JS will only run in the browser, so this should be browser-only
23+
#[cfg(target_arch = "wasm32")]
24+
// This path should be relative to the root of your project
25+
// That file will then be hosted behind `/.perseus/` and automatically fetched as needed
26+
#[wasm_bindgen(module = "/src/changeMessage.js")]
27+
extern "C" {
28+
#[wasm_bindgen(js_name = "changeMessage")]
29+
fn change_message();
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod index;
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use fantoccini::{Client, Locator};
2+
use perseus::wait_for_checkpoint;
3+
4+
#[perseus::test]
5+
async fn main(c: &mut Client) -> Result<(), fantoccini::error::CmdError> {
6+
c.goto("http://localhost:8080").await?;
7+
wait_for_checkpoint!("begin", 0, c);
8+
let url = c.current_url().await?;
9+
assert!(url.as_ref().starts_with("http://localhost:8080"));
10+
11+
// The greeting was passed through using build state
12+
wait_for_checkpoint!("initial_state_present", 0, c);
13+
wait_for_checkpoint!("page_visible", 0, c);
14+
let greeting = c.find(Locator::Css("p")).await?.text().await?;
15+
assert_eq!(greeting, "Hello World!");
16+
// For some reason, retrieving the inner HTML or text of a `<title>` doens't
17+
// work
18+
let title = c.find(Locator::Css("title")).await?.html(false).await?;
19+
assert!(title.contains("Index Page"));
20+
21+
// Go to `/about`
22+
c.find(Locator::Id("about-link")).await?.click().await?;
23+
let url = c.current_url().await?;
24+
assert!(url.as_ref().starts_with("http://localhost:8080/about"));
25+
wait_for_checkpoint!("initial_state_not_present", 0, c);
26+
wait_for_checkpoint!("page_visible", 1, c);
27+
// Make sure the hardcoded text there exists
28+
let text = c.find(Locator::Css("p")).await?.text().await?;
29+
assert_eq!(text, "About.");
30+
let title = c.find(Locator::Css("title")).await?.html(false).await?;
31+
assert!(title.contains("About Page"));
32+
// Make sure we get initial state if we refresh
33+
c.refresh().await?;
34+
wait_for_checkpoint!("initial_state_present", 0, c);
35+
36+
Ok(())
37+
}

0 commit comments

Comments
 (0)