Rust library to generate static websites
5
fork

Configure Feed

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

feat: first version of CSS

+118 -11
+63 -3
crates/framework/src/assets.rs
··· 1 1 use dyn_eq::DynEq; 2 + use log::info; 2 3 use rustc_hash::FxHashSet; 3 4 use std::hash::Hash; 5 + use std::process::Command; 6 + use std::time::SystemTime; 4 7 use std::{fs, path::PathBuf}; 5 8 6 9 #[derive(Default)] 7 10 pub struct PageAssets { 8 11 pub(crate) assets: FxHashSet<Box<dyn Asset>>, 9 12 pub(crate) scripts: FxHashSet<Script>, 13 + pub(crate) styles: FxHashSet<Style>, 10 14 } 11 15 12 16 impl PageAssets { ··· 25 29 26 30 script 27 31 } 32 + 33 + pub fn add_style(&mut self, style_path: PathBuf, tailwind: bool) -> Style { 34 + let style = Style { 35 + path: style_path, 36 + tailwind, 37 + }; 38 + 39 + self.styles.insert(style.clone()); 40 + 41 + style 42 + } 28 43 } 29 44 30 45 pub trait Asset: DynEq { 31 46 fn url(&self) -> Option<String>; 32 47 fn path(&self) -> &PathBuf; 33 48 34 - fn process(&self); 49 + fn process(&self) -> Option<String> { 50 + None 51 + } 35 52 fn hash(&self) -> [u8; 8]; 36 53 } 37 54 ··· 60 77 &self.path 61 78 } 62 79 63 - fn process(&self) { 80 + fn process(&self) -> Option<String> { 64 81 fs::copy( 65 82 &self.path, 66 83 "dist/_assets/".to_string() + self.path.file_name().unwrap().to_str().unwrap(), 67 84 ) 68 85 .unwrap(); 86 + 87 + None 69 88 } 70 89 71 90 fn hash(&self) -> [u8; 8] { ··· 90 109 &self.path 91 110 } 92 111 93 - fn process(&self) {} 112 + fn hash(&self) -> [u8; 8] { 113 + [0; 8] 114 + } 115 + } 116 + 117 + #[derive(Clone, PartialEq, Eq, Hash)] 118 + #[non_exhaustive] 119 + pub struct Style { 120 + pub path: PathBuf, 121 + pub(crate) tailwind: bool, 122 + } 123 + 124 + impl Asset for Style { 125 + fn url(&self) -> Option<String> { 126 + let file_name = self.path.file_name().unwrap().to_str().unwrap(); 127 + 128 + format!("/_assets/{}", file_name).into() 129 + } 130 + 131 + fn path(&self) -> &PathBuf { 132 + &self.path 133 + } 134 + 135 + fn process(&self) -> Option<String> { 136 + if self.tailwind { 137 + let tmp_path = "dist/_tmp/tailwind.css"; 138 + let start_tailwind = SystemTime::now(); 139 + let tailwind_output = Command::new("tailwindcss") 140 + .arg("--minify") 141 + .args(["--output", tmp_path]) 142 + .output() 143 + .expect("failed to execute process"); 144 + 145 + info!("Tailwind took {:?}", start_tailwind.elapsed().unwrap()); 146 + 147 + if tailwind_output.status.success() { 148 + return Some(tmp_path.into()); 149 + } 150 + } 151 + 152 + None 153 + } 94 154 95 155 fn hash(&self) -> [u8; 8] { 96 156 [0; 8]
+26 -1
crates/framework/src/lib.rs
··· 12 12 mod logging; 13 13 14 14 use std::{ 15 - fs::{self, File}, 15 + fs::{self, remove_dir_all, File}, 16 16 io::{self, Write}, 17 17 path::{Path, PathBuf}, 18 18 process::Termination, ··· 132 132 133 133 let mut build_pages_assets: FxHashSet<Box<dyn Asset>> = FxHashSet::default(); 134 134 let mut build_pages_scripts: FxHashSet<assets::Script> = FxHashSet::default(); 135 + let mut build_pages_styles: FxHashSet<assets::Style> = FxHashSet::default(); 135 136 136 137 for route in routes { 137 138 let routes = route.routes(); ··· 153 154 154 155 build_pages_assets.extend(page_assets.assets); 155 156 build_pages_scripts.extend(page_assets.scripts); 157 + build_pages_styles.extend(page_assets.styles); 156 158 157 159 build_metadata.pages.push(PageOutput { 158 160 route: route.route_raw().to_string(), ··· 185 187 186 188 build_pages_assets.extend(pages_assets.assets); 187 189 build_pages_scripts.extend(pages_assets.scripts); 190 + build_pages_styles.extend(pages_assets.styles); 188 191 } 189 192 } 190 193 } ··· 202 205 // TODO: Add outputted assets to build_metadata, might need dedicated fs methods for this 203 206 }); 204 207 208 + let css_inputs = build_pages_styles 209 + .iter() 210 + .map(|style| { 211 + let processed_path = style.process(); 212 + 213 + InputItem { 214 + import: { 215 + if let Some(processed_path) = processed_path { 216 + processed_path 217 + } else { 218 + style.path().to_string_lossy().to_string() 219 + } 220 + }, 221 + ..Default::default() 222 + } 223 + }) 224 + .collect::<Vec<InputItem>>(); 225 + 205 226 let bundler_inputs = build_pages_scripts 206 227 .iter() 207 228 .map(|script| InputItem { 208 229 import: script.path().to_string_lossy().to_string(), 209 230 ..Default::default() 210 231 }) 232 + .chain(css_inputs.into_iter()) 211 233 .collect::<Vec<InputItem>>(); 212 234 213 235 if !bundler_inputs.is_empty() { ··· 240 262 format_elapsed_time(assets_start.elapsed(), &FormatElapsedTimeOptions::default())?; 241 263 info!(target: "build", "{}", format!("Assets copied in {}", formatted_elasped_time).bold()); 242 264 } 265 + 266 + // Remove temporary files 267 + remove_dir_all("dist/_tmp").unwrap_or_default(); 243 268 244 269 let formatted_elasped_time = 245 270 format_elapsed_time(build_start.elapsed(), &section_format_options)?;
+1
crates/framework/src/page.rs
··· 67 67 pub use super::{ 68 68 DynamicPage, FullPage, InternalPage, Page, RenderResult, RouteContext, RouteParams, 69 69 }; 70 + pub use crate::assets::Asset; 70 71 pub use maudit_macros::{route, Params}; 71 72 }
+3
crates/user-example/data/tailwind.css
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities;
+8 -3
crates/user-example/src/pages/dynamic.rs
··· 26 26 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 27 27 let params = ctx.params.parse_into::<Params>(); 28 28 let image = ctx.assets.add_image("data/social-card.png".into()); 29 + let style = ctx.assets.add_style("data/tailwind.css".into(), true); 29 30 30 31 RenderResult::Html(html! { 31 - h1 { "Hello, world!" } 32 - img src=(image.path.to_string_lossy()) {} 33 - p { (params.page) } 32 + head { 33 + title { "Index" } 34 + link rel="stylesheet" href=(style.url().unwrap()) {} 35 + } 36 + h1 { "Hello, world!" } 37 + img src=(image.path.to_string_lossy()) {} 38 + p { (params.page) } 34 39 }) 35 40 } 36 41 }
+9 -4
crates/user-example/src/pages/index.rs
··· 11 11 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 12 12 let image = ctx.assets.add_image("data/logo.svg".into()); 13 13 let script = ctx.assets.add_script("data/some_other_script.js".into()); 14 + let style = ctx.assets.add_style("data/tailwind.css".into(), true); 14 15 15 16 let link_to_first_dynamic = DynamicExample::url_unsafe(&DynamicExampleParams { page: 1 }); 16 17 17 18 let safe_link_to_first_dynamic = DynamicExample 18 - .url(&DynamicExampleParams { page: 2 }) 19 + .url(&DynamicExampleParams { page: 0 }) 19 20 .unwrap(); 20 21 21 22 RenderResult::Html(html! { 23 + head { 24 + title { "Index" } 25 + link rel="stylesheet" href=(style.url().unwrap()) {} 26 + } 22 27 h1 { "Index" } 23 - img src=(image.path.to_string_lossy()) {} 24 - script src=(script.path.to_string_lossy()) {} 25 - a href=(link_to_first_dynamic) { "Go to first dynamic page" } 28 + img src=(image.url().unwrap()) {} 29 + script src=(script.url().unwrap()) {} 30 + a."text-red-500" href=(link_to_first_dynamic) { "Go to first dynamic page" } 26 31 a href=(safe_link_to_first_dynamic) { "Go to first dynamic page (safe)" } 27 32 }) 28 33 }
+8
crates/user-example/tailwind.config.js
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + module.exports = { 3 + content: ["./src/**/*.rs"], 4 + theme: { 5 + extend: {}, 6 + }, 7 + plugins: [], 8 + };