Skip to content

Commit 1e7e355

Browse files
committed
feat: ✨ built subcrate tro underlie cli functionality
This is very WIP!
1 parent 8b79be8 commit 1e7e355

File tree

29 files changed

+505
-41
lines changed

29 files changed

+505
-41
lines changed

Cargo.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ members = [
44
"packages/perseus-actix-web",
55
"packages/perseus-cli",
66
"examples/showcase/app",
7-
"examples/showcase/server-actix-web"
8-
]
7+
"examples/showcase/server-actix-web",
8+
"examples/basic",
9+
# FIXME
10+
"examples/basic/.perseus",
11+
"examples/basic/.perseus/server"
12+
]

examples/basic/.perseus/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist/

examples/basic/.perseus/Cargo.toml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# This crate defines the user's app in terms that WASM can understand, making development significantly simpler.
2+
3+
[package]
4+
name = "perseus-cli-builder"
5+
version = "0.1.0"
6+
edition = "2018"
7+
8+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9+
10+
[dependencies]
11+
# We alias here because the package name will change based on whatever's in the user's manifest
12+
app = { package = "perseus-basic", path = "../" }
13+
14+
perseus = { path = "../../../packages/perseus" }
15+
sycamore = { version = "0.5.1", features = ["ssr"] }
16+
sycamore-router = "0.5.1"
17+
web-sys = { version = "0.3", features = ["Headers", "Request", "RequestInit", "RequestMode", "Response", "ReadableStream", "Window"] }
18+
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
19+
serde = { version = "1", features = ["derive"] }
20+
serde_json = "1" # Possibly don't need?
21+
console_error_panic_hook = "0.1.6"
22+
urlencoding = "2.1"
23+
futures = "0.3"
24+
25+
# This section is needed for WASM Pack (which we use instead of Trunk for flexibility)
26+
[lib]
27+
crate-type = ["cdylib", "rlib"]
28+
29+
# We define a binary for building, serving, and doing both
30+
[[bin]]
31+
name = "perseus-internal"
32+
path = "src/bin/build.rs"

examples/basic/.perseus/bonnie.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# This file is temporary until the CLI is ready!
2+
3+
version="0.3.1"
4+
5+
[scripts]
6+
build = [
7+
"cargo run",
8+
"wasm-pack build --target web",
9+
"rm -rf dist/pkg",
10+
"mv pkg/ dist/",
11+
"rollup main.js --format iife --file dist/pkg/bundle.js"
12+
]
13+
serve = [
14+
"cd server",
15+
"cargo run"
16+
]

examples/basic/.perseus/main.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import init, { run } from "./dist/pkg/perseus_cli_builder.js";
2+
async function main() {
3+
await init("/.perseus/bundle.wasm");
4+
run();
5+
}
6+
main();
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# This crate defines the user's app in terms that WASM can understand, making development significantly simpler.
2+
3+
[package]
4+
name = "perseus-cli-server"
5+
version = "0.1.0"
6+
edition = "2018"
7+
8+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9+
10+
[dependencies]
11+
# We alias here because the package name will change based on whatever's in the user's manifest
12+
app = { package = "perseus-basic", path = "../../" }
13+
14+
perseus-actix-web = { path = "../../../../packages/perseus-actix-web" }
15+
actix-web = "3.3"
16+
futures = "0.3"
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use perseus_actix_web::{configurer, Options};
2+
use actix_web::{HttpServer, App};
3+
use futures::executor::block_on;
4+
use app::{get_templates_map, get_config_manager};
5+
use std::env;
6+
7+
#[actix_web::main]
8+
async fn main() -> std::io::Result<()> {
9+
// So we don't have to define a different `FsConfigManager` just for the server, we shift the execution context to the same level as everything else
10+
// The server has to be a separate crate because otherwise the dependencies don't work with WASM bundling
11+
env::set_current_dir("../").unwrap();
12+
13+
let host = env::var("HOST").unwrap_or_else(|_| "localhost".to_string());
14+
let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string()).parse::<u16>();
15+
if let Ok(port) = port {
16+
HttpServer::new(|| {
17+
App::new()
18+
.configure(
19+
block_on(configurer(
20+
Options {
21+
index: "../index.html".to_string(), // The user must define their own `index.html` file
22+
js_bundle: "dist/pkg/bundle.js".to_string(),
23+
// Our crate has the same name, so this will be predictable
24+
wasm_bundle: "dist/pkg/perseus_cli_builder_bg.wasm".to_string(),
25+
templates_map: get_templates_map()
26+
},
27+
get_config_manager()
28+
))
29+
)
30+
})
31+
.bind((host, port))?
32+
.run()
33+
.await
34+
} else {
35+
eprintln!("Port must be a number.");
36+
Ok(())
37+
}
38+
39+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use futures::executor::block_on;
2+
use perseus::{build_templates, SsrNode};
3+
use app::{get_templates_vec, get_config_manager};
4+
5+
fn main() {
6+
let config_manager = get_config_manager();
7+
8+
let fut = build_templates(
9+
get_templates_vec::<SsrNode>(),
10+
&config_manager,
11+
);
12+
let res = block_on(fut);
13+
if let Err(err) = res {
14+
eprintln!("Static generation failed: '{}'", err);
15+
} else {
16+
println!("Static generation successfully completed!");
17+
}
18+
}

examples/basic/.perseus/src/build.rs

Whitespace-only changes.

examples/basic/.perseus/src/lib.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use app::{
2+
AppRoute,
3+
APP_ROUTE,
4+
get_error_pages,
5+
match_route
6+
};
7+
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
8+
use sycamore::prelude::template;
9+
use sycamore_router::BrowserRouter;
10+
use perseus::{
11+
app_shell
12+
};
13+
14+
/// The entrypoint into the app itself. This will be compiled to WASM and actually executed, rendering the rest of the app.
15+
#[wasm_bindgen]
16+
pub fn run() -> Result<(), JsValue> {
17+
// If we're in development, panics should go to the console
18+
if cfg!(debug_assertions) {
19+
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
20+
}
21+
// Get the root (for the router) we'll be injecting page content into
22+
let root = web_sys::window()
23+
.unwrap()
24+
.document()
25+
.unwrap()
26+
.query_selector(APP_ROUTE)
27+
.unwrap()
28+
.unwrap();
29+
30+
sycamore::render_to(
31+
|| {
32+
template! {
33+
BrowserRouter(|route: AppRoute| {
34+
// TODO improve performance rather than naively copying error pages for every template
35+
match route {
36+
// We handle the 404 for the user for convenience
37+
AppRoute::NotFound => get_error_pages().get_template_for_page("", &404, "not found"),
38+
// All other routes are based on the user's given statements
39+
_ => {
40+
let (name, render_fn) = match_route(route);
41+
app_shell(
42+
name,
43+
render_fn,
44+
get_error_pages()
45+
)
46+
}
47+
}
48+
})
49+
}
50+
},
51+
&root,
52+
);
53+
54+
Ok(())
55+
}

examples/basic/.perseus/src/serve.rs

Whitespace-only changes.

examples/basic/Cargo.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "perseus-basic"
3+
version = "0.1.0"
4+
edition = "2018"
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" }
10+
sycamore = { version = "0.5.1", features = ["ssr"] }
11+
sycamore-router = "0.5.1"
12+
serde = { version = "1", features = ["derive"] }
13+
serde_json = "1" # Possibly don't need?
14+
15+
# This section is needed for WASM Pack (which we use instead of Trunk for flexibility)
16+
[lib]
17+
crate-type = ["cdylib", "rlib"]

examples/basic/index.html

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Perseus Starter App</title>
8+
<!-- Importing this runs Perseus -->
9+
<script src="/.perseus/bundle.js" defer></script>
10+
</head>
11+
<body>
12+
<div id="root"></div>
13+
</body>
14+
</html>

examples/basic/src/error_pages.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use perseus::{ErrorPages};
2+
use sycamore::template;
3+
4+
pub fn get_error_pages() -> ErrorPages {
5+
let mut error_pages = ErrorPages::new(Box::new(|_, _, _| {
6+
template! {
7+
p { "Another error occurred." }
8+
}
9+
}));
10+
error_pages.add_page(
11+
404,
12+
Box::new(|_, _, _| {
13+
template! {
14+
p { "Page not found." }
15+
}
16+
}),
17+
);
18+
error_pages.add_page(
19+
400,
20+
Box::new(|_, _, _| {
21+
template! {
22+
p { "Client error occurred..." }
23+
}
24+
}),
25+
);
26+
27+
error_pages
28+
}

examples/basic/src/lib.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
mod pages;
2+
mod error_pages;
3+
4+
use perseus::define_app;
5+
6+
#[derive(perseus::Route)]
7+
pub enum Route {
8+
#[to("/")]
9+
Index,
10+
#[to("/about")]
11+
About,
12+
#[not_found]
13+
NotFound,
14+
}
15+
define_app!{
16+
root: "#root",
17+
route: Route,
18+
router: [
19+
Route::Index => [
20+
"index".to_string(),
21+
pages::index::template_fn()
22+
],
23+
Route::About => [
24+
"about".to_string(),
25+
pages::about::template_fn()
26+
]
27+
],
28+
error_pages: crate::error_pages::get_error_pages(),
29+
templates: [
30+
crate::pages::index::get_page::<G>(),
31+
crate::pages::about::get_page::<G>()
32+
]
33+
// config_manager: perseus::FsConfigManager::new()
34+
}

examples/basic/src/pages/about.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use perseus::Template;
2+
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
3+
use std::sync::Arc;
4+
5+
#[component(AboutPage<G>)]
6+
pub fn about_page() -> SycamoreTemplate<G> {
7+
template! {
8+
p { "About." }
9+
}
10+
}
11+
12+
pub fn get_page<G: GenericNode>() -> Template<G> {
13+
Template::new("about").template(template_fn())
14+
}
15+
16+
pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
17+
Arc::new(|_| {
18+
template! {
19+
AboutPage()
20+
}
21+
})
22+
}

examples/basic/src/pages/index.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use perseus::{Template, StringResultWithCause};
2+
use serde::{Deserialize, Serialize};
3+
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
4+
use std::sync::Arc;
5+
6+
#[derive(Serialize, Deserialize, Debug)]
7+
pub struct IndexPageProps {
8+
pub greeting: String,
9+
}
10+
11+
#[component(IndexPage<G>)]
12+
pub fn index_page(props: IndexPageProps) -> SycamoreTemplate<G> {
13+
template! {
14+
p {(props.greeting)}
15+
a(href = "/about") { "About!" }
16+
}
17+
}
18+
19+
pub fn get_page<G: GenericNode>() -> Template<G> {
20+
Template::new("index")
21+
.build_state_fn(Arc::new(get_static_props))
22+
.template(template_fn())
23+
}
24+
25+
pub async fn get_static_props(_path: String) -> StringResultWithCause<String> {
26+
Ok(serde_json::to_string(&IndexPageProps {
27+
greeting: "Hello World!".to_string(),
28+
})
29+
.unwrap())
30+
}
31+
32+
pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
33+
Arc::new(|props: Option<String>| {
34+
template! {
35+
IndexPage(
36+
serde_json::from_str::<IndexPageProps>(&props.unwrap()).unwrap()
37+
)
38+
}
39+
})
40+
}

examples/basic/src/pages/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod about;
2+
pub mod index;

examples/showcase/app/src/bin/build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use perseus::{build_templates, FsConfigManager, SsrNode};
33
use perseus_showcase_app::pages;
44

55
fn main() {
6-
let config_manager = FsConfigManager::new();
6+
let config_manager = FsConfigManager::new("./dist".to_string());
77

88
let fut = build_templates(
99
vec![

examples/showcase/server-actix-web/src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ async fn main() -> std::io::Result<()> {
1616
wasm_bundle: "../app/pkg/perseus_showcase_app_bg.wasm".to_string(),
1717
templates_map: pages::get_templates_map::<SsrNode>()
1818
},
19-
FsConfigManager::new()
19+
FsConfigManager::new("../app/dist".to_string())
2020
))
2121
)
2222
})
2323
.bind(("localhost", 8080))?
2424
.run()
2525
.await
26-
}
26+
}

0 commit comments

Comments
 (0)