Merge pull request 'feat: minification' (#17) from minification into main
All checks were successful
ci/woodpecker/push/publish Pipeline was successful

Reviewed-on: #17
This commit is contained in:
Mylloon 2023-04-09 19:30:03 +02:00
commit eaaaf91cb2
Signed by: Forgejo
GPG key ID: E72245C752A07631
11 changed files with 287 additions and 192 deletions

177
Cargo.lock generated
View file

@ -260,33 +260,10 @@ dependencies = [
]
[[package]]
name = "askama"
version = "0.12.0"
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e"
dependencies = [
"askama_derive",
"askama_escape",
"humansize",
"num-traits",
"percent-encoding",
]
[[package]]
name = "askama_derive"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94"
dependencies = [
"basic-toml",
"mime",
"mime_guess",
"nom",
"proc-macro2",
"quote",
"serde",
"syn 2.0.13",
]
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "askama_escape"
@ -300,6 +277,19 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bae"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "base64"
version = "0.21.0"
@ -307,13 +297,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "basic-toml"
version = "0.1.2"
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1"
dependencies = [
"serde",
]
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bitflags"
@ -475,9 +462,9 @@ version = "0.1.0"
dependencies = [
"actix-files",
"actix-web",
"askama",
"glob",
"minify-html",
"ramhorns",
"serde",
"toml",
]
@ -589,6 +576,15 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -627,15 +623,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "idna"
version = "0.3.0"
@ -689,12 +676,6 @@ version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "libm"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
[[package]]
name = "local-channel"
version = "0.1.3"
@ -732,6 +713,29 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "logos"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-derive"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-syntax",
"syn 1.0.109",
]
[[package]]
name = "memchr"
version = "2.5.0"
@ -815,15 +819,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
@ -910,6 +905,30 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.56"
@ -919,6 +938,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.26"
@ -928,6 +958,33 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "ramhorns"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47008ae2e2a9085a3f658203609d79f8a027829cf88a088d0c0084e18ba8f0b9"
dependencies = [
"arrayvec",
"beef",
"fnv",
"logos",
"pulldown-cmark",
"ramhorns-derive",
]
[[package]]
name = "ramhorns-derive"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada9bbdd21adf426f932bf76b3db7d553538dffc16afd5fb8ce2ce2110a75536"
dependencies = [
"bae",
"fnv",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -1300,6 +1357,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "url"
version = "2.3.1"

View file

@ -12,7 +12,7 @@ license = "AGPL-3.0-or-later"
[dependencies]
actix-web = "4"
actix-files = "0.6"
askama = "0.12.0"
ramhorns = "0.14.0"
toml = "0.7.3"
serde = { version = "1.0.159", features = ["derive"] }
minify-html = "0.10.8"

View file

@ -1,5 +1,14 @@
use serde::Deserialize;
use std::fs;
use std::{
fs::{self, remove_dir_all},
path::PathBuf,
};
use glob::glob;
use minify_html::{minify, Cfg};
use std::{fs::File, io::Write, path::Path};
use crate::template::Template;
#[derive(Deserialize, Clone, Default)]
pub struct FileConfig {
@ -10,11 +19,6 @@ pub struct FileConfig {
pub onion: Option<String>,
}
#[derive(Clone)]
pub struct Config {
pub fc: FileConfig,
}
impl FileConfig {
fn new() -> Self {
Self {
@ -47,6 +51,13 @@ impl FileConfig {
}
}
#[derive(Clone)]
pub struct Config {
pub fc: FileConfig,
pub static_location: String,
pub tmpl: Template,
}
fn get_file_config(file_path: &str) -> FileConfig {
match fs::read_to_string(file_path) {
Ok(file) => match toml::from_str(&file) {
@ -66,7 +77,86 @@ fn get_file_config(file_path: &str) -> FileConfig {
pub fn get_config(file_path: &str) -> Config {
let internal_config = get_file_config(file_path);
let static_dir = "static".to_string();
let templates_dir = "templates".to_string();
let files_root = init(
"dist".to_string(),
static_dir.clone(),
templates_dir.clone(),
);
Config {
fc: internal_config,
static_location: format!("{}/{}", files_root, static_dir),
tmpl: Template {
directory: format!("{}/{}", files_root, templates_dir),
},
}
}
fn init(dist_dir: String, static_dir: String, templates_dir: String) -> String {
// The static folder is minimized only in release mode
if cfg!(debug_assertions) {
// Be sure that we not gonna use the dist folder by deleting it
remove_dir_all(dist_dir).unwrap_or_default();
".".to_string()
} else {
let cfg = Cfg::spec_compliant();
// Static files
for entry in glob(&format!("{static_dir}/**/*.*")).unwrap() {
let path = entry.unwrap();
let path_with_dist = path
.to_string_lossy()
.replace(&static_dir, &format!("{dist_dir}/{static_dir}"));
minify_and_copy(&cfg, path, path_with_dist);
}
// Template files
for entry in glob(&format!("{templates_dir}/**/*.*")).unwrap() {
let path = entry.unwrap();
let path_with_dist = path
.to_string_lossy()
.replace(&templates_dir, &format!("{dist_dir}/{templates_dir}"));
minify_and_copy(&cfg, path, path_with_dist);
}
dist_dir
}
}
fn minify_and_copy(cfg: &Cfg, path: PathBuf, path_with_dist: String) {
// Create folders
let new_path = Path::new(&path_with_dist);
fs::create_dir_all(new_path.parent().unwrap()).unwrap();
let mut copy = true;
if let Some(ext) = path.extension() {
// List of files who should be minified
if ["html", "css", "js", "svg", "webmanifest", "xml"]
.iter()
.any(|item| ext.to_string_lossy().to_lowercase().contains(item))
{
// We won't copy, we'll minify
copy = false;
// Minify
let data = fs::read(&path).unwrap();
let minified = minify(&data, cfg);
// Write files
let file = File::create(&path_with_dist);
file.expect("Error when minify the file")
.write_all(&minified)
.unwrap();
}
}
if copy {
// If no minification is needed
fs::copy(path, path_with_dist).unwrap();
}
}

View file

@ -1,12 +1,6 @@
use actix_files::Files;
use actix_web::{middleware::DefaultHeaders, web, App, HttpServer};
use glob::glob;
use minify_html::{minify, Cfg};
use std::{
fs::{self, File},
io::{self, Write},
path::Path,
};
use std::io;
mod config;
mod template;
@ -35,52 +29,6 @@ async fn main() -> io::Result<()> {
let addr = ("0.0.0.0", config.fc.port.unwrap());
let static_folder = "static";
let dist_folder = format!("dist/{static_folder}");
// The static folder is minimized only in release mode
let folder = if cfg!(debug_assertions) {
format!("{static_folder}/")
} else {
let cfg = Cfg::spec_compliant();
for entry in glob(&format!("{static_folder}/**/*.*")).unwrap() {
let path = entry.unwrap();
let path_with_dist = path.to_string_lossy().replace(static_folder, &dist_folder);
// Create folders
let new_path = Path::new(&path_with_dist);
fs::create_dir_all(new_path.parent().unwrap()).unwrap();
let mut copy = true;
if let Some(ext) = path.extension() {
// List of files who should be minified
if ["html", "css", "js", "svg", "webmanifest", "xml"]
.iter()
.any(|item| ext.to_string_lossy().to_lowercase().contains(item))
{
// We won't copy, we'll minify
copy = false;
// Minify
let data = fs::read(&path).unwrap();
let minified = minify(&data, &cfg);
// Write files
let mut file = File::create(&path_with_dist)?;
file.write_all(&minified)?;
}
}
if copy {
// If no minification is needed
fs::copy(path, path_with_dist)?;
}
}
format!("{dist_folder}/")
};
println!(
"Listening to {}://{}:{}",
config.clone().fc.scheme.unwrap(),
@ -103,7 +51,7 @@ async fn main() -> io::Result<()> {
.service(networks::page)
.service(portfolio::page)
.service(contrib::page)
.service(Files::new("/", &folder))
.service(Files::new("/", config.static_location.clone()))
.default_service(web::to(not_found::page))
})
.bind(addr)?

View file

@ -1,7 +1,6 @@
use actix_web::{dev::ConnectionInfo, get, routes, web, HttpRequest, HttpResponse, Responder};
use askama::Template;
use crate::config::Config;
use actix_web::{dev::ConnectionInfo, get, routes, web, HttpRequest, HttpResponse, Responder};
use ramhorns::Content;
#[routes]
#[get("/.well-known/security.txt")]
@ -13,8 +12,7 @@ pub async fn security(req: HttpRequest, config: web::Data<Config>) -> impl Respo
))
}
#[derive(Template)]
#[template(path = "../templates/security.txt")]
#[derive(Content)]
struct SecurityTemplate {
contact: String,
pref_lang: String,
@ -22,17 +20,18 @@ struct SecurityTemplate {
}
fn get_security(config: Config, info: ConnectionInfo) -> String {
let data = SecurityTemplate {
contact: config.fc.mail.unwrap_or_default(),
pref_lang: config.fc.lang.unwrap_or_default(),
url: format!(
"{}://{}/.well-known/security.txt",
info.scheme(),
info.host()
),
};
data.render().unwrap()
config.tmpl.render(
"security.txt",
SecurityTemplate {
contact: config.fc.mail.unwrap_or_default(),
pref_lang: config.fc.lang.unwrap_or_default(),
url: format!(
"{}://{}/.well-known/security.txt",
info.scheme(),
info.host()
),
},
)
}
#[get("/humans.txt")]

View file

@ -1,17 +1,15 @@
use actix_web::{get, HttpResponse, Responder};
use askama::Template;
use crate::template::render_html;
use crate::config::Config;
use actix_web::{get, web, HttpResponse, Responder};
use ramhorns::Content;
#[get("/contrib")]
pub async fn page() -> impl Responder {
HttpResponse::Ok().body(get_page())
pub async fn page(config: web::Data<Config>) -> impl Responder {
HttpResponse::Ok().body(get_page(config.get_ref().clone()))
}
#[derive(Template)]
#[template(path = "../templates/contrib.html")]
#[derive(Content)]
struct PortfolioTemplate {}
pub fn get_page() -> std::string::String {
render_html(&PortfolioTemplate {})
pub fn get_page(config: Config) -> std::string::String {
config.tmpl.render("contrib.html", PortfolioTemplate {})
}

View file

@ -1,17 +1,16 @@
use actix_web::{get, HttpResponse, Responder};
use askama::Template;
use actix_web::{get, web, HttpResponse, Responder};
use ramhorns::Content;
use crate::template::render_html;
use crate::config::Config;
#[get("/")]
pub async fn page() -> impl Responder {
HttpResponse::Ok().body(get_page())
pub async fn page(config: web::Data<Config>) -> impl Responder {
HttpResponse::Ok().body(get_page(config.get_ref().clone()))
}
#[derive(Template)]
#[template(path = "../templates/index.html")]
#[derive(Content)]
struct IndexTemplate {}
pub fn get_page() -> std::string::String {
render_html(&IndexTemplate {})
pub fn get_page(config: Config) -> std::string::String {
config.tmpl.render("index.html", IndexTemplate {})
}

View file

@ -1,17 +1,16 @@
use actix_web::{get, HttpResponse, Responder};
use askama::Template;
use actix_web::{get, web, HttpResponse, Responder};
use ramhorns::Content;
use crate::template::render_html;
use crate::config::Config;
#[get("/networks")]
pub async fn page() -> impl Responder {
HttpResponse::Ok().body(get_page())
pub async fn page(config: web::Data<Config>) -> impl Responder {
HttpResponse::Ok().body(get_page(config.get_ref().clone()))
}
#[derive(Template)]
#[template(path = "../templates/networks.html")]
#[derive(Content)]
struct NetworksTemplate {}
pub fn get_page() -> std::string::String {
render_html(&NetworksTemplate {})
pub fn get_page(config: Config) -> std::string::String {
config.tmpl.render("networks.html", NetworksTemplate {})
}

View file

@ -1,16 +1,15 @@
use actix_web::{HttpResponse, Responder};
use askama::Template;
use actix_web::{web, HttpResponse, Responder};
use ramhorns::Content;
use crate::template::render_html;
use crate::config::Config;
pub async fn page() -> impl Responder {
HttpResponse::NotFound().body(get_page())
pub async fn page(config: web::Data<Config>) -> impl Responder {
HttpResponse::NotFound().body(get_page(config.get_ref().clone()))
}
#[derive(Template)]
#[template(path = "../templates/404.html")]
#[derive(Content)]
struct Error404Template {}
pub fn get_page() -> std::string::String {
render_html(&Error404Template {})
pub fn get_page(config: Config) -> std::string::String {
config.tmpl.render("404.html", Error404Template {})
}

View file

@ -1,17 +1,16 @@
use actix_web::{get, HttpResponse, Responder};
use askama::Template;
use actix_web::{get, web, HttpResponse, Responder};
use ramhorns::Content;
use crate::template::render_html;
use crate::config::Config;
#[get("/portfolio")]
pub async fn page() -> impl Responder {
HttpResponse::Ok().body(get_page())
pub async fn page(config: web::Data<Config>) -> impl Responder {
HttpResponse::Ok().body(get_page(config.get_ref().clone()))
}
#[derive(Template)]
#[template(path = "../templates/portfolio.html")]
#[derive(Content)]
struct PortfolioTemplate {}
pub fn get_page() -> std::string::String {
render_html(&PortfolioTemplate {})
pub fn get_page(config: Config) -> std::string::String {
config.tmpl.render("portfolio.html", PortfolioTemplate {})
}

View file

@ -1,14 +1,15 @@
use askama::DynTemplate;
use minify_html::{minify, Cfg};
use ramhorns::{Content, Ramhorns};
pub fn render_html(template: &dyn DynTemplate) -> String {
let data = template.dyn_render().unwrap();
// we just minify before sending to the client,
// we need to find a way to pre-minify static pages to the dist folder,
// maybe in release mode? to make debugging easier
// from https://git.mylloon.fr/Anri/mylloon.fr/issues/15
let cfg = Cfg::spec_compliant();
String::from_utf8(minify(data.as_bytes(), &cfg)).unwrap()
#[derive(Clone)]
pub struct Template {
pub directory: String,
}
impl Template {
pub fn render<C: Content>(&self, template: &str, data: C) -> String {
let mut templates: Ramhorns = Ramhorns::lazy(&self.directory).unwrap();
let tplt = templates.from_file(template).unwrap();
tplt.render(&data)
}
}