From af709f2e72ea6fa6dfb2c3607c8b43941dc85436 Mon Sep 17 00:00:00 2001 From: aleidk Date: Mon, 21 Apr 2025 20:14:07 -0400 Subject: [PATCH] refactor: change assets handling to vite-rs chore: update dev environment wip: handle vite tooling manually --- .gitignore | 4 +- .idea/QuickJinja_project.xml | 10 + .idea/dictionaries/project.xml | 8 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/runConfigurations/Dev.xml | 8 + .idea/runConfigurations/Run_with_vite.xml | 25 ++ .idea/runConfigurations/Vite_Dev_Server.xml | 16 ++ .idea/runConfigurations/dev_database_pg.xml | 37 +++ .idea/sqlDataSources.xml | 42 +++ .idea/sqldialects.xml | 6 + Cargo.lock | 281 +------------------ Cargo.toml | 10 +- build.rs | 27 +- frontend/assets/js/index.ts | 1 + frontend/static/.gitkeep | 0 frontend/templates/base.html | 9 +- package.json | 44 +-- public/.gitkeep | 0 public/assets/.gitkeep | 0 src/config.rs | 2 + src/error.rs | 3 + src/main.rs | 4 +- src/router.rs | 94 ++++--- src/static_assets.rs | 110 +++++--- src/static_assets/template_functions.rs | 36 +++ vite.config.ts | 12 + 26 files changed, 406 insertions(+), 389 deletions(-) create mode 100644 .idea/QuickJinja_project.xml create mode 100644 .idea/dictionaries/project.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/runConfigurations/Dev.xml create mode 100644 .idea/runConfigurations/Run_with_vite.xml create mode 100644 .idea/runConfigurations/Vite_Dev_Server.xml create mode 100644 .idea/runConfigurations/dev_database_pg.xml create mode 100644 .idea/sqlDataSources.xml create mode 100644 .idea/sqldialects.xml create mode 100644 frontend/static/.gitkeep create mode 100644 public/.gitkeep create mode 100644 public/assets/.gitkeep create mode 100644 src/static_assets/template_functions.rs diff --git a/.gitignore b/.gitignore index 3399b78..807d687 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Devfiles .devfiles/bin/**/* +public/assets/** +public/.vite #### -- TEMPLATES BEGIN -- #### @@ -109,7 +111,6 @@ dist # vuepress v2.x temp and cache directory .temp -.cache # Docusaurus cache and generated files .docusaurus @@ -145,4 +146,3 @@ dist # Added by cargo /target -.env diff --git a/.idea/QuickJinja_project.xml b/.idea/QuickJinja_project.xml new file mode 100644 index 0000000..76760d4 --- /dev/null +++ b/.idea/QuickJinja_project.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..5f28118 --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,8 @@ + + + + endblock + htmx + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..fefa8f5 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Dev.xml b/.idea/runConfigurations/Dev.xml new file mode 100644 index 0000000..a76dce5 --- /dev/null +++ b/.idea/runConfigurations/Dev.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run_with_vite.xml b/.idea/runConfigurations/Run_with_vite.xml new file mode 100644 index 0000000..e695ac0 --- /dev/null +++ b/.idea/runConfigurations/Run_with_vite.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Vite_Dev_Server.xml b/.idea/runConfigurations/Vite_Dev_Server.xml new file mode 100644 index 0000000..63cf301 --- /dev/null +++ b/.idea/runConfigurations/Vite_Dev_Server.xml @@ -0,0 +1,16 @@ + + + + + + - - - + {% if is_production == false %} + + {% endif %} + + {% block title %}Axum web service!{% endblock %} diff --git a/package.json b/package.json index b50eadc..bef9057 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,26 @@ { - "name": "compendium", - "module": "index.ts", - "version": "0.1.0", - "devDependencies": { - "@alecodes/tmpl-build-and-load": "^0.1.3", - "@biomejs/biome": "1.9.4", - "@types/bun": "^1.2.10", - "sass-embedded": "^1.86.3", - "vite": "^6.3.1" - }, - "peerDependencies": { - "typescript": "^5.7.3" - }, - "type": "module", - "dependencies": { - "@mini-strap/core": "^0.1.2", - "@picocss/pico": "^2.1.1", - "feather-icons": "^4.29.2", - "htmx.org": "2.0.4" - } + "name": "compendium", + "module": "index.ts", + "version": "0.1.0", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "devDependencies": { + "@alecodes/tmpl-build-and-load": "^0.1.3", + "@biomejs/biome": "1.9.4", + "@types/bun": "^1.2.10", + "sass-embedded": "^1.86.3", + "vite": "^6.3.1" + }, + "peerDependencies": { + "typescript": "^5.7.3" + }, + "type": "module", + "dependencies": { + "@mini-strap/core": "^0.1.2", + "@picocss/pico": "^2.1.1", + "feather-icons": "^4.29.2", + "htmx.org": "2.0.4" + } } diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/assets/.gitkeep b/public/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/config.rs b/src/config.rs index 0f37fdc..daff0d9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -84,6 +84,7 @@ impl DBConfig { pub struct Config { pub db: DBConfig, pub addr: SocketAddr, + pub public_dir: PathBuf, } impl Default for Config { @@ -91,6 +92,7 @@ impl Default for Config { Config { db: DBConfig::default(), addr: "0.0.0.0:3000".parse().unwrap(), + public_dir: PathBuf::from("public"), } } } diff --git a/src/error.rs b/src/error.rs index 5bef9fb..a1c55bf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,6 +22,9 @@ pub enum Error { #[error(transparent)] Env(#[from] std::env::VarError), + #[error(transparent)] + JsonParse(#[from] serde_json::Error), + #[error(transparent)] Template(#[from] minijinja::Error), diff --git a/src/main.rs b/src/main.rs index 9ff00e5..3cc647e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::main] async fn main() -> Result<()> { let config = Config::new("./config.toml".into())?; - let assets = Assets::new(); + let assets = Assets::new(&config)?; // Logs tracing_subscriber::registry() @@ -40,7 +40,7 @@ async fn main() -> Result<()> { let (tx_state, tx_layer) = Tx::setup(pool); - let app = router::new() + let app = router::new(&config) .layer(TraceLayer::new_for_http().on_request(())) .layer(tx_layer) .layer(AutoVaryLayer) diff --git a/src/router.rs b/src/router.rs index 3b3a990..5f0938f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,8 +1,8 @@ -use axum::http::StatusCode; +use crate::config::Config; +use crate::{AppState, ResultTemplate, Tx}; use axum::{ - extract::{Path, State}, - http::{header, HeaderMap, HeaderValue}, - response::Html, + extract::State + , routing::get, Router, }; @@ -11,57 +11,67 @@ use chrono::Utc; use minijinja::context; use serde::Serialize; use sqlx::prelude::FromRow; -use vite_rs::ViteFile; +use tower_http::services::ServeDir; -use crate::{AppState, Error, Result, ResultTemplate, Tx}; +pub fn new(config: &Config) -> Router { + let router = Router::new() + .route("/", get(handler_home)) + .nest("/public", load_asset_router(config)); -pub fn new() -> Router { - Router::new() - .route("/", get(handler_home)) - .route("/assets/{*asset}", get(handle_assets)) + router } -async fn handle_assets( - State(state): State, - Path(asset_path): Path, -) -> Result<(HeaderMap, String)> { - let full_path = format!("frontend/assets/{}", asset_path); - let asset: ViteFile = state.assets.get_asset(&full_path).ok_or(Error::HTTP(StatusCode::NOT_FOUND))?; - let mut headers = HeaderMap::new(); - headers.insert(header::CONTENT_TYPE, HeaderValue::from_str(&asset.content_type).unwrap()); +#[cfg(feature = "embed")] +fn load_asset_router(config: &Config) -> Router { + Router::new() + .fallback_service( + ServeDir::new(&config.public_dir), + ) + .nest_service( + "/assets", + ServeDir::new(concat!(env!("OUT_DIR"), "/assets")), + ) +} - Ok((headers, String::from_utf8(asset.bytes.to_vec()).unwrap())) +#[cfg(not(feature = "embed"))] +fn load_asset_router(config: &Config) -> Router { + Router::new() + .fallback_service( + ServeDir::new(&config.public_dir), + ) + .nest_service( + "/assets", + ServeDir::new(&config.public_dir.join("assets")), + ) } #[derive(FromRow, Debug, Serialize)] struct ExampleRow { - pub database: String, - pub version: String, - pub current_database: String, - pub current_user: String, - pub current_timestamp: chrono::DateTime, + pub database: String, + pub version: String, + pub current_database: String, + pub current_user: String, + pub current_timestamp: chrono::DateTime, } async fn handler_home( - State(state): State, - HxRequest(hx_request): HxRequest, - mut tx: Tx, + State(state): State, + HxRequest(hx_request): HxRequest, + mut tx: Tx, ) -> ResultTemplate { - let template = state.assets.get_template("index.html").ok_or(Error::HTTP(StatusCode::NOT_FOUND))?; + let rows = sqlx::query_as::<_, ExampleRow>("select 'Postgres' as database, setting as version, current_database(), current_user, current_timestamp from pg_settings where name = 'server_version'") + .fetch_all(&mut tx) + .await?; - let rows = sqlx::query_as::<_, ExampleRow>("select 'Postgres' as database, setting as version, current_database(), current_user, current_timestamp from pg_settings where name = 'server_version'") - .fetch_all(&mut tx) - .await?; + let content = if hx_request { + state + .assets + .render_template_block("index.html", "htmx", context!(rows => rows))? + } else { + state + .assets + .render_template("index.html", context!(rows => rows))? + }; - println!("{:?}", rows); - - let content = if hx_request { - template - .eval_to_state(context!(rows => rows))? - .render_block("htmx")? - } else { - template.render(context!(rows => rows))? - }; - - Ok(Html(content)) + Ok(content) } diff --git a/src/static_assets.rs b/src/static_assets.rs index 48e5718..2deaaa4 100644 --- a/src/static_assets.rs +++ b/src/static_assets.rs @@ -1,14 +1,28 @@ -use minijinja::{path_loader, Environment, Template}; -use minijinja_autoreload::AutoReloader; -use vite_rs::{ViteFile, ViteProcess}; +mod template_functions; -#[derive(vite_rs::Embed)] -#[root = "."] -struct Static; +use crate::config::Config; +use crate::static_assets::template_functions::load_functions; +use crate::{Result, ResultTemplate}; +use axum::response::Html; +use minijinja::{path_loader, Environment}; +use minijinja_autoreload::AutoReloader; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[derive(Clone)] +pub struct ViteAsset { + pub file: String, + pub src: String, + pub is_entry: bool, +} + +pub type ViteManifest = HashMap; pub struct Assets { templates: Environment<'static>, - _guard: Option, + manifest: ViteManifest, _reloader: Option, } @@ -16,26 +30,30 @@ impl Clone for Assets { fn clone(&self) -> Self { Self { templates: self.templates.clone(), - _guard: None, + manifest: self.manifest.clone(), _reloader: None, } } } impl Assets { - pub fn new() -> Self { - #[allow(unused_mut)] let mut templates = Environment::new(); - let mut _guard = None; + pub fn new(config: &Config) -> Result { + let (manifest, manifest_str) = Self::load_vite_manifest(config)?; + + let mut templates = Environment::new(); + templates.add_global("vite_manifest", manifest_str); + + load_functions(&mut templates); + templates.set_loader(path_loader("frontend/templates")); let mut _reloader = None; // Load in dev mode #[cfg(debug_assertions)] { - // templates.set_loader(minijinja::path_loader("frontend/templates")); - _guard = Static::start_dev_server(true); _reloader = Some(AutoReloader::new(|notifier| { - let template_path = "path/to/templates"; + let template_path = "frontend/templates"; let mut env = Environment::new(); + load_functions(&mut env); env.set_loader(path_loader(template_path)); notifier.watch_path(template_path, true); Ok(env) @@ -45,31 +63,59 @@ impl Assets { // Load in prod mode #[cfg(not(debug_assertions))] { - minijinja_embed::load_templates!(&mut env); + minijinja_embed::load_templates!(&mut templates); } - - Self { + Ok(Self { templates, - _guard, + manifest, _reloader, - } + }) } - pub fn get_asset(&self, path: &str) -> Option { - let full_path = format!("frontend/assets/{}", path); - Static::get(full_path.as_str()) + #[cfg(feature = "embed")] + fn load_vite_manifest(_config: &Config) -> Result<(ViteManifest, String)> { + let file = include_str!(concat!(env!("OUT_DIR"), "/.vite/manifest.json")); + Ok((serde_json::from_str(file)?, file.to_string())) } - pub fn get_template(&self, path: &str) -> Option