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: [