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]
|
[package]
|
||||||
name = "compendium"
|
name = "compendium"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
bundled = []
|
embed = ["vite"]
|
||||||
|
vite = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.1", features = ["macros"] }
|
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() {
|
fn main() {
|
||||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
#[cfg(feature = "vite")]
|
||||||
// we only need to bundle the templates with the
|
|
||||||
// feature is enabled.
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
{
|
||||||
|
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")
|
std::process::Command::new("bun")
|
||||||
.args(&["vite", "build", "--outDir", &out_dir])
|
.args(&["vite", "build", "--outDir", &out_dir])
|
||||||
.status()
|
.status()
|
||||||
.unwrap();
|
.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
|
minijinja_embed::embed_templates!("frontend/templates");
|
||||||
let dest_path = Path::new(&out_dir).join(".vite/manifest.json");
|
println!("cargo::rerun-if-changed=frontend/templates");
|
||||||
fs::create_dir_all(dest_path.parent().unwrap()).unwrap();
|
|
||||||
fs::write(&dest_path, "{}").unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
0
frontend/static/.gitkeep
Normal file
0
frontend/static/.gitkeep
Normal file
|
|
@ -8,8 +8,8 @@
|
||||||
{% if is_production == false %}
|
{% if is_production == false %}
|
||||||
<script src="{{ asset('/@vite/client') }}" type="module"></script>
|
<script src="{{ asset('/@vite/client') }}" type="module"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<link href="{{ asset('assets/css/style.scss') }}" rel="stylesheet"/>
|
<link href="{{ asset('css/style.scss') }}" rel="stylesheet"/>
|
||||||
<script src="{{ asset('assets/js/index.ts') }}" type="module"></script>
|
<script src="{{ asset('js/index.ts') }}" type="module"></script>
|
||||||
<title>
|
<title>
|
||||||
{% block title %}Axum web service!{% endblock %}
|
{% block title %}Axum web service!{% endblock %}
|
||||||
</title>
|
</title>
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ impl DBConfig {
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub db: DBConfig,
|
pub db: DBConfig,
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
|
pub public_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
|
@ -91,6 +92,7 @@ impl Default for Config {
|
||||||
Config {
|
Config {
|
||||||
db: DBConfig::default(),
|
db: DBConfig::default(),
|
||||||
addr: "0.0.0.0:3000".parse().unwrap(),
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let config = Config::new("./config.toml".into())?;
|
let config = Config::new("./config.toml".into())?;
|
||||||
let assets = Assets::new();
|
let assets = Assets::new(&config)?;
|
||||||
|
|
||||||
// Logs
|
// Logs
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
|
|
@ -40,7 +40,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let (tx_state, tx_layer) = Tx::setup(pool);
|
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(TraceLayer::new_for_http().on_request(()))
|
||||||
.layer(tx_layer)
|
.layer(tx_layer)
|
||||||
.layer(AutoVaryLayer)
|
.layer(AutoVaryLayer)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::{AppState, ResultTemplate, Tx};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::State
|
||||||
http::{header, HeaderMap, HeaderValue},
|
,
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
|
@ -9,34 +11,38 @@ use chrono::Utc;
|
||||||
use minijinja::context;
|
use minijinja::context;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::prelude::FromRow;
|
use sqlx::prelude::FromRow;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
use crate::static_assets::{ViteManifest, VITE_MANIFEST_STR};
|
pub fn new(config: &Config) -> Router<AppState> {
|
||||||
use crate::{AppState, Result, ResultTemplate, Tx};
|
let router = Router::new()
|
||||||
|
|
||||||
pub fn new() -> Router<AppState> {
|
|
||||||
Router::new()
|
|
||||||
.route("/", get(handler_home))
|
.route("/", get(handler_home))
|
||||||
.route("/assets/{*asset}", get(handle_assets))
|
.nest("/public", load_asset_router(config));
|
||||||
|
|
||||||
|
router
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_assets(
|
#[cfg(feature = "embed")]
|
||||||
State(state): State<AppState>,
|
fn load_asset_router(config: &Config) -> Router<AppState> {
|
||||||
Path(asset_path): Path<String>,
|
Router::new()
|
||||||
) -> Result<(HeaderMap, String)> {
|
.fallback_service(
|
||||||
let mut headers = HeaderMap::new();
|
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)?;
|
#[cfg(not(feature = "embed"))]
|
||||||
|
fn load_asset_router(config: &Config) -> Result<(ViteManifest, String)> {
|
||||||
headers.insert(
|
Router::new()
|
||||||
header::CONTENT_TYPE,
|
.fallback_service(
|
||||||
HeaderValue::from_str("text/plain").unwrap(),
|
ServeDir::new(&config.public_dir),
|
||||||
);
|
)
|
||||||
|
.nest_service(
|
||||||
println!("{}, {:?}", asset_path, manifest);
|
"/assets",
|
||||||
let asset = manifest
|
ServeDir::new(&config.public_dir.join("assets")),
|
||||||
.get(format!("frontend/assets/{}", asset_path).as_str())
|
)
|
||||||
.unwrap();
|
|
||||||
Ok((headers, asset.file.clone()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromRow, Debug, Serialize)]
|
#[derive(FromRow, Debug, Serialize)]
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
mod template_functions;
|
mod template_functions;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
use crate::static_assets::template_functions::load_functions;
|
use crate::static_assets::template_functions::load_functions;
|
||||||
use crate::ResultTemplate;
|
use crate::{Result, ResultTemplate};
|
||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use minijinja::{path_loader, Environment};
|
use minijinja::{path_loader, Environment};
|
||||||
use minijinja_autoreload::AutoReloader;
|
use minijinja_autoreload::AutoReloader;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub const VITE_MANIFEST_STR: &str = include_str!(concat!(env!("OUT_DIR"), "/.vite/manifest.json"));
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ViteAsset {
|
pub struct ViteAsset {
|
||||||
pub file: String,
|
pub file: String,
|
||||||
pub src: String,
|
pub src: String,
|
||||||
|
|
@ -22,6 +22,7 @@ pub type ViteManifest = HashMap<String, ViteAsset>;
|
||||||
|
|
||||||
pub struct Assets {
|
pub struct Assets {
|
||||||
templates: Environment<'static>,
|
templates: Environment<'static>,
|
||||||
|
manifest: ViteManifest,
|
||||||
_reloader: Option<AutoReloader>,
|
_reloader: Option<AutoReloader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,14 +30,19 @@ impl Clone for Assets {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
templates: self.templates.clone(),
|
templates: self.templates.clone(),
|
||||||
|
manifest: self.manifest.clone(),
|
||||||
_reloader: None,
|
_reloader: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assets {
|
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();
|
let mut templates = Environment::new();
|
||||||
|
templates.add_global("vite_manifest", manifest_str);
|
||||||
|
|
||||||
load_functions(&mut templates);
|
load_functions(&mut templates);
|
||||||
templates.set_loader(path_loader("frontend/templates"));
|
templates.set_loader(path_loader("frontend/templates"));
|
||||||
let mut _reloader = None;
|
let mut _reloader = None;
|
||||||
|
|
@ -60,10 +66,27 @@ impl Assets {
|
||||||
minijinja_embed::load_templates!(&mut templates);
|
minijinja_embed::load_templates!(&mut templates);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
templates,
|
templates,
|
||||||
|
manifest,
|
||||||
_reloader,
|
_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 {
|
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
|
fn load_asset(state: &State, asset: String) -> Result<String, Error> {
|
||||||
.join(asset.as_str())
|
let manifest: ViteManifest = from_str(
|
||||||
.map_err(|_| ErrorKind::EvalBlock)?
|
state.lookup("vite_manifest")
|
||||||
.to_string());
|
.ok_or(ErrorKind::EvalBlock)?
|
||||||
}
|
.as_str()
|
||||||
|
.ok_or(ErrorKind::EvalBlock)?
|
||||||
|
).map_err(|_| ErrorKind::EvalBlock)?;
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
let local_asset = manifest.get(asset.as_str());
|
||||||
{
|
|
||||||
let url = url::Url::parse("http://localhost:3001").map_err(|_| ErrorKind::EvalBlock)?;
|
|
||||||
|
|
||||||
return Ok(url
|
Ok(match local_asset {
|
||||||
.join(asset.as_str())
|
None => asset,
|
||||||
.map_err(|_| ErrorKind::EvalBlock)?
|
Some(asset) => {
|
||||||
.path()
|
format!("/public/{}", asset.file.clone())
|
||||||
.to_string());
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn load_functions(env: &mut Environment) {
|
pub(super) fn load_functions(env: &mut Environment) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import {defineConfig} from "vite";
|
import {defineConfig} from "vite";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
root: "frontend/assets",
|
||||||
|
publicDir: "frontend/static",
|
||||||
|
base: "/public/assets",
|
||||||
plugins: [],
|
plugins: [],
|
||||||
server: {
|
server: {
|
||||||
port: 3001,
|
port: 3001,
|
||||||
|
|
@ -8,6 +11,9 @@ export default defineConfig({
|
||||||
cors: true,
|
cors: true,
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
outDir: "../../public", // outDir is relative to the root config
|
||||||
|
assetsDir: "assets",
|
||||||
|
emptyOutDir: true,
|
||||||
manifest: true,
|
manifest: true,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: [
|
input: [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue