mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
dynamic/static embeds
This commit is contained in:
parent
e5c9aeb7fe
commit
cb56c80555
22 changed files with 9068 additions and 665 deletions
894
Cargo.lock
generated
894
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -28,6 +28,4 @@ async-session = "3.0.0"
|
||||||
quick-xml = { version = "0.28.1", features = ["serialize"] }
|
quick-xml = { version = "0.28.1", features = ["serialize"] }
|
||||||
reqwest = "0.11.16"
|
reqwest = "0.11.16"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
demostf-build = { path = "./build", version = "*" }
|
demostf-build = { path = "./build", version = "*" }
|
||||||
|
|
|
||||||
12
build.rs
12
build.rs
|
|
@ -1,12 +0,0 @@
|
||||||
use demostf_build::{bundle_script, bundle_style, save_asset};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
|
||||||
println!("cargo:rerun-if-changed=style");
|
|
||||||
println!("cargo:rerun-if-changed=images");
|
|
||||||
println!("cargo:rerun-if-changed=script");
|
|
||||||
|
|
||||||
save_asset!("style.css", bundle_style("style/style.css"));
|
|
||||||
save_asset!("upload.js", bundle_script("script/upload.ts"));
|
|
||||||
save_asset!("demo_list.js", bundle_script("script/demo_list.js"));
|
|
||||||
}
|
|
||||||
828
build/Cargo.lock
generated
828
build/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -10,25 +10,8 @@ path = "src/bundle_script.rs"
|
||||||
name = "script"
|
name = "script"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
demostf-build-derive = {version = "0.1", path = "./derive"}
|
||||||
|
demostf-build-bundlers = {version = "0.1", path = "./bundlers"}
|
||||||
|
rand = "0.8.5"
|
||||||
tracing-subscriber = "0.3.16"
|
tracing-subscriber = "0.3.16"
|
||||||
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
|
|
||||||
base64 = "0.21.0"
|
|
||||||
urlencoding = "2.1.2"
|
|
||||||
const-fnv1a-hash = "1.1.0"
|
|
||||||
swc = "0.259.6"
|
|
||||||
swc_common = { version = "0.30.5", features = ["tty-emitter", "concurrent"], path = "../../../rust/swc/crates/swc_common" }
|
|
||||||
#swc_bundler = "0.212.5"
|
|
||||||
swc_bundler = { version = "0.212.5", path = "../../../rust/swc/crates/swc_bundler" }
|
|
||||||
#swc_ecma_loader = "0.42.5"
|
|
||||||
swc_ecma_loader = { version = "0.42.5", path = "../../../rust/swc/crates/swc_ecma_loader", features = ["node", "cache"] }
|
|
||||||
swc_ecma_ast = { version = "0.102.5", path = "../../../rust/swc/crates/swc_ecma_ast" }
|
|
||||||
swc_atoms = { version = "0.4.43", path = "../../../rust/swc/crates/swc_atoms" }
|
|
||||||
swc_ecma_parser = { version = "0.132.6", features = ["typescript"], path = "../../../rust/swc/crates/swc_ecma_parser" }
|
|
||||||
swc_ecma_codegen = { version = "0.137.6", path = "../../../rust/swc/crates/swc_ecma_codegen" }
|
|
||||||
swc_ecma_transforms_base = { version = "0.125.1", path = "../../../rust/swc/crates/swc_ecma_transforms_base" }
|
|
||||||
swc_ecma_transforms_typescript = { version = "0.175.4", path = "../../../rust/swc/crates/swc_ecma_transforms_typescript" }
|
|
||||||
swc_ecma_visit = { version = "0.88.5", path = "../../../rust/swc/crates/swc_ecma_visit" }
|
|
||||||
anyhow = "1.0.70"
|
|
||||||
#jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions" }
|
|
||||||
jsx-dom-expressions = { version = "0.1", path = "../../../rust/swc-plugin-jsx-dom-expressions" }
|
|
||||||
|
|
||||||
|
|
|
||||||
3943
build/bundlers/Cargo.lock
generated
Normal file
3943
build/bundlers/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
29
build/bundlers/Cargo.toml
Normal file
29
build/bundlers/Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "demostf-build-bundlers"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
|
||||||
|
base64 = "0.21.0"
|
||||||
|
urlencoding = "2.1.2"
|
||||||
|
swc = "0.259.6"
|
||||||
|
swc_common = { version = "0.30.5", features = ["tty-emitter", "concurrent"], git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
swc_core = { version = "0.74.6", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
#swc_bundler = "0.212.5"
|
||||||
|
swc_bundler = { version = "0.212.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
#swc_ecma_loader = "0.42.5"
|
||||||
|
swc_ecma_loader = { version = "0.42.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old", features = ["node", "cache"] }
|
||||||
|
swc_ecma_ast = { version = "0.102.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
swc_atoms = { version = "0.4.43", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
swc_ecma_parser = { version = "0.132.6", features = ["typescript"], git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
swc_ecma_codegen = { version = "0.137.6", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
swc_ecma_transforms_base = { version = "0.125.1", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
swc_ecma_transforms_typescript = { version = "0.175.4", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
swc_ecma_visit = { version = "0.88.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||||
|
anyhow = "1.0.70"
|
||||||
|
jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions", branch = "loader-browser-overwrite-old" }
|
||||||
|
#jsx-dom-expressions = { version = "0.1", path = "../../../../rust/swc-plugin-jsx-dom-expressions" }
|
||||||
|
rand = "0.8.5"
|
||||||
|
fnv = "1.0.7"
|
||||||
|
|
||||||
39
build/bundlers/src/lib.rs
Normal file
39
build/bundlers/src/lib.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
mod script;
|
||||||
|
mod style;
|
||||||
|
|
||||||
|
use fnv::FnvHasher;
|
||||||
|
pub use script::bundle_script;
|
||||||
|
use std::fs::read;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
pub use style::bundle_style;
|
||||||
|
|
||||||
|
pub fn bundle_raw(input: &str) -> Vec<u8> {
|
||||||
|
read(input).expect("failed to read raw")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guess_embed(path: &str) -> (&'static str, bool) {
|
||||||
|
match path.split('.').last().unwrap() {
|
||||||
|
"svg" => ("image/svg+xml", false),
|
||||||
|
"png" => ("image/png", true),
|
||||||
|
ext => panic!("no mimetype known for {ext}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash(input: &[u8]) -> String {
|
||||||
|
let mut hasher = FnvHasher::default();
|
||||||
|
input.hash(&mut hasher);
|
||||||
|
format!("{:x}", hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn guess_mime(path: &str) -> &'static str {
|
||||||
|
if path.ends_with("svg") {
|
||||||
|
return "image/svg+xml";
|
||||||
|
} else if path.ends_with("png") {
|
||||||
|
return "image/png";
|
||||||
|
} else if path.ends_with("css") {
|
||||||
|
return "text/css";
|
||||||
|
} else if path.ends_with("js") {
|
||||||
|
return "text/javascript";
|
||||||
|
}
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
|
@ -23,13 +23,13 @@ use swc_ecma_transforms_base::hygiene::hygiene;
|
||||||
use swc_ecma_transforms_typescript::strip;
|
use swc_ecma_transforms_typescript::strip;
|
||||||
use swc_ecma_visit::{as_folder, FoldWith};
|
use swc_ecma_visit::{as_folder, FoldWith};
|
||||||
|
|
||||||
pub fn bundle_script(script: &str) -> String {
|
pub fn bundle_script(script: &str) -> Vec<u8> {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let minify = false;
|
let minify = false;
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let minify = true;
|
let minify = true;
|
||||||
|
|
||||||
let output = GLOBALS.set(&Default::default(), || {
|
GLOBALS.set(&Default::default(), || {
|
||||||
let cm = Arc::<SourceMap>::default();
|
let cm = Arc::<SourceMap>::default();
|
||||||
|
|
||||||
let globals = &Box::default();
|
let globals = &Box::default();
|
||||||
|
|
@ -62,8 +62,7 @@ pub fn bundle_script(script: &str) -> String {
|
||||||
write(minify, cm.clone(), &module.module, &mut buf);
|
write(minify, cm.clone(), &module.module, &mut buf);
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
});
|
})
|
||||||
String::from_utf8(output).expect("invalid utf8 bundle")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(minify: bool, cm: Arc<SourceMap>, module: &Module, out: W) {
|
fn write<W: Write>(minify: bool, cm: Arc<SourceMap>, module: &Module, out: W) {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::guess_mime;
|
use crate::guess_embed;
|
||||||
use base64::engine::general_purpose::STANDARD;
|
use base64::engine::general_purpose::STANDARD;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use lightningcss::bundler::{Bundler, FileProvider};
|
use lightningcss::bundler::{Bundler, FileProvider};
|
||||||
|
|
@ -11,7 +11,7 @@ use std::convert::Infallible;
|
||||||
use std::fs::read;
|
use std::fs::read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub fn bundle_style(style: &str) -> String {
|
pub fn bundle_style(style: &str) -> Vec<u8> {
|
||||||
// todo build time?
|
// todo build time?
|
||||||
let fs = FileProvider::new();
|
let fs = FileProvider::new();
|
||||||
let mut bundler = Bundler::new(
|
let mut bundler = Bundler::new(
|
||||||
|
|
@ -49,6 +49,7 @@ pub fn bundle_style(style: &str) -> String {
|
||||||
})
|
})
|
||||||
.expect("failed to output css")
|
.expect("failed to output css")
|
||||||
.code
|
.code
|
||||||
|
.into_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InlineUrlVisitor;
|
struct InlineUrlVisitor;
|
||||||
|
|
@ -64,7 +65,7 @@ impl<'i> Visitor<'i> for InlineUrlVisitor {
|
||||||
eprintln!("Failed to write inline file {path}: {e}");
|
eprintln!("Failed to write inline file {path}: {e}");
|
||||||
panic!("Failed to inline");
|
panic!("Failed to inline");
|
||||||
});
|
});
|
||||||
let (mime, encode) = guess_mime(path);
|
let (mime, encode) = guess_embed(path);
|
||||||
|
|
||||||
if encode {
|
if encode {
|
||||||
let encoded = STANDARD.encode(content);
|
let encoded = STANDARD.encode(content);
|
||||||
3994
build/derive/Cargo.lock
generated
Normal file
3994
build/derive/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
build/derive/Cargo.toml
Normal file
16
build/derive/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "demostf-build-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "demostf_build_derive"
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "2.0.12"
|
||||||
|
quote = "1.0.26"
|
||||||
|
proc-macro2 = "1.0.54"
|
||||||
|
structmeta = "0.2.0"
|
||||||
|
merge = "0.1.0"
|
||||||
|
demostf-build-bundlers = {version = "0.1", path = "../bundlers"}
|
||||||
203
build/derive/src/asset.rs
Normal file
203
build/derive/src/asset.rs
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
use crate::{err, Derivable, DeriveParams};
|
||||||
|
use demostf_build_bundlers::guess_mime;
|
||||||
|
use merge::Merge;
|
||||||
|
use proc_macro2::{Ident, TokenStream};
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use structmeta::StructMeta;
|
||||||
|
use syn::parse::Parse;
|
||||||
|
use syn::{Attribute, DeriveInput, LitStr, Result};
|
||||||
|
|
||||||
|
pub struct Asset;
|
||||||
|
|
||||||
|
impl Derivable for Asset {
|
||||||
|
type Params = AssetParams;
|
||||||
|
|
||||||
|
fn derive(params: AssetParams) -> Result<TokenStream> {
|
||||||
|
let struct_ident = params.name;
|
||||||
|
let span = struct_ident.span();
|
||||||
|
let input_path = params.source;
|
||||||
|
|
||||||
|
let asset = if params.debug {
|
||||||
|
AssetContent::Runtime {
|
||||||
|
url: input_path.clone(),
|
||||||
|
ty: params.ty,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let content = params.ty.bundle(&input_path);
|
||||||
|
AssetContent::Compiled {
|
||||||
|
route: params.url.clone(),
|
||||||
|
content,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let content = asset.content();
|
||||||
|
let buster = asset.buster();
|
||||||
|
let etag = asset.etag();
|
||||||
|
let url = asset.url();
|
||||||
|
let mime = guess_mime(&input_path);
|
||||||
|
let mime = quote_spanned!(span => #mime);
|
||||||
|
let route = params.url;
|
||||||
|
let route = quote_spanned!(span => #route);
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
quote_spanned!(span => impl demostf_build::Asset for #struct_ident {
|
||||||
|
fn mime() -> &'static str {
|
||||||
|
#mime
|
||||||
|
}
|
||||||
|
fn etag() -> &'static str {
|
||||||
|
#etag
|
||||||
|
}
|
||||||
|
fn cache_buster() -> std::borrow::Cow<'static, str> {
|
||||||
|
#buster
|
||||||
|
}
|
||||||
|
fn content() -> std::borrow::Cow<'static, [u8]> {
|
||||||
|
#content
|
||||||
|
}
|
||||||
|
fn route() -> &'static str {
|
||||||
|
#route
|
||||||
|
}
|
||||||
|
fn url() -> std::borrow::Cow<'static, str> {
|
||||||
|
#url
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AssetParams {
|
||||||
|
name: Ident,
|
||||||
|
debug: bool,
|
||||||
|
source: String,
|
||||||
|
url: String,
|
||||||
|
ty: AssetType,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AssetType {
|
||||||
|
Js,
|
||||||
|
Css,
|
||||||
|
Raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetType {
|
||||||
|
fn from_url(url: &str) -> Self {
|
||||||
|
if url.ends_with("css") {
|
||||||
|
return AssetType::Css;
|
||||||
|
} else if url.ends_with("js") {
|
||||||
|
return AssetType::Js;
|
||||||
|
}
|
||||||
|
return AssetType::Raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bundle_fn(&self) -> TokenStream {
|
||||||
|
match self {
|
||||||
|
AssetType::Css => quote!(bundle_style),
|
||||||
|
AssetType::Js => quote!(bundle_script),
|
||||||
|
AssetType::Raw => quote!(bundle_raw),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bundle(self, url: &str) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
AssetType::Css => demostf_build_bundlers::bundle_style(url),
|
||||||
|
AssetType::Js => demostf_build_bundlers::bundle_script(url),
|
||||||
|
AssetType::Raw => demostf_build_bundlers::bundle_raw(url),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AssetContent {
|
||||||
|
Runtime { url: String, ty: AssetType },
|
||||||
|
Compiled { route: String, content: Vec<u8> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetContent {
|
||||||
|
pub fn content(&self) -> TokenStream {
|
||||||
|
match self {
|
||||||
|
AssetContent::Runtime { url, ty } => {
|
||||||
|
let bundle_fn = ty.bundle_fn();
|
||||||
|
quote!(demostf_build::#bundle_fn(#url).into())
|
||||||
|
}
|
||||||
|
AssetContent::Compiled { content, .. } => {
|
||||||
|
quote!(std::borrow::Cow::Borrowed(&[
|
||||||
|
#(#content,)*
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn buster(&self) -> TokenStream {
|
||||||
|
match self {
|
||||||
|
AssetContent::Runtime { .. } => {
|
||||||
|
quote!(demostf_build::random_cache_buster().into())
|
||||||
|
}
|
||||||
|
AssetContent::Compiled { content, .. } => {
|
||||||
|
let hash = demostf_build_bundlers::hash(&content);
|
||||||
|
quote!(std::borrow::Cow::Borrowed(#hash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn etag(&self) -> TokenStream {
|
||||||
|
match self {
|
||||||
|
AssetContent::Runtime { .. } => {
|
||||||
|
quote!("")
|
||||||
|
}
|
||||||
|
AssetContent::Compiled { content, .. } => {
|
||||||
|
let hash = demostf_build_bundlers::hash(&content);
|
||||||
|
quote!(#hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn url(&self) -> TokenStream {
|
||||||
|
match self {
|
||||||
|
AssetContent::Runtime { .. } => {
|
||||||
|
quote!(format!("{}?v={}", Self::route(), Self::cache_buster()).into())
|
||||||
|
}
|
||||||
|
AssetContent::Compiled { content, route } => {
|
||||||
|
let hash = demostf_build_bundlers::hash(&content);
|
||||||
|
let url = format!("{}?v={}", route, hash);
|
||||||
|
quote!(std::borrow::Cow::Borrowed(#url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeriveParams for AssetParams {
|
||||||
|
fn parse(input: &DeriveInput) -> Result<AssetParams> {
|
||||||
|
let attrs: AssetAttrs = parse_attrs(&input.attrs);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let debug = true;
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let debug = false;
|
||||||
|
let Some(source) = attrs.source else {
|
||||||
|
return err("missing require attribute #[asset(source)]", input);
|
||||||
|
};
|
||||||
|
let source = source.value();
|
||||||
|
let Some(url) = attrs.url else {
|
||||||
|
return err("missing require attribute #[asset(url)]", input);
|
||||||
|
};
|
||||||
|
let url = url.value();
|
||||||
|
let ty = AssetType::from_url(&url);
|
||||||
|
|
||||||
|
Ok(AssetParams {
|
||||||
|
name: input.ident.clone(),
|
||||||
|
debug,
|
||||||
|
source,
|
||||||
|
url,
|
||||||
|
ty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_attrs<T: Parse + Default + Merge>(attrs: &[Attribute]) -> T {
|
||||||
|
let mut result = T::default();
|
||||||
|
for attr in attrs {
|
||||||
|
if let Ok(parsed) = attr.parse_args() {
|
||||||
|
result.merge(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, StructMeta, Merge)]
|
||||||
|
struct AssetAttrs {
|
||||||
|
source: Option<LitStr>,
|
||||||
|
url: Option<LitStr>,
|
||||||
|
}
|
||||||
43
build/derive/src/lib.rs
Normal file
43
build/derive/src/lib.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
//! Derive macros for tf-log-parser
|
||||||
|
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
mod asset;
|
||||||
|
|
||||||
|
use crate::asset::Asset;
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::ToTokens;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use syn::{parse_macro_input, DeriveInput, Error, Result};
|
||||||
|
|
||||||
|
/// Derive the `Asset` trait for a struct
|
||||||
|
#[proc_macro_derive(Asset, attributes(asset))]
|
||||||
|
pub fn derive_asset(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let expanded = derive_trait::<Asset>(parse_macro_input!(input as DeriveInput));
|
||||||
|
|
||||||
|
proc_macro::TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic wrapper for error handling
|
||||||
|
fn derive_trait<Trait: Derivable>(input: DeriveInput) -> TokenStream {
|
||||||
|
derive_trait_inner::<Trait>(input).unwrap_or_else(|err| err.into_compile_error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_trait_inner<Trait: Derivable>(input: DeriveInput) -> Result<TokenStream> {
|
||||||
|
let params = Trait::Params::parse(&input)?;
|
||||||
|
Trait::derive(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Derivable {
|
||||||
|
type Params: DeriveParams;
|
||||||
|
|
||||||
|
fn derive(params: Self::Params) -> Result<TokenStream>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait DeriveParams: Sized {
|
||||||
|
fn parse(input: &DeriveInput) -> Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err<R, T: ToTokens, U: Display>(msg: U, span: T) -> Result<R> {
|
||||||
|
return Err(Error::new_spanned(&span, msg));
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
|
|
||||||
mod script;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let path = args().skip(1).next().unwrap();
|
let path = args().skip(1).next().unwrap();
|
||||||
let output = script::bundle_script(&path);
|
let output = demostf_build::bundle_script(&path);
|
||||||
// println!("{output}")
|
// println!("{output}")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,23 @@
|
||||||
mod script;
|
pub use demostf_build_bundlers::bundle_raw;
|
||||||
mod style;
|
pub use demostf_build_bundlers::bundle_script;
|
||||||
|
pub use demostf_build_bundlers::bundle_style;
|
||||||
|
pub use demostf_build_derive::Asset;
|
||||||
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use const_fnv1a_hash::fnv1a_hash_str_32;
|
pub trait Asset {
|
||||||
pub use script::bundle_script;
|
fn mime() -> &'static str;
|
||||||
pub use style::bundle_style;
|
fn cache_buster() -> Cow<'static, str>;
|
||||||
|
fn etag() -> &'static str;
|
||||||
#[macro_export]
|
fn content() -> Cow<'static, [u8]>;
|
||||||
macro_rules! save_asset {
|
fn url() -> Cow<'static, str>;
|
||||||
($name:expr, $val:expr) => {
|
fn route() -> &'static str;
|
||||||
let val = $val;
|
|
||||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
|
||||||
std::fs::write(format!("{out_dir}/{}", $name), &val).expect("failed to write asset");
|
|
||||||
let hash = demostf_build::hash(&val);
|
|
||||||
std::fs::write(format!("{out_dir}/{}.hash", $name), hash)
|
|
||||||
.expect("failed to write asset hash");
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash(data: &str) -> String {
|
pub fn random_cache_buster() -> String {
|
||||||
format!("{:x}", fnv1a_hash_str_32(data))
|
rand::thread_rng()
|
||||||
}
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(8)
|
||||||
fn guess_mime(path: &str) -> (&'static str, bool) {
|
.map(char::from)
|
||||||
match path.split('.').last().unwrap() {
|
.collect()
|
||||||
"svg" => ("image/svg+xml", false),
|
|
||||||
"png" => ("image/png", true),
|
|
||||||
ext => panic!("no mimetype known for {ext}"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
74
src/asset.rs
74
src/asset.rs
|
|
@ -1,26 +1,8 @@
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use demostf_build::Asset;
|
||||||
use hyper::header::{CACHE_CONTROL, CONTENT_TYPE, ETAG};
|
use hyper::header::{CACHE_CONTROL, CONTENT_TYPE, ETAG};
|
||||||
use hyper::http::{HeaderName, HeaderValue};
|
use hyper::http::{HeaderName, HeaderValue};
|
||||||
|
|
||||||
macro_rules! saved_asset {
|
|
||||||
($name:expr) => {
|
|
||||||
include_str!(concat!(env!("OUT_DIR"), "/", $name))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
macro_rules! saved_asset_url {
|
|
||||||
($name:expr) => {
|
|
||||||
concat!("/", $name, "?v=", crate::asset::saved_asset_hash!($name))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! saved_asset_hash {
|
|
||||||
($name:expr) => {
|
|
||||||
include_str!(concat!(env!("OUT_DIR"), "/", $name, ".hash"))
|
|
||||||
};
|
|
||||||
($name:expr, quoted) => {
|
|
||||||
concat!(r#"""#, crate::asset::saved_asset_hash!($name), r#"""#)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn cache_headers(
|
pub const fn cache_headers(
|
||||||
content_type: &'static str,
|
content_type: &'static str,
|
||||||
etag: &'static str,
|
etag: &'static str,
|
||||||
|
|
@ -35,51 +17,9 @@ pub const fn cache_headers(
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn guess_mime(path: &'static str) -> &'static str {
|
pub async fn serve_asset<A: Asset>() -> impl IntoResponse {
|
||||||
use const_str::ends_with;
|
let mime = A::mime();
|
||||||
if ends_with!(path, "svg") {
|
let style = A::content();
|
||||||
return "image/svg+xml";
|
let etag = A::etag();
|
||||||
} else if ends_with!(path, "png") {
|
(cache_headers(mime, etag), style.into_owned())
|
||||||
return "image/png";
|
|
||||||
} else if ends_with!(path, "css") {
|
|
||||||
return "text/css";
|
|
||||||
} else if ends_with!(path, "js") {
|
|
||||||
return "text/javascript";
|
|
||||||
}
|
}
|
||||||
return "text/plain";
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! serve_static {
|
|
||||||
($name:expr) => {
|
|
||||||
|| async {
|
|
||||||
const CONTENT: &[u8] = include_bytes!($name);
|
|
||||||
const HASH: u32 = const_fnv1a_hash::fnv1a_hash_32(&CONTENT, None);
|
|
||||||
const HASH_S: const_base::ArrayStr<8> = const_base::WrongOutputLength::unwrap(
|
|
||||||
const_base::encode(&HASH.to_le_bytes(), const_base::Config::HEX),
|
|
||||||
);
|
|
||||||
(
|
|
||||||
crate::asset::cache_headers(crate::asset::guess_mime($name), HASH_S.as_str()),
|
|
||||||
CONTENT,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! serve_compiled {
|
|
||||||
($name:expr) => {
|
|
||||||
|| async {
|
|
||||||
let style = crate::asset::saved_asset!($name);
|
|
||||||
let etag = crate::asset::saved_asset_hash!($name, quoted);
|
|
||||||
(
|
|
||||||
crate::asset::cache_headers(crate::asset::guess_mime($name), etag),
|
|
||||||
style,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use saved_asset;
|
|
||||||
pub(crate) use saved_asset_hash;
|
|
||||||
pub(crate) use saved_asset_url;
|
|
||||||
pub(crate) use serve_compiled;
|
|
||||||
pub(crate) use serve_static;
|
|
||||||
|
|
|
||||||
26
src/main.rs
26
src/main.rs
|
|
@ -5,6 +5,7 @@ mod error;
|
||||||
mod pages;
|
mod pages;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
|
use crate::asset::serve_asset;
|
||||||
pub use crate::config::Config;
|
pub use crate::config::Config;
|
||||||
use crate::config::Listen;
|
use crate::config::Listen;
|
||||||
use crate::data::demo::{Demo, ListDemo};
|
use crate::data::demo::{Demo, ListDemo};
|
||||||
|
|
@ -13,11 +14,10 @@ use crate::data::steam_id::SteamId;
|
||||||
use crate::data::user::User;
|
use crate::data::user::User;
|
||||||
use crate::pages::about::AboutPage;
|
use crate::pages::about::AboutPage;
|
||||||
use crate::pages::demo::DemoPage;
|
use crate::pages::demo::DemoPage;
|
||||||
use crate::pages::index::Index;
|
use crate::pages::index::{DemoListScript, Index};
|
||||||
use crate::pages::render;
|
use crate::pages::upload::{UploadPage, UploadScript};
|
||||||
use crate::pages::upload::UploadPage;
|
use crate::pages::{render, GlobalStyle};
|
||||||
use crate::session::{SessionData, COOKIE_NAME};
|
use crate::session::{SessionData, COOKIE_NAME};
|
||||||
use asset::{serve_compiled, serve_static};
|
|
||||||
use async_session::{MemoryStore, Session, SessionStore};
|
use async_session::{MemoryStore, Session, SessionStore};
|
||||||
use axum::extract::{MatchedPath, Path, RawQuery};
|
use axum::extract::{MatchedPath, Path, RawQuery};
|
||||||
use axum::headers::Cookie;
|
use axum::headers::Cookie;
|
||||||
|
|
@ -25,6 +25,7 @@ use axum::http::header::{LOCATION, SET_COOKIE};
|
||||||
use axum::http::{HeaderValue, Request, StatusCode};
|
use axum::http::{HeaderValue, Request, StatusCode};
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::{extract::State, routing::get, Router, Server, TypedHeader};
|
use axum::{extract::State, routing::get, Router, Server, TypedHeader};
|
||||||
|
use demostf_build::Asset;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
use hyperlocal::UnixServerExt;
|
use hyperlocal::UnixServerExt;
|
||||||
use maud::Markup;
|
use maud::Markup;
|
||||||
|
|
@ -50,6 +51,13 @@ struct App {
|
||||||
pub session_store: MemoryStore,
|
pub session_store: MemoryStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Asset)]
|
||||||
|
#[asset(source = "images/logo.png", url = "/images/logo.png")]
|
||||||
|
struct LogoPng;
|
||||||
|
#[derive(Asset)]
|
||||||
|
#[asset(source = "images/logo.svg", url = "/images/logo.svg")]
|
||||||
|
struct LogoSvg;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
|
|
@ -75,11 +83,11 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
.route("/style.css", get(serve_compiled!("style.css")))
|
.route(GlobalStyle::route(), get(serve_asset::<GlobalStyle>))
|
||||||
.route("/upload.js", get(serve_compiled!("upload.js")))
|
.route(UploadScript::route(), get(serve_asset::<UploadScript>))
|
||||||
.route("/demo_list.js", get(serve_compiled!("demo_list.js")))
|
.route(DemoListScript::route(), get(serve_asset::<DemoListScript>))
|
||||||
.route("/images/logo.png", get(serve_static!("../images/logo.png")))
|
.route(LogoPng::route(), get(serve_asset::<LogoPng>))
|
||||||
.route("/images/logo.svg", get(serve_static!("../images/logo.svg")))
|
.route(LogoSvg::route(), get(serve_asset::<LogoSvg>))
|
||||||
.route("/about", get(about))
|
.route("/about", get(about))
|
||||||
.route("/login/callback", get(login_callback))
|
.route("/login/callback", get(login_callback))
|
||||||
.route("/login", get(login))
|
.route("/login", get(login))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::asset::saved_asset_url;
|
|
||||||
use crate::data::demo::ListDemo;
|
use crate::data::demo::ListDemo;
|
||||||
use crate::pages::Page;
|
use crate::pages::Page;
|
||||||
|
use demostf_build::Asset;
|
||||||
use maud::{html, Markup, Render};
|
use maud::{html, Markup, Render};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -10,13 +10,17 @@ pub struct Index<'a> {
|
||||||
pub api: &'a str,
|
pub api: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Asset)]
|
||||||
|
#[asset(source = "script/demo_list.js", url = "/demo_list.js")]
|
||||||
|
pub struct DemoListScript;
|
||||||
|
|
||||||
impl Page for Index<'_> {
|
impl Page for Index<'_> {
|
||||||
fn title(&self) -> Cow<'static, str> {
|
fn title(&self) -> Cow<'static, str> {
|
||||||
"Demos - demos.tf".into()
|
"Demos - demos.tf".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self) -> Markup {
|
fn render(&self) -> Markup {
|
||||||
let script = saved_asset_url!("demo_list.js");
|
let script = DemoListScript::url();
|
||||||
html! {
|
html! {
|
||||||
h1 { "Demos" }
|
h1 { "Demos" }
|
||||||
#filter-bar data-maps = (MapList(&self.maps)) data-api-base = (self.api) {}
|
#filter-bar data-maps = (MapList(&self.maps)) data-api-base = (self.api) {}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ pub mod index;
|
||||||
mod plugin_section;
|
mod plugin_section;
|
||||||
pub mod upload;
|
pub mod upload;
|
||||||
|
|
||||||
use crate::asset::saved_asset_url;
|
|
||||||
use crate::session::SessionData;
|
use crate::session::SessionData;
|
||||||
|
use demostf_build::Asset;
|
||||||
use maud::{html, Markup, DOCTYPE};
|
use maud::{html, Markup, DOCTYPE};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -14,8 +14,12 @@ pub trait Page {
|
||||||
fn render(&self) -> Markup;
|
fn render(&self) -> Markup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Asset)]
|
||||||
|
#[asset(source = "style/style.css", url = "/style.css")]
|
||||||
|
pub struct GlobalStyle;
|
||||||
|
|
||||||
pub fn render<T: Page>(page: T, session: SessionData) -> Markup {
|
pub fn render<T: Page>(page: T, session: SessionData) -> Markup {
|
||||||
let style_url = saved_asset_url!("style.css");
|
let style_url = GlobalStyle::url();
|
||||||
html! {
|
html! {
|
||||||
(DOCTYPE)
|
(DOCTYPE)
|
||||||
html {
|
html {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::asset::saved_asset_url;
|
|
||||||
use crate::pages::plugin_section::PluginSection;
|
use crate::pages::plugin_section::PluginSection;
|
||||||
use crate::pages::Page;
|
use crate::pages::Page;
|
||||||
|
use demostf_build::Asset;
|
||||||
use maud::{html, Markup};
|
use maud::{html, Markup};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -17,13 +17,17 @@ impl<'a> UploadPage<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Asset)]
|
||||||
|
#[asset(source = "script/upload.ts", url = "/upload.js")]
|
||||||
|
pub struct UploadScript;
|
||||||
|
|
||||||
impl Page for UploadPage<'_> {
|
impl Page for UploadPage<'_> {
|
||||||
fn title(&self) -> Cow<'static, str> {
|
fn title(&self) -> Cow<'static, str> {
|
||||||
"Upload - demos.tf".into()
|
"Upload - demos.tf".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self) -> Markup {
|
fn render(&self) -> Markup {
|
||||||
let script = saved_asset_url!("upload.js");
|
let script = UploadScript::url();
|
||||||
html! {
|
html! {
|
||||||
.upload-page {
|
.upload-page {
|
||||||
section.upload {
|
section.upload {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
@import '../node_modules/@thisbeyond/solid-select/dist/esm/style.css';
|
@import '../node_modules/@thisbeyond/solid-select/dist/esm/style.css';
|
||||||
|
|
||||||
|
#filter-bar {
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
.filter-bar {
|
.filter-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue