From e8f111c2ff3b00d663a8b513f14faa1f5d64a7d7 Mon Sep 17 00:00:00 2001 From: aleidk Date: Fri, 25 Apr 2025 16:53:44 -0400 Subject: [PATCH] wip: handle vite tooling manually --- .idea/runConfigurations/Run_with_vite.xml | 25 ++++++++++ Cargo.toml | 5 +- build.rs | 31 +++++++------ frontend/static/.gitkeep | 0 frontend/templates/base.html | 4 +- src/config.rs | 2 + src/main.rs | 4 +- src/router.rs | 56 +++++++++++++---------- src/static_assets.rs | 35 +++++++++++--- src/static_assets/template_functions.rs | 37 +++++++-------- vite.config.ts | 6 +++ 11 files changed, 134 insertions(+), 71 deletions(-) create mode 100644 .idea/runConfigurations/Run_with_vite.xml create mode 100644 frontend/static/.gitkeep diff --git a/.idea/runConfigurations/Run_with_vite.xml b/.idea/runConfigurations/Run_with_vite.xml new file mode 100644 index 0000000..b7cf94b --- /dev/null +++ b/.idea/runConfigurations/Run_with_vite.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 9c44718..7014a68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "compendium" version = "0.1.0" -edition = "2021" +edition = "2024" [features] -bundled = [] +embed = ["vite"] +vite = [] [dependencies] axum = { version = "0.8.1", features = ["macros"] } diff --git a/build.rs b/build.rs index 9c4632b..2b6833b 100644 --- a/build.rs +++ b/build.rs @@ -1,24 +1,27 @@ -use std::fs; -use std::path::Path; - fn main() { - let out_dir = std::env::var("OUT_DIR").unwrap(); - // we only need to bundle the templates with the - // feature is enabled. - #[cfg(not(debug_assertions))] + #[cfg(feature = "vite")] { + let out_dir = if cfg!(feature = "embed") { + std::env::var("OUT_DIR").unwrap() + } else { + format!("../../{}", std::env::var("CPD_PUBLIC_DIR").unwrap_or(String::from("public"))) + }; + std::process::Command::new("bun") .args(&["vite", "build", "--outDir", &out_dir]) .status() .unwrap(); - minijinja_embed::embed_templates!("frontend/templates"); - } - #[cfg(debug_assertions)] + println!("cargo::rerun-if-changed=frontend/assets"); + println!("cargo::rerun-if-changed=frontend/static"); + println!("cargo::rerun-if-env-changed=CPD_PUBLIC_DIR"); + println!("cargo::rerun-if-env-changed=CARGO_FEATURE_EMBED"); + } + println!("cargo::rerun-if-env-changed=CARGO_FEATURE_VITE"); + + #[cfg(feature = "embed")] { - // dummy file to satisfy the compiler in dev builds - let dest_path = Path::new(&out_dir).join(".vite/manifest.json"); - fs::create_dir_all(dest_path.parent().unwrap()).unwrap(); - fs::write(&dest_path, "{}").unwrap(); + minijinja_embed::embed_templates!("frontend/templates"); + println!("cargo::rerun-if-changed=frontend/templates"); } } diff --git a/frontend/static/.gitkeep b/frontend/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/templates/base.html b/frontend/templates/base.html index 358cd75..4220c6f 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -8,8 +8,8 @@ {% if is_production == false %} {% endif %} - - + + {% block title %}Axum web service!{% endblock %} 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/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 09946f2..b3c8e3d 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,6 +1,8 @@ +use crate::config::Config; +use crate::{AppState, ResultTemplate, Tx}; use axum::{ - extract::{Path, State}, - http::{header, HeaderMap, HeaderValue}, + extract::State + , routing::get, Router, }; @@ -9,34 +11,38 @@ use chrono::Utc; use minijinja::context; use serde::Serialize; use sqlx::prelude::FromRow; +use tower_http::services::ServeDir; -use crate::static_assets::{ViteManifest, VITE_MANIFEST_STR}; -use crate::{AppState, Result, ResultTemplate, Tx}; - -pub fn new() -> Router { - Router::new() +pub fn new(config: &Config) -> Router { + let router = Router::new() .route("/", get(handler_home)) - .route("/assets/{*asset}", get(handle_assets)) + .nest("/public", load_asset_router(config)); + + router } -async fn handle_assets( - State(state): State, - Path(asset_path): Path, -) -> Result<(HeaderMap, String)> { - let mut headers = HeaderMap::new(); +#[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")), + ) +} - let manifest: ViteManifest = serde_json::from_str(VITE_MANIFEST_STR)?; - - headers.insert( - header::CONTENT_TYPE, - HeaderValue::from_str("text/plain").unwrap(), - ); - - println!("{}, {:?}", asset_path, manifest); - let asset = manifest - .get(format!("frontend/assets/{}", asset_path).as_str()) - .unwrap(); - Ok((headers, asset.file.clone())) +#[cfg(not(feature = "embed"))] +fn load_asset_router(config: &Config) -> Result<(ViteManifest, String)> { + Router::new() + .fallback_service( + ServeDir::new(&config.public_dir), + ) + .nest_service( + "/assets", + ServeDir::new(&config.public_dir.join("assets")), + ) } #[derive(FromRow, Debug, Serialize)] diff --git a/src/static_assets.rs b/src/static_assets.rs index d06825a..2deaaa4 100644 --- a/src/static_assets.rs +++ b/src/static_assets.rs @@ -1,17 +1,17 @@ mod template_functions; +use crate::config::Config; use crate::static_assets::template_functions::load_functions; -use crate::ResultTemplate; +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; -pub const VITE_MANIFEST_STR: &str = include_str!(concat!(env!("OUT_DIR"), "/.vite/manifest.json")); - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] +#[derive(Clone)] pub struct ViteAsset { pub file: String, pub src: String, @@ -22,6 +22,7 @@ pub type ViteManifest = HashMap; pub struct Assets { templates: Environment<'static>, + manifest: ViteManifest, _reloader: Option, } @@ -29,14 +30,19 @@ impl Clone for Assets { fn clone(&self) -> Self { Self { templates: self.templates.clone(), + manifest: self.manifest.clone(), _reloader: None, } } } impl Assets { - pub fn new() -> Self { + 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; @@ -60,10 +66,27 @@ impl Assets { minijinja_embed::load_templates!(&mut templates); } - Self { + Ok(Self { templates, + manifest, _reloader, - } + }) + } + + #[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())) + } + + #[cfg(not(feature = "embed"))] + fn load_vite_manifest(config: &Config) -> Result<(ViteManifest, String)> { + let file = std::fs::read_to_string(config.public_dir.join(".vite/manifest.json"))?; + Ok((serde_json::from_str(file.as_str())?, file)) + } + + pub fn get_vite_asset(&self, asset: &str) -> Option<&ViteAsset> { + self.manifest.get(asset) } pub fn render_template(&self, path: &str, ctx: S) -> ResultTemplate { diff --git a/src/static_assets/template_functions.rs b/src/static_assets/template_functions.rs index 5f00075..aabe5fd 100644 --- a/src/static_assets/template_functions.rs +++ b/src/static_assets/template_functions.rs @@ -1,27 +1,24 @@ -use minijinja::{Environment, Error, ErrorKind}; +use crate::static_assets::ViteManifest; +use minijinja::{Environment, Error, ErrorKind, State}; +use serde_json::from_str; -fn load_asset(asset: String) -> Result { - #[cfg(debug_assertions)] - { - let url = - url::Url::parse("http://localhost:3001/frontend/").map_err(|_| ErrorKind::EvalBlock)?; - return Ok(url - .join(asset.as_str()) - .map_err(|_| ErrorKind::EvalBlock)? - .to_string()); - } +fn load_asset(state: &State, asset: String) -> Result { + let manifest: ViteManifest = from_str( + state.lookup("vite_manifest") + .ok_or(ErrorKind::EvalBlock)? + .as_str() + .ok_or(ErrorKind::EvalBlock)? + ).map_err(|_| ErrorKind::EvalBlock)?; - #[cfg(not(debug_assertions))] - { - let url = url::Url::parse("http://localhost:3001").map_err(|_| ErrorKind::EvalBlock)?; + let local_asset = manifest.get(asset.as_str()); - return Ok(url - .join(asset.as_str()) - .map_err(|_| ErrorKind::EvalBlock)? - .path() - .to_string()); - } + Ok(match local_asset { + None => asset, + Some(asset) => { + format!("/public/{}", asset.file.clone()) + } + }) } pub(super) fn load_functions(env: &mut Environment) { diff --git a/vite.config.ts b/vite.config.ts index b39451b..95bb5f5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,9 @@ import {defineConfig} from "vite"; export default defineConfig({ + root: "frontend/assets", + publicDir: "frontend/static", + base: "/public/assets", plugins: [], server: { port: 3001, @@ -8,6 +11,9 @@ export default defineConfig({ cors: true, }, build: { + outDir: "../../public", // outDir is relative to the root config + assetsDir: "assets", + emptyOutDir: true, manifest: true, rollupOptions: { input: [