Skip to content

Commit

Permalink
feat: ✨ added actix-web integration
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Aug 22, 2021
1 parent dcbabc0 commit 0e0f2f1
Show file tree
Hide file tree
Showing 21 changed files with 215 additions and 103 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"packages/perseus",
"packages/perseus-actix-web",
"examples/showcase/app",
"examples/showcase/server"
"examples/showcase/server-actix-web"
]
5 changes: 3 additions & 2 deletions bonnie.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ dev.subcommands.build = [
"cd examples/showcase",
"bonnie build --watch"
]
dev.subcommands.serve = [
dev.subcommands.serve.cmd = [
"cd examples/showcase",
"bonnie serve"
"bonnie serve %server"
]
dev.subcommands.serve.args = [ "server" ]
build = "cargo build"
test = "cargo watch -x \"test\""
check = "cargo check && cargo fmt -- --check && cargo clippy && cargo test" # This will be run on CI as well
Expand Down
3 changes: 2 additions & 1 deletion examples/showcase/app/src/pages/about.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use perseus::template::Template;
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
use std::sync::Arc;

#[component(AboutPage<G>)]
pub fn about_page() -> SycamoreTemplate<G> {
Expand All @@ -13,7 +14,7 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|_| {
Arc::new(|_| {
template! {
AboutPage()
}
Expand Down
5 changes: 3 additions & 2 deletions examples/showcase/app/src/pages/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use perseus::template::Template;
use perseus::errors::ErrorCause;
use serde::{Deserialize, Serialize};
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
use std::sync::Arc;

#[derive(Serialize, Deserialize, Debug)]
pub struct IndexPageProps {
Expand All @@ -18,7 +19,7 @@ pub fn index_page(props: IndexPageProps) -> SycamoreTemplate<G> {

pub fn get_page<G: GenericNode>() -> Template<G> {
Template::new("index")
.build_state_fn(Box::new(get_static_props))
.build_state_fn(Arc::new(get_static_props))
.template(template_fn())
}

Expand All @@ -30,7 +31,7 @@ pub async fn get_static_props(_path: String) -> Result<String, (String, ErrorCau
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|props: Option<String>| {
Arc::new(|props: Option<String>| {
template! {
IndexPage(
serde_json::from_str::<IndexPageProps>(&props.unwrap()).unwrap()
Expand Down
5 changes: 3 additions & 2 deletions examples/showcase/app/src/pages/ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use perseus::template::{Template};
use perseus::Request;
use serde::{Deserialize, Serialize};
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
use std::sync::Arc;

#[derive(Serialize, Deserialize)]
pub struct IpPageProps {
Expand All @@ -24,7 +25,7 @@ pub fn dashboard_page(props: IpPageProps) -> SycamoreTemplate<G> {

pub fn get_page<G: GenericNode>() -> Template<G> {
Template::new("ip")
.request_state_fn(Box::new(get_request_state))
.request_state_fn(Arc::new(get_request_state))
.template(template_fn())
}

Expand All @@ -44,7 +45,7 @@ pub async fn get_request_state(_path: String, req: Request) -> Result<String, (S
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|props: Option<String>| {
Arc::new(|props: Option<String>| {
template! {
IpPage(
serde_json::from_str::<IpPageProps>(&props.unwrap()).unwrap()
Expand Down
3 changes: 2 additions & 1 deletion examples/showcase/app/src/pages/new_post.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use perseus::template::Template;
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
use std::sync::Arc;

#[component(NewPostPage<G>)]
pub fn new_post_page() -> SycamoreTemplate<G> {
Expand All @@ -13,7 +14,7 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|_| {
Arc::new(|_| {
template! {
NewPostPage()
}
Expand Down
7 changes: 4 additions & 3 deletions examples/showcase/app/src/pages/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use perseus::template::Template;
use perseus::errors::ErrorCause;
use serde::{Deserialize, Serialize};
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
use std::sync::Arc;

#[derive(Serialize, Deserialize)]
pub struct PostPageProps {
Expand All @@ -25,8 +26,8 @@ pub fn post_page(props: PostPageProps) -> SycamoreTemplate<G> {

pub fn get_page<G: GenericNode>() -> Template<G> {
Template::new("post")
.build_paths_fn(Box::new(get_static_paths))
.build_state_fn(Box::new(get_static_props))
.build_paths_fn(Arc::new(get_static_paths))
.build_state_fn(Arc::new(get_static_props))
.incremental_path_rendering(true)
.template(template_fn())
}
Expand Down Expand Up @@ -58,7 +59,7 @@ pub async fn get_static_paths() -> Result<Vec<String>, String> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|props: Option<String>| {
Arc::new(|props: Option<String>| {
template! {
PostPage(
serde_json::from_str::<PostPageProps>(&props.unwrap()).unwrap()
Expand Down
7 changes: 4 additions & 3 deletions examples/showcase/app/src/pages/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use perseus::template::Template;
use perseus::errors::ErrorCause;
use serde::{Deserialize, Serialize};
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
use std::sync::Arc;

#[derive(Serialize, Deserialize, Debug)]
pub struct TimePageProps {
Expand All @@ -21,8 +22,8 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
// This page will revalidate every five seconds (to illustrate revalidation)
.revalidate_after("5s".to_string())
.incremental_path_rendering(true)
.build_state_fn(Box::new(get_build_state))
.build_paths_fn(Box::new(get_build_paths))
.build_state_fn(Arc::new(get_build_state))
.build_paths_fn(Arc::new(get_build_paths))
}

pub async fn get_build_state(_path: String) -> Result<String, (String, ErrorCause)> {
Expand All @@ -37,7 +38,7 @@ pub async fn get_build_paths() -> Result<Vec<String>, String> {
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|props: Option<String>| {
Arc::new(|props: Option<String>| {
template! {
TimePage(
serde_json::from_str::<TimePageProps>(&props.unwrap()).unwrap()
Expand Down
7 changes: 4 additions & 3 deletions examples/showcase/app/src/pages/time_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use perseus::template::Template;
use perseus::errors::ErrorCause;
use serde::{Deserialize, Serialize};
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
use std::sync::Arc;

#[derive(Serialize, Deserialize, Debug)]
pub struct TimePageProps {
Expand All @@ -21,8 +22,8 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
// This page will revalidate every five seconds (to illustrate revalidation)
// Try changing this to a week, even though the below custom logic says to always revalidate, we'll only do it weekly
.revalidate_after("5s".to_string())
.should_revalidate_fn(Box::new(|| async { Ok(true) }))
.build_state_fn(Box::new(get_build_state))
.should_revalidate_fn(Arc::new(|| async { Ok(true) }))
.build_state_fn(Arc::new(get_build_state))
}

pub async fn get_build_state(_path: String) -> Result<String, (String, ErrorCause)> {
Expand All @@ -33,7 +34,7 @@ pub async fn get_build_state(_path: String) -> Result<String, (String, ErrorCaus
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|props: Option<String>| {
Arc::new(|props: Option<String>| {
template! {
TimePage(
serde_json::from_str::<TimePageProps>(&props.unwrap()).unwrap()
Expand Down
5 changes: 3 additions & 2 deletions examples/showcase/bonnie.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ build.subcommands.--watch = [
"bonnie clean",
"find ../../ -not -path \"../../target/*\" -not -path \"../../.git/*\" -not -path \"../../examples/showcase/app/dist/*\" | entr -s \"bonnie build\""
]
serve = [
"cd server",
serve.cmd = [
"cd server-%server",
"cargo watch -w ../../../ -x \"run\""
]
serve.args = [ "server" ]
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[package]
name = "perseus-showcase-server"
name = "perseus-showcase-server-actix-web"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
perseus = { path = "../../../packages/perseus" }
perseus-actix-web = { path = "../../../packages/perseus-actix-web" }
actix-web = "3.3"
actix-files = "0.5"
urlencoding = "2.1"
Expand Down
26 changes: 26 additions & 0 deletions examples/showcase/server-actix-web/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use perseus::config_manager::FsConfigManager;
use perseus_actix_web::{configurer, Options};
use perseus_showcase_app::pages;
use sycamore::SsrNode;
use actix_web::{HttpServer, App};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.configure(
configurer(
Options {
index: "../app/index.html".to_string(),
js_bundle: "../app/pkg/bundle.js".to_string(),
wasm_bundle: "../app/pkg/perseus_showcase_app_bg.wasm".to_string(),
templates_map: pages::get_templates_map::<SsrNode>()
},
FsConfigManager::new()
)
)
})
.bind(("localhost", 8080))?
.run()
.await
}
67 changes: 0 additions & 67 deletions examples/showcase/server/src/main.rs

This file was deleted.

16 changes: 16 additions & 0 deletions packages/perseus-actix-web/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "perseus-actix-web"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
perseus = { path = "../perseus" }
actix-web = "3.3"
actix-files = "0.5"
urlencoding = "2.1"
serde_json = "1"
error-chain = "0.12"
futures = "0.3"
sycamore = { version = "0.5.1", features = ["ssr"] }
55 changes: 55 additions & 0 deletions packages/perseus-actix-web/src/configurer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use actix_files::NamedFile;
use actix_web::web;
use sycamore::prelude::SsrNode;

use perseus::{
config_manager::ConfigManager,
serve::get_render_cfg,
template::TemplateMap,
};

use crate::page_data::page_data;

/// The options for setting up the Actix Web integration. This should be literally constructed, as nothing is optional.
#[derive(Clone)]
pub struct Options {
/// The location on the filesystem of your JavaScript bundle.
pub js_bundle: String,
/// The location on the filesystem of your WASM bundle.
pub wasm_bundle: String,
/// The location on the filesystem of your `index.html` file that includes the JS bundle.
pub index: String,
/// A `HashMap` of your app's templates by their paths.
pub templates_map: TemplateMap<SsrNode>
}

async fn js_bundle(opts: web::Data<Options>) -> std::io::Result<NamedFile> {
NamedFile::open(&opts.js_bundle)
}
async fn wasm_bundle(opts: web::Data<Options>) -> std::io::Result<NamedFile> {
NamedFile::open(&opts.wasm_bundle)
}
async fn index(opts: web::Data<Options>) -> std::io::Result<NamedFile> {
NamedFile::open(&opts.index)
}

/// Configures an existing Actix Web app for Perseus. This returns a function that does the configuring so it can take arguments.
pub fn configurer<C: ConfigManager + 'static>(opts: Options, config_manager: C) -> impl Fn(&mut web::ServiceConfig) {
move |cfg: &mut web::ServiceConfig| {
cfg
.data(get_render_cfg().expect("Couldn't get render configuration!"))
.data(config_manager.clone())
.data(opts.clone())
// TODO chunk JS and WASM bundles
// These allow getting the basic app code (not including the static data)
// This contains everything in the spirit of a pseudo-SPA
.route("/.perseus/bundle.js", web::get().to(js_bundle))
.route("/.perseus/bundle.wasm", web::get().to(wasm_bundle))
// This allows getting the static HTML/JSON of a page
// We stream both together in a single JSON object so SSR works (otherwise we'd have request IDs and weird caching...)
.route("/.perseus/page/{filename:.*}", web::get().to(page_data::<C>))
// For everything else, we'll serve the app shell directly
// FIXME
.route("*", web::get().to(index));
}
}
Loading

0 comments on commit 0e0f2f1

Please sign in to comment.