Hashless assets (#4312)

* add support for external assets

* fix playwright tests

* add more documentation to with_hash_suffix

* restore external asset hashing tests

* Update gtk workflow dependencies

* fix doc test

* multiple versions of libgtk

* clear dx in playwright tests

* fix fullstack caching test

* try without apt cache

* cache everything except libxdo-dev

* revert ci changes

* fix wasm asset options

* fix merge conflict

* recommend the used attribute instead

* Move options to AssetOptionsBuilder

* Don't deprecate non-canonical new asset options functions

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-06-30 19:21:49 -07:00
1 parent 0bd376b873
commit e74c838441
22 files changed
+569 -316

No files matched your search

+140 -20
View File
@@ -17,9 +17,148 @@ use crate::{
serde::Serialize,
serde::Deserialize,
)]
#[non_exhaustive]
pub struct AssetOptions {
/// If a hash should be added to the asset path
pub(crate) add_hash: bool,
/// The variant of the asset
pub(crate) variant: AssetVariant,
}
impl AssetOptions {
/// Create a new asset options builder
pub const fn builder() -> AssetOptionsBuilder<()> {
AssetOptionsBuilder::new()
}
/// Get the variant of the asset
pub const fn variant(&self) -> &AssetVariant {
&self.variant
}
/// Check if a hash should be added to the asset path
pub const fn hash_suffix(&self) -> bool {
self.add_hash
}
/// Try to get the extension for the asset. If the asset options don't define an extension, this will return None
pub const fn extension(&self) -> Option<&'static str> {
match self.variant {
AssetVariant::Image(image) => image.extension(),
AssetVariant::Css(_) => Some("css"),
AssetVariant::CssModule(_) => Some("css"),
AssetVariant::Js(_) => Some("js"),
AssetVariant::Folder(_) => None,
AssetVariant::Unknown => None,
}
}
/// Convert the options into options for a generic asset
pub const fn into_asset_options(self) -> AssetOptions {
self
}
}
/// A builder for [`AssetOptions`]
///
/// ```rust
/// # use manganis::AssetOptionsBuilder;
/// static ASSET: Asset = asset!(
/// "image.png",
/// AssetOptionsBuilder::new()
/// .with_hash_suffix(false)
/// );
/// ```
pub struct AssetOptionsBuilder<T> {
/// If a hash should be added to the asset path
pub(crate) add_hash: bool,
/// The variant of the asset
pub(crate) variant: T,
}
impl Default for AssetOptionsBuilder<()> {
fn default() -> Self {
Self::default()
}
}
impl AssetOptionsBuilder<()> {
/// Create a new asset options builder with an unknown variant
pub const fn new() -> Self {
Self {
add_hash: true,
variant: (),
}
}
/// Create a default asset options builder
pub const fn default() -> Self {
Self::new()
}
/// Convert the builder into asset options with the given variant
pub const fn into_asset_options(self) -> AssetOptions {
AssetOptions {
add_hash: self.add_hash,
variant: AssetVariant::Unknown,
}
}
}
impl<T> AssetOptionsBuilder<T> {
/// Create a new asset options builder with the given variant
pub(crate) const fn variant(variant: T) -> Self {
Self {
add_hash: true,
variant,
}
}
/// Set whether a hash should be added to the asset path. Manganis adds hashes to asset paths by default
/// for [cache busting](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching#cache_busting).
/// With hashed assets, you can serve the asset with a long expiration time, and when the asset changes,
/// the hash in the path will change, causing the browser to fetch the new version.
///
/// This method will only effect if the hash is added to the bundled asset path. If you are using the asset
/// macro, the asset struct still needs to be used in your rust code to ensure the asset is included in the binary.
///
/// <div class="warning">
///
/// If you are using an asset outside of rust code where you know what the asset hash will be, you must use the
/// `#[used]` attribute to ensure the asset is included in the binary even if it is not referenced in the code.
///
/// ```rust
/// #[used]
/// static ASSET: manganis::Asset = manganis::asset!(
/// "path/to/asset.png",
/// AssetVariant::Unknown.into_asset_options()
/// .with_hash_suffix(false)
/// );
/// ```
///
/// </div>
pub const fn with_hash_suffix(mut self, add_hash: bool) -> Self {
self.add_hash = add_hash;
self
}
}
/// Settings for a specific type of asset
#[derive(
Debug,
Eq,
PartialEq,
PartialOrd,
Clone,
Copy,
Hash,
SerializeConst,
serde::Serialize,
serde::Deserialize,
)]
#[repr(C, u8)]
#[non_exhaustive]
pub enum AssetOptions {
pub enum AssetVariant {
/// An image asset
Image(ImageAssetOptions),
/// A folder asset
@@ -33,22 +172,3 @@ pub enum AssetOptions {
/// An unknown asset
Unknown,
}
impl AssetOptions {
/// Try to get the extension for the asset. If the asset options don't define an extension, this will return None
pub const fn extension(&self) -> Option<&'static str> {
match self {
AssetOptions::Image(image) => image.extension(),
AssetOptions::Css(_) => Some("css"),
AssetOptions::CssModule(_) => Some("css"),
AssetOptions::Js(_) => Some("js"),
AssetOptions::Folder(_) => None,
AssetOptions::Unknown => None,
}
}
/// Convert the options into options for a generic asset
pub const fn into_asset_options(self) -> Self {
self
}
}