dynamic/static embeds

This commit is contained in:
Robin Appelman 2023-04-12 21:08:09 +02:00
commit cb56c80555
22 changed files with 9068 additions and 665 deletions

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
View 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
View 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
View 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));
}