Skip to content

Commit

Permalink
feat: ✨ added ssr
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jul 31, 2021
1 parent 7a334cf commit ac79996
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 4 deletions.
3 changes: 2 additions & 1 deletion examples/showcase/app/src/bin/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ fn main() {
build_templates(vec![
pages::index::get_page::<SsrNode>(),
pages::about::get_page::<SsrNode>(),
pages::post::get_page::<SsrNode>()
pages::post::get_page::<SsrNode>(),
pages::ip::get_page::<SsrNode>()
], &config_manager).expect("Static generation failed!");

println!("Static generation successfully completed!");
Expand Down
6 changes: 6 additions & 0 deletions examples/showcase/app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ enum AppRoute {
Post {
slug: String
},
#[to("/ip")]
Ip,
#[not_found]
NotFound
}
Expand Down Expand Up @@ -50,6 +52,10 @@ pub fn run() -> Result<(), JsValue> {
format!("post/{}", slug),
pages::post::template_fn()
),
AppRoute::Ip => app_shell(
"ip".to_string(),
pages::ip::template_fn()
),
AppRoute::NotFound => template! {
p {"Not Found."}
}
Expand Down
43 changes: 43 additions & 0 deletions examples/showcase/app/src/pages/ip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// This page illustrates SSR

use serde::{Serialize, Deserialize};
use sycamore::prelude::{template, component, GenericNode, Template as SycamoreTemplate};
use perseus::template::Template;

#[derive(Serialize, Deserialize)]
pub struct IpPageProps {
ip: String,
}

#[component(IpPage<G>)]
pub fn dashboard_page(props: IpPageProps) -> SycamoreTemplate<G> {
template! {
p {
(
format!("Your IP address is {}.", props.ip)
)
}
}
}

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

pub fn get_request_state(_path: String) -> String {
serde_json::to_string(
&IpPageProps {
ip: "x.x.x.x".to_string()
}
).unwrap()
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Box::new(|props: Option<String>| template! {
IpPage(
serde_json::from_str::<IpPageProps>(&props.unwrap()).unwrap()
)
})
}
4 changes: 3 additions & 1 deletion examples/showcase/app/src/pages/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod index;
pub mod about;
pub mod post;
pub mod ip;

use perseus::{get_templates_map, template::Template};
use sycamore::prelude::GenericNode;
Expand All @@ -11,6 +12,7 @@ pub fn get_templates_map<G: GenericNode>() -> HashMap<String, Template<G>> {
get_templates_map! [
index::get_page::<G>(),
about::get_page::<G>(),
post::get_page::<G>()
post::get_page::<G>(),
ip::get_page::<G>()
]
}
19 changes: 17 additions & 2 deletions src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub fn get_page(
let html: String;
let state: Option<String>;

// TODO remove render options system altogether now that we're passing pages around
// Handle each different type of rendering (static paths have already been done though, so we don't need to deal with them)
if render_opts.contains(&RenderOpt::StaticProps) {
// Get the static HTML
Expand All @@ -85,10 +86,24 @@ pub fn get_page(
Ok(state) => Some(state),
Err(_) => None
};
} else {
} else if render_opts.contains(&RenderOpt::Server) {
// Get the template itself (we need it for generation)
let template = templates.get(&template_name);
let template = match template {
Some(template) => template,
None => bail!(ErrorKind::PageNotFound(path.to_string()))
};
// Generate the initial state (this may generate an error, but there's no file that can't exist)
state = Some(template.get_request_state(path.to_string())?);
// Use that to render the static HTML
html = sycamore::render_to_string(
||
template.render_for_template(state.clone())
);
} else {
bail!(ErrorKind::NoRenderOpts(template_name));
}
// TODO support SSR, revalidation, and ISR
// TODO support revalidation and ISR

// Combine everything into one JSON object
let res = PageData {
Expand Down
10 changes: 10 additions & 0 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ impl<G: GenericNode> Template<G> {
bail!(ErrorKind::TemplateFeatureNotEnabled(self.path.clone(), "build_state".to_string()))
}
}
/// Gets the request-time state for a template. This is equivalent to SSR, and will not be performed at build-time. Unlike
/// `.get_build_paths()` though, this will be passed information about the request that triggered the render.
pub fn get_request_state(&self, path: String) -> Result<String> {
if let Some(get_request_state) = &self.get_request_state {
// TODO support error handling for render functions
Ok(get_request_state(path))
} else {
bail!(ErrorKind::TemplateFeatureNotEnabled(self.path.clone(), "request_state".to_string()))
}
}

// Value getters
/// Gets the path of the template. This is the root path under which any generated pages will be served. In the simplest case, there will
Expand Down

0 comments on commit ac79996

Please sign in to comment.