Files
dioxus/packages/manganis/manganis-macro/src/asset.rs
T
Miles MurgawandJonathan Kelley e96550c48c Manganis CSS Modules Support (#3578)
* feat: type safe css selectors

* feat: css modules support

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
2025-04-15 14:44:22 -07:00

128 lines
4.7 KiB
Rust

use crate::{resolve_path, AssetParseError};
use macro_string::MacroString;
use manganis_core::hash::AssetHash;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use std::path::PathBuf;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned as _,
Token,
};
pub struct AssetParser {
/// The token(s) of the source string, for error reporting
pub(crate) path_expr: proc_macro2::TokenStream,
/// The asset itself
pub(crate) asset: Result<PathBuf, AssetParseError>,
/// The source of the trailing options
pub(crate) options: TokenStream2,
}
impl Parse for AssetParser {
// we can take
//
// This gives you the Asset type - it's generic and basically unrefined
// ```
// asset!("/assets/myfile.png")
// ```
//
// To narrow the type, use a method call to get the refined type
// ```
// asset!(
// "/assets/myfile.png",
// ImageAssetOptions::new()
// .format(ImageFormat::Jpg)
// .size(512, 512)
// )
// ```
//
// But we need to decide the hint first before parsing the options
fn parse(input: ParseStream) -> syn::Result<Self> {
// And then parse the options
let (MacroString(src), path_expr) = input.call(crate::parse_with_tokens)?;
let asset = resolve_path(&src);
let _comma = input.parse::<Token![,]>();
let options = input.parse()?;
Ok(Self {
path_expr,
asset,
options,
})
}
}
impl ToTokens for AssetParser {
// The manganis macro outputs info to two different places:
// 1) The crate the macro was invoked in
// - It needs the hashed contents of the file, the file path, and the file options
// - Most of this is just forwarding the input, the only thing that the macro needs to do is hash the file contents
// 2) A bundler that supports manganis (currently just dioxus-cli)
// - The macro needs to output the absolute path to the asset for the bundler to find later
// - It also needs to serialize the bundled asset along with the asset options for the bundler to use later
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let asset = match self.asset.as_ref() {
Ok(asset) => asset,
Err(err) => {
let err = err.to_string();
tokens.append_all(quote! { compile_error!(#err) });
return;
}
};
let asset_str = asset.to_string_lossy();
let mut asset_str = proc_macro2::Literal::string(&asset_str);
asset_str.set_span(self.path_expr.span());
let hash = match AssetHash::hash_file_contents(asset) {
Ok(hash) => hash,
Err(err) => {
let err = err.to_string();
tokens.append_all(quote! { compile_error!(#err) });
return;
}
};
let hash = hash.bytes();
// Generate the link section for the asset
// The link section includes the source path and the output path of the asset
let link_section = crate::generate_link_section(quote!(__ASSET));
// generate the asset::new method to deprecate the `./assets/blah.css` syntax
let constructor = if asset.is_relative() {
quote::quote! { create_bundled_asset_relative }
} else {
quote::quote! { create_bundled_asset }
};
let options = if self.options.is_empty() {
quote! { manganis::AssetOptions::Unknown }
} else {
self.options.clone()
};
tokens.extend(quote! {
{
// We keep a hash of the contents of the asset for cache busting
const __ASSET_HASH: &[u8] = &[#(#hash),*];
// The source is used by the CLI to copy the asset
const __ASSET_SOURCE_PATH: &'static str = #asset_str;
// The options give the CLI info about how to process the asset
// Note: into_asset_options is not a trait, so we cannot accept the options directly
// in the constructor. Stable rust doesn't have support for constant functions in traits
const __ASSET_OPTIONS: manganis::AssetOptions = #options.into_asset_options();
// Create the asset that the crate will use. This is used both in the return value and
// added to the linker for the bundler to copy later
const __ASSET: manganis::BundledAsset = manganis::macro_helpers::#constructor(__ASSET_SOURCE_PATH, __ASSET_HASH, __ASSET_OPTIONS);
#link_section
manganis::Asset::new(__ASSET, __keep_link_section)
}
})
}
}