From 45243e10354c1e1e70a6cab3a69492b657ea483d Mon Sep 17 00:00:00 2001 From: hulxv Date: Sun, 22 Sep 2024 02:44:18 +0300 Subject: [PATCH 1/2] docs(build): improve docs of `metassr_build::manifest` --- crates/metassr-build/src/server/manifest.rs | 127 ++++++++++++++++++-- 1 file changed, 117 insertions(+), 10 deletions(-) diff --git a/crates/metassr-build/src/server/manifest.rs b/crates/metassr-build/src/server/manifest.rs index c8effd0..7b71490 100644 --- a/crates/metassr-build/src/server/manifest.rs +++ b/crates/metassr-build/src/server/manifest.rs @@ -1,5 +1,10 @@ -use anyhow::{anyhow, Result}; +//! Manifest generation and management for the `metassr` framework. +//! +//! This module defines the structures and functions for generating, serializing, and managing +//! the `manifest.json` file, which maps routes to their associated page entries and renderers +//! in the SSR (server-side rendering) process. +use anyhow::{anyhow, Result}; use metassr_fs_analyzer::dist_dir::{DistDirContainer, PageEntry}; use metassr_utils::cache_dir::CacheDir; @@ -15,14 +20,30 @@ use std::{ use super::targets::Targets; +/// Represents a single entry in the manifest for a specific route. +/// +/// This struct contains details about the page's assets (scripts and styles) +/// and the path to the server-side renderer responsible for rendering the page. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ManifestEntry { +pub struct ManifestRouteEntry { + /// Unique identifier for the entry (usually a hash or numeric ID). pub id: i64, + /// The page entry containing scripts, styles, and path for the route's static assets. pub page_entry: PageEntry, + /// Path to the server-side renderer script for this route. pub renderer: PathBuf, } -impl ManifestEntry { +impl ManifestRouteEntry { + /// Creates a new `ManifestRouteEntry`. + /// + /// # Parameters + /// - `id`: Unique identifier for the entry. + /// - `page_entry`: Information about the page's static assets. + /// - `renderer`: Path to the server-side renderer. + /// + /// # Returns + /// A new `ManifestRouteEntry` instance. pub fn new(id: i64, page_entry: PageEntry, renderer: PathBuf) -> Self { Self { id, @@ -32,13 +53,24 @@ impl ManifestEntry { } } +/// Holds global information for the manifest, such as the HTML `` file and the cache directory. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GlobalEntry { + /// Path to the global HTML `` file. pub head: PathBuf, + /// Path to the cache directory where SSR renderers are stored. pub cache: PathBuf, } impl GlobalEntry { + /// Creates a new `GlobalEntry`. + /// + /// # Parameters + /// - `head`: Path to the global `` file. + /// - `cache`: Path to the cache directory. + /// + /// # Returns + /// A new `GlobalEntry` instance. pub fn new(head: &H, cache: &C) -> Result where H: AsRef + ?Sized, @@ -51,13 +83,25 @@ impl GlobalEntry { } } +/// The manifest that maps routes to their respective page entries and SSR renderers. +/// +/// It also contains a `GlobalEntry` which stores information shared across all routes. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Manifest { + /// The global entry shared across all routes. pub global: GlobalEntry, - routes: HashMap, + /// A map of route paths to `ManifestRouteEntry` objects. + routes: HashMap, } impl Manifest { + /// Creates a new empty `Manifest` with a given global entry. + /// + /// **Parameters** + /// - `global`: The global entry (head and cache paths). + /// + /// **Returns** + /// A new `Manifest` instance with no routes. pub fn new(global: GlobalEntry) -> Self { Self { global, @@ -65,22 +109,43 @@ impl Manifest { } } + /// Inserts a new route into the manifest. + /// + /// **Parameters** + /// - `route`: The route string (e.g., "blog", "home", "#root"). + /// - `id`: The unique identifier for the page entry. + /// - `page_entry`: A reference to the `PageEntry` containing static asset information. + /// - `renderer`: The path to the server-side renderer for this route. + /// + /// **Returns** + /// Optionally returns the previous `ManifestRouteEntry` if the route already existed. pub fn insert( &mut self, route: &str, id: i64, page_entry: &PageEntry, renderer: PathBuf, - ) -> Option { - let entry = ManifestEntry::new(id, page_entry.clone(), renderer); + ) -> Option { + let entry = ManifestRouteEntry::new(id, page_entry.clone(), renderer); self.routes.insert(route.to_string(), entry) } + /// Serializes the manifest into a pretty-printed JSON string. + /// + /// **Returns** + /// A `Result` containing the JSON string or an error if serialization fails. pub fn to_json(&self) -> Result { let json = to_string_pretty(&self)?; Ok(json) } + /// Writes the manifest to a file as `manifest.json`. + /// + /// **Parameters** + /// - `path`: The path where the manifest file should be written. + /// + /// **Returns** + /// A `Result` containing the path to the manifest file or an error if writing fails. pub fn write + ?Sized>(&self, path: &S) -> Result { let manifest_filename = "manifest.json"; let path = PathBuf::from(path); @@ -89,12 +154,27 @@ impl Manifest { file.write_all(self.to_json()?.as_bytes())?; Ok(path) } - pub fn get(&self, route: &str) -> Option<&ManifestEntry> { + + /// Retrieves a `ManifestRouteEntry` for a given route. + /// + /// **Parameters** + /// - `route`: The route string. + /// + /// **Returns** + /// An `Option` containing a reference to the `ManifestRouteEntry` if it exists. + pub fn get(&self, route: &str) -> Option<&ManifestRouteEntry> { self.routes.get(route) } } impl + ?Sized> From<&S> for Manifest { + /// Loads a `Manifest` from a JSON file located at the specified path. + /// + /// **Parameters** + /// - `path`: The directory where `manifest.json` is located. + /// + /// **Returns** + /// A `Manifest` instance deserialized from the file. fn from(path: &S) -> Self { let manifest_filename = "manifest.json"; let path = PathBuf::from(path).join(manifest_filename); @@ -104,6 +184,10 @@ impl + ?Sized> From<&S> for Manifest { } } +/// Generates a `Manifest` by analyzing the distribution and cache directories. +/// +/// This struct is responsible for matching routes with their corresponding page assets +/// and SSR renderers. pub struct ManifestGenerator { targets: Targets, dist: DistDirContainer, @@ -111,6 +195,15 @@ pub struct ManifestGenerator { } impl ManifestGenerator { + /// Creates a new `ManifestGenerator`. + /// + /// **Parameters** + /// - `targets`: A collection of route targets and their identifiers. + /// - `cache`: The cache directory containing SSR renderers. + /// - `dist`: The distribution directory containing page assets. + /// + /// **Returns** + /// A new `ManifestGenerator` instance. pub fn new(targets: Targets, cache: CacheDir, dist: DistDirContainer) -> Self { Self { targets, @@ -118,12 +211,27 @@ impl ManifestGenerator { cache, } } + + /// Generates the manifest by iterating over the route targets, matching them with + /// their assets in the distribution directory, and assigning SSR renderers from the cache. + /// + /// **Parameters** + /// - `head`: The path to the global HTML `` file. + /// + /// **Returns** + /// A `Result` containing the generated `Manifest` or an error if generation fails. pub fn generate + ?Sized>(&self, head: &H) -> Result { - let cache_path = self.cache.path(); + let cache_path = self.cache.path(); let global = GlobalEntry::new(head, cache_path)?; let mut manifest = Manifest::new(global); for (path, &id) in self.targets.iter() { + // Derive route name from the path. + // Example: + // - Input: "dist/cache/pages/blog/$article/index.server.js" + // - Output: "blog/$article" + // + // If the parent of the stripped path is an empty string, the route will be "#root" let route = match path .strip_prefix(cache_path.join("pages"))? .parent() @@ -136,11 +244,10 @@ impl ManifestGenerator { let page_entry = match self.dist.pages.get(route) { Some(e) => e, None => { - return Err(anyhow!("manifest: No Entries founded for: {:#?}", route)); + return Err(anyhow!("manifest: No entries found for: {:#?}", route)); } }; manifest.insert(route, id, page_entry, path.canonicalize()?); - // dbg!(&route, &page_entry); } Ok(manifest) } From ad83df389689455f8e1ebc0abaa1205a0274b635 Mon Sep 17 00:00:00 2001 From: hulxv Date: Sun, 22 Sep 2024 02:44:56 +0300 Subject: [PATCH 2/2] test(builder):: add test-cases to `metassr-build::manifest` --- crates/metassr-build/src/server/manifest.rs | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/crates/metassr-build/src/server/manifest.rs b/crates/metassr-build/src/server/manifest.rs index 7b71490..1ef36c7 100644 --- a/crates/metassr-build/src/server/manifest.rs +++ b/crates/metassr-build/src/server/manifest.rs @@ -252,3 +252,45 @@ impl ManifestGenerator { Ok(manifest) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::env::temp_dir; + use std::fs; + use std::path::PathBuf; + + #[test] + fn test_manifest_global_entry_creation() { + let global_entry = GlobalEntry::new("head.html", ".cache").unwrap(); + assert_eq!( + global_entry.head, + PathBuf::from("head.html").canonicalize().unwrap() + ); + assert_eq!(global_entry.cache, PathBuf::from(".cache")); + } + + #[test] + fn test_manifest_to_json() { + let manifest = Manifest::new(GlobalEntry::new("head.html", ".cache").unwrap()); + let json = manifest.to_json().unwrap(); + assert!(json.contains("\"head\"")); + assert!(json.contains("\"cache\"")); + } + + #[test] + fn test_manifest_write_and_read() { + // Use the system temp directory + let temp_path = temp_dir().join("test_manifest_dir"); + fs::create_dir_all(&temp_path).unwrap(); + + let manifest = Manifest::new(GlobalEntry::new("head.html", ".cache").unwrap()); + manifest.write(&temp_path).unwrap(); + + let read_manifest: Manifest = Manifest::from(&temp_path); + assert_eq!(read_manifest.global.cache, PathBuf::from(".cache")); + + // Cleanup the temp directory + fs::remove_dir_all(&temp_path).unwrap(); + } +}