Skip to content

Commit

Permalink
feat: ✨ made rendering functions asynchronous
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Aug 15, 2021
1 parent fa50d4c commit 5b403b2
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 71 deletions.
1 change: 1 addition & 0 deletions examples/showcase/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1" # Possibly don't need?
console_error_panic_hook = "0.1.6"
urlencoding = "2.1"
futures = "0.3"

# This section is needed for WASM Pack (which we use instead of Trunk for flexibility)
[lib]
Expand Down
6 changes: 4 additions & 2 deletions examples/showcase/app/src/bin/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ use perseus::{
};
use perseus_showcase_app::pages;
use sycamore::prelude::SsrNode;
use futures::executor::block_on;

fn main() {
let config_manager = FsConfigManager::new();

build_templates(vec![
let fut = build_templates(vec![
pages::index::get_page::<SsrNode>(),
pages::about::get_page::<SsrNode>(),
pages::post::get_page::<SsrNode>(),
pages::new_post::get_page::<SsrNode>(),
pages::ip::get_page::<SsrNode>(),
pages::time::get_page::<SsrNode>(),
pages::time_root::get_page::<SsrNode>()
], &config_manager).expect("Static generation failed!");
], &config_manager);
block_on(fut).expect("Static generation failed!");

println!("Static generation successfully completed!");
}
2 changes: 1 addition & 1 deletion examples/showcase/app/src/pages/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.template(template_fn())
}

pub fn get_static_props(_path: String) -> Result<String, String> {
pub async fn get_static_props(_path: String) -> Result<String, String> {
Ok(serde_json::to_string(
&IndexPageProps {
greeting: "Hello World!".to_string()
Expand Down
2 changes: 1 addition & 1 deletion examples/showcase/app/src/pages/ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.template(template_fn())
}

pub fn get_request_state(_path: String) -> Result<String, String> {
pub async fn get_request_state(_path: String) -> Result<String, String> {
Ok(serde_json::to_string(
&IpPageProps {
ip: "x.x.x.x".to_string()
Expand Down
4 changes: 2 additions & 2 deletions examples/showcase/app/src/pages/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.template(template_fn())
}

pub fn get_static_props(path: String) -> Result<String, String> {
pub async fn get_static_props(path: String) -> Result<String, String> {
let path_vec: Vec<&str> = path.split('/').collect();
let title_slug = path_vec[path_vec.len() - 1];
// This is just an example
Expand All @@ -45,7 +45,7 @@ pub fn get_static_props(path: String) -> Result<String, String> {
).unwrap())
}
// TODO
pub fn get_static_paths() -> Result<Vec<String>, String> {
pub async fn get_static_paths() -> Result<Vec<String>, String> {
Ok(vec![
"test".to_string()
])
Expand Down
4 changes: 2 additions & 2 deletions examples/showcase/app/src/pages/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.build_paths_fn(Box::new(get_build_paths))
}

pub fn get_build_state(_path: String) -> Result<String, String> {
pub async fn get_build_state(_path: String) -> Result<String, String> {
Ok(serde_json::to_string(
&TimePageProps {
time: format!("{:?}", std::time::SystemTime::now())
}
).unwrap())
}

pub fn get_build_paths() -> Result<Vec<String>, String> {
pub async fn get_build_paths() -> Result<Vec<String>, String> {
Ok(vec![
"test".to_string()
])
Expand Down
4 changes: 2 additions & 2 deletions examples/showcase/app/src/pages/time_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ 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(|| {
.should_revalidate_fn(Box::new(|| async {
Ok(true)
}))
.build_state_fn(Box::new(get_build_state))
}

pub fn get_build_state(_path: String) -> Result<String, String> {
pub async fn get_build_state(_path: String) -> Result<String, String> {
Ok(serde_json::to_string(
&TimePageProps {
time: format!("{:?}", std::time::SystemTime::now())
Expand Down
2 changes: 1 addition & 1 deletion examples/showcase/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async fn page_data(
) -> ActixResult<String> {
let path = req.match_info().query("filename");
// TODO match different types of errors here
let page_data = get_page(path, &render_cfg, &templates, config_manager.get_ref()).map_err(error::ErrorNotFound)?;
let page_data = get_page(path, &render_cfg, &templates, config_manager.get_ref()).await.map_err(error::ErrorNotFound)?;

Ok(
serde_json::to_string(&page_data).unwrap()
Expand Down
76 changes: 44 additions & 32 deletions src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use crate::{
use crate::errors::*;
use std::collections::HashMap;
use sycamore::prelude::SsrNode;
use futures::future::try_join_all;

/// Builds a template, writing static data as appropriate. This should be used as part of a larger build process. This returns both a list
/// of the extracted render options for this template (needed at request time), a list of pages that it explicitly generated, and a boolean
/// as to whether or not it only generated a single page to occupy the template's root path (`true` unless using using build-time path
/// generation).
pub fn build_template(
pub async fn build_template(
template: Template<SsrNode>,
config_manager: &impl ConfigManager
) -> Result<
Expand All @@ -28,7 +29,7 @@ pub fn build_template(
// Handle static path generation
// Because we iterate over the paths, we need a base path if we're not generating custom ones (that'll be overriden if needed)
let paths = match template.uses_build_paths() {
true => template.get_build_paths()?,
true => template.get_build_paths().await?,
false => {
single_page = true;
vec![String::new()]
Expand All @@ -49,7 +50,7 @@ pub fn build_template(
// We'll only write a static state if one is explicitly generated
if template.uses_build_state() {
// We pass in the latter part of the path, without the base specifier (because that would be the same for everything in the template)
let initial_state = template.get_build_state(path.to_string())?;
let initial_state = template.get_build_state(path.to_string()).await?;
// Write that intial state to a static JSON file
config_manager
.write(&format!("./dist/static/{}.json", full_path), &initial_state)?;
Expand Down Expand Up @@ -92,42 +93,53 @@ pub fn build_template(
Ok((paths, single_page))
}

// TODO function to build pages
/// Runs the build process of building many different templates.
pub fn build_templates(templates: Vec<Template<SsrNode>>, config_manager: &impl ConfigManager) -> Result<()> {
// The render configuration stores a list of pages to the root paths of their templates
async fn build_template_and_get_cfg(template: Template<SsrNode>, config_manager: &impl ConfigManager) -> Result<HashMap<String, String>> {
let mut render_cfg = HashMap::new();
// Create each of the templates
for template in templates {
let template_root_path = template.get_path();
let is_incremental = template.uses_incremental();

let (pages, single_page) = build_template(template, config_manager)?;
// If the tempalte represents a single page itself, we don't need any concatenation
if single_page {
let template_root_path = template.get_path();
let is_incremental = template.uses_incremental();

let (pages, single_page) = build_template(template, config_manager).await?;
// If the template represents a single page itself, we don't need any concatenation
if single_page {
render_cfg.insert(
template_root_path.clone(),
template_root_path.clone()
);
} else {
// Add each page that the template explicitly generated (ignoring ISR for now)
for page in pages {
render_cfg.insert(
format!("{}/{}", &template_root_path, &page),
template_root_path.clone()
);
}
// Now if the page uses ISR, add an explicit `/*` in there after the template root path
// Incremental rendering requires build-time path generation
if is_incremental {
render_cfg.insert(
template_root_path.clone(),
format!("{}/*", &template_root_path),
template_root_path.clone()
);
} else {
// Add each page that the template explicitly generated (ignoring ISR for now)
for page in pages {
render_cfg.insert(
format!("{}/{}", &template_root_path, &page),
template_root_path.clone()
);
}
// Now if the page uses ISR, add an explicit `/*` in there after the template root path
// Incremental rendering requires build-time path generation
if is_incremental {
render_cfg.insert(
format!("{}/*", &template_root_path),
template_root_path.clone()
);
}
}
}

Ok(render_cfg)
}

/// Runs the build process of building many different templates.
pub async fn build_templates(templates: Vec<Template<SsrNode>>, config_manager: &impl ConfigManager) -> Result<()> {
// The render configuration stores a list of pages to the root paths of their templates
let mut render_cfg: HashMap<String, String> = HashMap::new();
// Create each of the templates
let mut futs = Vec::new();
for template in templates {
futs.push(build_template_and_get_cfg(template, config_manager));
}
let template_cfgs = try_join_all(futs).await?;
for template_cfg in template_cfgs {
render_cfg.extend(template_cfg.into_iter())
}

config_manager
.write("./dist/render_conf.json", &serde_json::to_string(&render_cfg)?)?;

Expand Down
28 changes: 14 additions & 14 deletions src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ fn render_build_state(path_encoded: &str, config_manager: &impl ConfigManager) -
Ok((html, state))
}
/// Renders a template that generated its state at request-time. Note that revalidation and ISR have no impact on SSR-rendered pages.
fn render_request_state(template: &Template<SsrNode>, path: &str) -> Result<(String, Option<String>)> {
async fn render_request_state(template: &Template<SsrNode>, path: &str) -> Result<(String, Option<String>)> {
// Generate the initial state (this may generate an error, but there's no file that can't exist)
let state = Some(template.get_request_state(path.to_string())?);
let state = Some(template.get_request_state(path.to_string()).await?);
// Use that to render the static HTML
let html = sycamore::render_to_string(
||
Expand All @@ -63,7 +63,7 @@ fn get_incremental_cached(path_encoded: &str, config_manager: &impl ConfigManage
}
}
/// Checks if a template should revalidate by time.
fn should_revalidate(template: &Template<SsrNode>, path_encoded: &str, config_manager: &impl ConfigManager) -> Result<bool> {
async fn should_revalidate(template: &Template<SsrNode>, path_encoded: &str, config_manager: &impl ConfigManager) -> Result<bool> {
let mut should_revalidate = false;
// If it revalidates after a certain period of time, we needd to check that BEFORE the custom logic
if template.revalidates_with_time() {
Expand All @@ -83,12 +83,12 @@ fn should_revalidate(template: &Template<SsrNode>, path_encoded: &str, config_ma

// Now run the user's custom revalidation logic
if template.revalidates_with_logic() {
should_revalidate = template.should_revalidate()?;
should_revalidate = template.should_revalidate().await?;
}
Ok(should_revalidate)
}
/// Revalidates a template
fn revalidate(
async fn revalidate(
template: &Template<SsrNode>,
path: &str, path_encoded: &str,
config_manager: &impl ConfigManager
Expand All @@ -97,7 +97,7 @@ fn revalidate(
let state = Some(
template.get_build_state(
format!("{}/{}", template.get_path(), path)
)?
).await?
);
let html = sycamore::render_to_string(
||
Expand All @@ -123,7 +123,8 @@ fn revalidate(
/// Gets the HTML/JSON data for the given page path. This will call SSG/SSR/etc., whatever is needed for that page. Note that HTML generated
/// at request-time will **always** replace anything generated at build-time, incrementally, revalidated, etc.
// TODO let this function take a request struct of some form
pub fn get_page(
// TODO possible further optimizations on this for futures?
pub async fn get_page(
path: &str,
render_cfg: &HashMap<String, String>,
templates: &TemplateMap<SsrNode>,
Expand Down Expand Up @@ -183,8 +184,8 @@ pub fn get_page(
// It's cached
Some(html_val) => {
// Check if we need to revalidate
if should_revalidate(template, &path_encoded, config_manager)? {
let (html_val, state) = revalidate(template, path, &path_encoded, config_manager)?;
if should_revalidate(template, &path_encoded, config_manager).await? {
let (html_val, state) = revalidate(template, path, &path_encoded, config_manager).await?;
// Build-time generated HTML is the lowest priority, so we'll only set it if nothing else already has
if html.is_empty() {
html = html_val
Expand All @@ -209,7 +210,7 @@ pub fn get_page(
let state = Some(
template.get_build_state(
format!("{}/{}", template.get_path(), path)
)?
).await?
);
let html_val = sycamore::render_to_string(
||
Expand Down Expand Up @@ -241,8 +242,8 @@ pub fn get_page(
}
} else {
// Handle if we need to revalidate
if should_revalidate(template, &path_encoded, config_manager)? {
let (html_val, state) = revalidate(template, path, &path_encoded, config_manager)?;
if should_revalidate(template, &path_encoded, config_manager).await? {
let (html_val, state) = revalidate(template, path, &path_encoded, config_manager).await?;
// Build-time generated HTML is the lowest priority, so we'll only set it if nothing else already has
if html.is_empty() {
html = html_val
Expand All @@ -260,12 +261,11 @@ pub fn get_page(
}
// Handle request state
if template.uses_request_state() {
let (html_val, state) = render_request_state(template, path)?;
let (html_val, state) = render_request_state(template, path).await?;
// Request-time HTML always overrides anything generated at build-time or incrementally (this has more information)
html = html_val;
states.request_state = state;
}
// TODO support revalidation

// Amalgamate the states
// If the user has defined custom logic for this, we'll defer to that
Expand Down
Loading

0 comments on commit 5b403b2

Please sign in to comment.