generated from alecodes/base-template
wip: handle vite tooling manually
This commit is contained in:
parent
9c1a8f030c
commit
e8f111c2ff
11 changed files with 134 additions and 71 deletions
25
.idea/runConfigurations/Run_with_vite.xml
generated
Normal file
25
.idea/runConfigurations/Run_with_vite.xml
generated
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run with vite" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="buildProfileId" value="dev" />
|
||||
<option name="command" value="run --package compendium --bin compendium --features vite" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<envs>
|
||||
<env name="CPD_DB_HOST" value="localhost" />
|
||||
<env name="CPD_DB_NAME" value="compendium" />
|
||||
<env name="CPD_DB_PASSWORD" value="password" />
|
||||
<env name="CPD_DB_USER" value="postgres" />
|
||||
</envs>
|
||||
<option name="emulateTerminal" value="true" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="buildTarget" value="REMOTE" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -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"] }
|
||||
|
|
|
|||
31
build.rs
31
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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
frontend/static/.gitkeep
Normal file
0
frontend/static/.gitkeep
Normal file
|
|
@ -8,8 +8,8 @@
|
|||
{% if is_production == false %}
|
||||
<script src="{{ asset('/@vite/client') }}" type="module"></script>
|
||||
{% endif %}
|
||||
<link href="{{ asset('assets/css/style.scss') }}" rel="stylesheet"/>
|
||||
<script src="{{ asset('assets/js/index.ts') }}" type="module"></script>
|
||||
<link href="{{ asset('css/style.scss') }}" rel="stylesheet"/>
|
||||
<script src="{{ asset('js/index.ts') }}" type="module"></script>
|
||||
<title>
|
||||
{% block title %}Axum web service!{% endblock %}
|
||||
</title>
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<AppState> {
|
||||
Router::new()
|
||||
pub fn new(config: &Config) -> Router<AppState> {
|
||||
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<AppState>,
|
||||
Path(asset_path): Path<String>,
|
||||
) -> Result<(HeaderMap, String)> {
|
||||
let mut headers = HeaderMap::new();
|
||||
#[cfg(feature = "embed")]
|
||||
fn load_asset_router(config: &Config) -> Router<AppState> {
|
||||
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)]
|
||||
|
|
|
|||
|
|
@ -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<String, ViteAsset>;
|
|||
|
||||
pub struct Assets {
|
||||
templates: Environment<'static>,
|
||||
manifest: ViteManifest,
|
||||
_reloader: Option<AutoReloader>,
|
||||
}
|
||||
|
||||
|
|
@ -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<Self> {
|
||||
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<S: Serialize>(&self, path: &str, ctx: S) -> ResultTemplate {
|
||||
|
|
|
|||
|
|
@ -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<String, Error> {
|
||||
#[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<String, Error> {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue