Skip to content

Commit

Permalink
feat: ✨ added page path matching logic
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jul 29, 2021
1 parent 7c9e433 commit 734f9df
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 22 deletions.
2 changes: 1 addition & 1 deletion examples/showcase/bonnie.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build.cmd = [
"wasm-pack build --target web",
"rollup ./main.js --format iife --file ./pkg/bundle.js"
]
build.subcommands.--watch = "find . -not -path \"./target/*\" -not -path \"./.git/*\" | entr -s \"bonnie build\""
build.subcommands.--watch = "find ../../ -not -path \"../../target/*\" -not -path \"../../.git/*\" | entr -s \"bonnie build\""
serve = [
"cd server",
"cargo watch -w ../ -x \"run\""
Expand Down
85 changes: 68 additions & 17 deletions src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@ use crate::errors::*;
use std::any::Any;
use sycamore::prelude::SsrNode;

/// Builds a template, writing static data as appropriate. This should be used as part of a larger build process.
pub fn build_template<Props: Serialize + DeserializeOwned + Any>(template: Template<Props, SsrNode>, config_manager: &impl ConfigManager) -> Result<Vec<RenderOpt>> {
/// 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<Props: Serialize + DeserializeOwned + Any>(
template: Template<Props, SsrNode>,
config_manager: &impl ConfigManager
) -> Result<
(
Vec<RenderOpt>,
Vec<String>,
bool
)
> {
let mut render_opts: Vec<RenderOpt> = Vec::new();
let mut single_page = false;
let template_path = template.get_path();

// Handle the boolean properties
Expand All @@ -30,8 +43,18 @@ pub fn build_template<Props: Serialize + DeserializeOwned + Any>(template: Templ
render_opts.push(RenderOpt::StaticPaths);
template.get_build_paths()?
},
false => vec![template_path.clone()]
false => {
single_page = true;
vec![String::new()]
}
};
// Add the rest of the render options before we loop over defined pages
if template.uses_build_state() {
render_opts.push(RenderOpt::StaticProps);
}
if template.uses_request_state() {
render_opts.push(RenderOpt::Server);
}

// Iterate through the paths to generate initial states if needed
for path in paths.iter() {
Expand All @@ -45,8 +68,7 @@ pub fn build_template<Props: Serialize + DeserializeOwned + Any>(template: Templ

// Handle static initial state generation
// We'll only write a static state if one is explicitly generated
if template.uses_build_state() {
render_opts.push(RenderOpt::StaticProps);
if render_opts.contains(&RenderOpt::StaticProps) {
// 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_str = serde_json::to_string(&initial_state).unwrap();
Expand All @@ -65,14 +87,11 @@ pub fn build_template<Props: Serialize + DeserializeOwned + Any>(template: Templ
.unwrap();
}

// Handle server-side rendering
// By definition, everything here is done at request-time, so there's not really much to do
// Note also that if a template only uses SSR, it won't get prerendered at build time whatsoever
if template.uses_request_state() {
render_opts.push(RenderOpt::Server);
}
// Note that SSR has already been handled by checking for `.uses_request_state()` above, we don't need to do any rendering here
// If a template only uses SSR, it won't get prerendered at build time whatsoever

// If the template is very basic, prerender without any state
// It's safe to add a property to the render options here because `.is_basic()` will only return true if path generation is not being used (or anything else)
if template.is_basic() {
render_opts.push(RenderOpt::StaticProps);
let prerendered = sycamore::render_to_string(
Expand All @@ -86,26 +105,58 @@ pub fn build_template<Props: Serialize + DeserializeOwned + Any>(template: Templ
}
}

Ok(render_opts)
Ok((render_opts, paths, single_page))
}

/// Runs the build process of building many different templates. This is done with a macro because typing for a function means we have to do
/// things on the heap.
/// (Any better solutions are welcome in PRs!)
// TODO set up error handling here
#[macro_export]
macro_rules! build_templates {
(
[$($template:expr),+],
$config_manager:expr
) => {
let mut render_conf: $crate::render_cfg::RenderCfg = ::std::collections::HashMap::new();
let mut templates_conf: $crate::render_cfg::TemplatesCfg = ::std::collections::HashMap::new();
let mut pages_conf: $crate::render_cfg::PagesCfg = ::std::collections::HashMap::new();
// Create each of the templates
$(
render_conf.insert(
$template.get_path(),
$crate::build::build_template($template, $config_manager)
.unwrap()
let (render_opts, pages, single_page) = $crate::build::build_template($template, $config_manager)
.unwrap();
let template_root_path = $template.get_path();
templates_conf.insert(
template_root_path.clone(),
render_opts
);
if single_page {
pages_conf.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 {
pages_conf.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 $template.uses_incremental() {
pages_conf.insert(
format!("{}/*", &template_root_path),
template_root_path.clone()
);
}
}
)+

let render_conf = $crate::render_cfg::RenderCfg {
templates: templates_conf,
pages: pages_conf
};
$config_manager
.write("./dist/render_conf.json", &serde_json::to_string(&render_conf).unwrap())
.unwrap();
Expand Down
18 changes: 17 additions & 1 deletion src/render_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,20 @@ pub enum RenderOpt {
Server,
}

pub type RenderCfg = HashMap<String, Vec<RenderOpt>>;
pub type TemplatesCfg = HashMap<String, Vec<RenderOpt>>;
pub type PagesCfg = HashMap<String, String>;

/// The configuration that details how to render each page. Every known page path has an entry here except for those with ISR.
/// Any page that uses ISR (by defining the `TODO` property on its `Page` definition) has an entry for the template followed by `/*` to
/// avoid storing potentially billions of pages in this file. Any explicitly defined page though will be present in here for maximum speed.
/// Note that ISR is not compatible with defining other pages specifically under the root of the ISR template with different templates (e.g.
/// defining `/posts/*` and then defining a new template `/posts/index`, you'd have to use the same template there if you use ISR).
#[derive(Serialize, Deserialize)]
pub struct RenderCfg {
/// All the registered templates. Each of these corresponds to a `Page` definition. They all have a series of render options that are
/// automatically generated with `build_page` (invoked by `build_pages!`).
pub templates: TemplatesCfg,
/// The actual pages themselves, each mapping to the name of their template that defines their render options. Again, ISR rendered pages
/// are stored with a wildcard here.
pub pages: PagesCfg,
}
8 changes: 5 additions & 3 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ pub struct Template<Props: Serialize + DeserializeOwned, G: GenericNode>
/// A function that gets the paths to render for at built-time. This is equivalent to `get_static_paths` in NextJS. If
/// `incremental_path_rendering` is `true`, more paths can be rendered at request time on top of these.
get_build_paths: Option<GetBuildPathsFn>,
/// Defiens whether or not any new paths that match this template will be prerendered and cached in production. This allows you to
/// Defines whether or not any new paths that match this template will be prerendered and cached in production. This allows you to
/// have potentially billions of templates and retain a super-fast build process. The first user will have an ever-so-slightly slower
/// experience, and everyone else gets the beneftis afterwards.
/// experience, and everyone else gets the beneftis afterwards. This requires `get_build_paths`. Note that the template root will NOT
/// be rendered on demand, and must be explicitly defined if it's wanted. It can uuse a different template.
incremental_path_rendering: bool,
/// A function that gets the initial state to use to prerender the template at build time. This will be passed the path of the template, and
/// will be run for any sub-paths. This is equivalent to `get_static_props` in NextJS.
Expand Down Expand Up @@ -86,7 +87,8 @@ impl<Props: Serialize + DeserializeOwned, G: GenericNode> Template<Props, G> {
}

// Value getters
/// Gets the path of the template.
/// 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
/// only be one page rendered, and it will occupy that root position.
pub fn get_path(&self) -> String {
self.path.clone()
}
Expand Down

0 comments on commit 734f9df

Please sign in to comment.