Rust library to generate static websites
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: new article on compile time

+188 -13
+7
Cargo.lock
··· 2572 2572 ] 2573 2573 2574 2574 [[package]] 2575 + name = "maudit-example-runtime-to-the-max" 2576 + version = "0.1.0" 2577 + dependencies = [ 2578 + "maudit", 2579 + ] 2580 + 2581 + [[package]] 2575 2582 name = "maudit-macros" 2576 2583 version = "0.5.0" 2577 2584 dependencies = [
+26 -6
crates/maudit-cli/src/dev.rs
··· 18 18 19 19 fn should_rebuild_for_event(event: &DebouncedEvent) -> bool { 20 20 event.paths.iter().any(|path| { 21 + println!("Checking path: {:?}, event: {:?}", path, event.kind); 21 22 should_watch_path(path) 22 23 && match event.kind { 23 24 // Only rebuild on actual content modifications, not metadata changes ··· 49 50 ("MAUDIT_DEV", "true"), 50 51 ("MAUDIT_QUIET", "true"), 51 52 ("CARGO_TERM_COLOR", "always"), 52 - ("RUSTFLAGS", "-Awarnings"), 53 53 ]) 54 54 .stderr(std::process::Stdio::piped()) 55 55 .spawn() ··· 110 110 let (tx, mut rx) = tokio::sync::mpsc::channel::<DebounceEventResult>(100); 111 111 112 112 let mut debouncer = new_debouncer( 113 - std::time::Duration::from_millis(100), 113 + std::time::Duration::from_millis(20), 114 114 None, 115 115 move |result: DebounceEventResult| { 116 116 tx.blocking_send(result).unwrap_or(()); ··· 119 119 120 120 debouncer 121 121 .watcher() 122 - .watch(Path::new(cwd), RecursiveMode::Recursive)?; 122 + .watch(&Path::new(cwd).join("src"), RecursiveMode::Recursive)?; 123 + 124 + debouncer 125 + .watcher() 126 + .watch(&Path::new(cwd).join("content"), RecursiveMode::Recursive)?; 127 + 128 + debouncer 129 + .watcher() 130 + .unwatch(&Path::new(cwd).join("target")) 131 + .ok(); 132 + debouncer 133 + .watcher() 134 + .unwatch(&Path::new(cwd).join(".git")) 135 + .ok(); 136 + debouncer 137 + .watcher() 138 + .unwatch(&Path::new(cwd).join("node_modules")) 139 + .ok(); 140 + debouncer 141 + .watcher() 142 + .unwatch(&Path::new(cwd).join("dist")) 143 + .ok(); 123 144 124 145 // Handle file events 125 146 tokio::spawn(async move { ··· 137 158 .collect(); 138 159 139 160 if !triggering_events.is_empty() { 140 - debug!("File events: {} valid changes", triggering_events.len()); 161 + info!("File events: {} valid changes", triggering_events.len()); 141 162 for event in &triggering_events { 142 163 for path in &event.paths { 143 - debug!(" {:?}: {}", event.kind, path.display()); 164 + info!(" {:?}: {}", event.kind, path.display()); 144 165 } 145 166 } 146 167 ··· 174 195 ("MAUDIT_DEV", "true"), 175 196 ("MAUDIT_QUIET", "true"), 176 197 ("CARGO_TERM_COLOR", "always"), 177 - ("RUSTFLAGS", "-Awarnings"), 178 198 ]) 179 199 .stdout(std::process::Stdio::inherit()) 180 200 .stderr(std::process::Stdio::piped())
+4
examples/runtime-to-the-max/.gitignore
··· 1 + target 2 + dist 3 + node_modules 4 + .DS_Store
+11
examples/runtime-to-the-max/Cargo.toml
··· 1 + [package] 2 + name = "maudit-example-runtime-to-the-max" 3 + version = "0.1.0" 4 + edition = "2024" 5 + publish = false 6 + 7 + [package.metadata.maudit] 8 + intended_version = "0.6.2" 9 + 10 + [dependencies] 11 + maudit = { workspace = true }
+3
examples/runtime-to-the-max/pages.txt
··· 1 + hello: Hello! 2 + another_page: Another Page 3 + imnested/index: I'm nested!
+32
examples/runtime-to-the-max/src/main.rs
··· 1 + use maudit::{BuildOptions, BuildOutput, content_sources, coronate, route::prelude::*}; 2 + 3 + #[route(format!("/dynamic/{}/", self.dynamic_page.0))] 4 + struct Dynamic { 5 + dynamic_page: (String, String), 6 + } 7 + 8 + impl Route for Dynamic { 9 + fn render(&self, _: &mut PageContext) -> impl Into<RenderResult> { 10 + self.dynamic_page.1.clone() 11 + } 12 + } 13 + 14 + fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 15 + let routes: Vec<Box<dyn FullRoute>> = std::fs::read_to_string("pages.txt")? 16 + .lines() 17 + .filter_map(|line| { 18 + let mut parts = line.splitn(2, ": "); 19 + match (parts.next(), parts.next()) { 20 + (Some(name), Some(content)) => Some((name.to_string(), content.to_string())), 21 + _ => None, 22 + } 23 + }) 24 + .map(|dynamic_page| Box::new(Dynamic { dynamic_page }) as Box<dyn FullRoute>) 25 + .collect(); 26 + 27 + coronate( 28 + &routes.iter().map(|r| r.as_ref()).collect::<Vec<_>>(), 29 + content_sources![], 30 + BuildOptions::default(), 31 + ) 32 + }
+12
examples/runtime-to-the-max/src/routes/dynamic.rs
··· 1 + use maudit::route::prelude::*; 2 + 3 + #[route(format!("/dynamic/{}/", self.dynamic_page.0))] 4 + pub struct Dynamic { 5 + pub dynamic_page: (String, String), 6 + } 7 + 8 + impl Route for Dynamic { 9 + fn render(&self, _: &mut PageContext) -> impl Into<RenderResult> { 10 + self.dynamic_page.1.clone() 11 + } 12 + }
+13 -1
website/assets/prin.css
··· 136 136 line-height: 1.75; 137 137 } 138 138 139 + .prose blockquote { 140 + border-left: 4px solid var(--color-borders); 141 + margin-left: -1.5rem; 142 + margin-right: -1.5rem; 143 + padding-left: 1rem; 144 + color: var(--color-darker-black); 145 + font-style: italic; 146 + margin-top: 1em; 147 + margin-bottom: 1em; 148 + } 149 + 139 150 .prose code, 140 151 .intro-code code { 141 152 font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, ··· 155 166 content: "`"; 156 167 } 157 168 158 - .prose pre { 169 + .prose pre, 170 + .intro-code pre { 159 171 margin-left: -1.5rem; 160 172 margin-right: -1.5rem; 161 173 }
+74
website/content/news/maudit-compile-time.md
··· 1 + --- 2 + title: "Maudit is faster than most other static website generators, but" 3 + description: "Your website changes less often than its content" 4 + author: The Maudit Team 5 + date: 2025-10-02 6 + --- 7 + 8 + Objectively speaking, unless it does something weird, a binary using Maudit will generate a website pretty fast. This is expected: Maudit is pretty fast, Rust is pretty fast, native binaries are pretty fast, it checks out. 9 + 10 + ## _However,_ 11 + 12 + If a Maudit project is a Rust project, and [Rust projects are slow to compile](https://www.reddit.com/r/rust/comments/xna9mb/why_are_rust_programs_slow_to_compile/) and you need to compile to build your website, doesn't that make Maudit slow by default? 13 + 14 + Yes, **but**, there are a few things to consider: 15 + 16 + - The slow completely cold compile and download are rare (much like you don't run `npm install` before every build) 17 + - Incremental warm builds are not that slow (< 3s~), and do not necessarily get slower as your website gets larger. The same blog with 5000 and 5 articles compile in the same amount of time (unless they're 5000 different pages and layouts, in which case, well) 18 + 19 + And most importantly: **Not every change require recompilation.** Updating your Markdown content, updating frontend JavaScript or CSS, updating some images assets all don't require recompilation and are most definitely more common changes than changing your project's logic. 20 + 21 + ## Workarounds 22 + 23 + You can also push this further, if your layouts do change often and you don't want to recompile your project on every change, use a runtime templating language like [minijinja](https://github.com/mitsuhiko/minijinja) or [Tera](https://keats.github.io/tera/docs/). 24 + 25 + You can push it even further! [Routes paths are not static, they can be fully dynamic](https://maudit.org/docs/routing/#:~:text=The%20path%20can%20be%20any%20Rust%20expression) as such, you could load your routes fully at runtime. 26 + 27 + > This example is available in the [examples/runtime-to-the-max](https://github.com/bruits/maudit/tree/main/examples/runtime-to-the-max) directory of the Maudit repository. 28 + 29 + ```rust 30 + use maudit::{BuildOptions, BuildOutput, content_sources, coronate, route::prelude::*}; 31 + 32 + #[route(format!("/dynamic/{}/", self.dynamic_page.0))] 33 + struct Dynamic { 34 + dynamic_page: (String, String), 35 + } 36 + 37 + impl Route for Dynamic { 38 + fn render(&self, _: &mut PageContext) -> impl Into<RenderResult> { 39 + self.dynamic_page.1.clone() 40 + } 41 + } 42 + 43 + fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 44 + let routes: Vec<Box<dyn FullRoute>> = std::fs::read_to_string("pages.txt")? 45 + .lines() 46 + .filter_map(|line| { 47 + let mut parts = line.splitn(2, ": "); 48 + match (parts.next(), parts.next()) { 49 + (Some(name), Some(content)) => Some((name.to_string(), content.to_string())), 50 + _ => None, 51 + } 52 + }) 53 + .map(|dynamic_page| Box::new(Dynamic { dynamic_page }) as Box<dyn FullRoute>) 54 + .collect(); 55 + 56 + coronate( 57 + &routes.iter().map(|r| r.as_ref()).collect::<Vec<_>>(), 58 + content_sources![], 59 + BuildOptions::default(), 60 + ) 61 + } 62 + ``` 63 + 64 + where `pages.txt` looks like this: 65 + 66 + ```txt 67 + hello: Hello! 68 + another_page: Another Page 69 + imnested/index: I'm nested! 70 + ``` 71 + 72 + This is my CMS at home, don't judge me. More seriously, you could imagine loading pages from an actual CMS, a database, a `.json` file etc. And thus, combined with runtime templating, be able to add an infinite amount of pages without ever recompiling. 73 + 74 + Maudit is a library, not a framework. Make it work for you!
+6 -6
website/src/routes/news.rs
··· 1 - use maud::html; 2 1 use maud::PreEscaped; 2 + use maud::html; 3 3 use maudit::route::prelude::*; 4 4 use std::collections::BTreeMap; 5 5 6 6 use crate::content::NewsContent; 7 - use crate::layout::layout; 8 7 use crate::layout::SeoMeta; 8 + use crate::layout::layout; 9 9 10 10 #[route("/news/")] 11 11 pub struct NewsIndex; ··· 52 52 } 53 53 } 54 54 @if let Some(description) = &article.data(ctx).description { 55 - p.text-lg.text-gray-600 { (description) } 55 + p.text-lg.text-gray-600.italic { (description) } 56 56 } 57 57 } 58 58 } ··· 122 122 123 123 layout( 124 124 html! { 125 - div.container.mx-auto."py-10"."pb-24"."max-w-[80ch]"."px-8"."sm:px-0" { 125 + div.container.mx-auto."py-10"."pb-24"."max-w-[80ch]"."px-6"."sm:px-0" { 126 126 section.mb-4.border-b."border-[#e9e9e7]".pb-2 { 127 127 @if let Some(date) = &date { 128 128 p.text-sm.font-bold { (date) } 129 129 } 130 - h1."text-6xl"."sm:text-7xl".font-bold { (title) } 130 + h1."text-5xl"."sm:text-6xl".font-bold.mb-3 { (title) } 131 131 @if let Some(description) = &description { 132 - p.text-xl."sm:text-2xl" { (description) } 132 + p.text-xl."sm:text-2xl".italic { (description) } 133 133 } 134 134 } 135 135