Restore manganis optimizations (#3195)
* Create a crate for constant serialization of config structs for manganis * use SerializeConst for the image asset builder * switch to a declarative macro for assets * add serializable options for each asset type * implement the new manganis macro * optimize assets in the CLI * Fix assets with dioxus formattable * reduce fuzzing test limits * use the new syntax in examples * fix avif support * Fix manganis doc tests * cache asset optimization * Split out asset and bundled asset * make hash pointer width independent * Fix manganis macro docs * add constvec::is_empty method to fix clippy lint * remove nasm feature * simplify test_rsplit_once test so typos passes * revert example change from clippy fix * always accept unix paths * fix windows path seperator * fix folder asset hash * Optimize assets in a blocking task * Fix asset options docs * Document Asset and BundledAsset * simplify the linker macro a bit * add more docs to AssetParser expansion * add image format helpers * Split by both unix and windows path separators and take the smallest one
This commit is contained in:
1 parent
31d72db82d
commit
f9617c8d33
64 files changed
+6233
-704
No files matched your search
@@ -1,118 +1,156 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
path::PathBuf,
|
||||
time::SystemTime,
|
||||
};
|
||||
use crate::AssetOptions;
|
||||
use const_serialize::{ConstStr, SerializeConst};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// The location we'll write to the link section - needs to be serializable
|
||||
///
|
||||
/// This basically is 1:1 with `manganis/Asset` but with more metadata to be useful to the macro and cli
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct ResourceAsset {
|
||||
/// The input path `/assets/blah.css`
|
||||
pub input: PathBuf,
|
||||
|
||||
/// The canonicalized asset
|
||||
///
|
||||
/// `Users/dioxus/dev/app/assets/blah.css`
|
||||
pub absolute: PathBuf,
|
||||
|
||||
/// The post-bundle name of the asset - do we include the `assets` name?
|
||||
///
|
||||
/// `blahcss123.css`
|
||||
pub bundled: String,
|
||||
/// An asset that should be copied by the bundler with some options. This type will be
|
||||
/// serialized into the binary and added to the link section [`LinkSection::CURRENT`](crate::linker::LinkSection::CURRENT).
|
||||
/// CLIs that support manganis, should pull out the assets from the link section, optimize,
|
||||
/// and write them to the filesystem at [`BundledAsset::bundled_path`] for the application
|
||||
/// to use.
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Clone,
|
||||
Copy,
|
||||
Hash,
|
||||
SerializeConst,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub struct BundledAsset {
|
||||
/// The absolute path of the asset
|
||||
absolute_source_path: ConstStr,
|
||||
/// The bundled path of the asset
|
||||
bundled_path: ConstStr,
|
||||
/// The options for the asset
|
||||
options: AssetOptions,
|
||||
}
|
||||
|
||||
impl ResourceAsset {
|
||||
pub fn parse_any(raw: &str) -> Result<Self, AssetParseError> {
|
||||
// get the location where the asset is absolute, relative to
|
||||
//
|
||||
// IE
|
||||
// /users/dioxus/dev/app/
|
||||
// is the root of
|
||||
// /users/dioxus/dev/app/assets/blah.css
|
||||
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap();
|
||||
|
||||
// 1. the input file should be a pathbuf
|
||||
let input = PathBuf::from(raw);
|
||||
|
||||
// 2. absolute path to the asset
|
||||
let absolute = manifest_dir.join(raw.trim_start_matches('/'));
|
||||
let absolute =
|
||||
absolute
|
||||
.canonicalize()
|
||||
.map_err(|err| AssetParseError::AssetDoesntExist {
|
||||
err,
|
||||
path: absolute,
|
||||
})?;
|
||||
|
||||
// 3. the bundled path is the unique name of the asset
|
||||
let bundled = Self::make_unique_name(absolute.clone())?;
|
||||
|
||||
Ok(Self {
|
||||
input,
|
||||
absolute,
|
||||
bundled,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_unique_name(file_path: PathBuf) -> Result<String, AssetParseError> {
|
||||
// Create a hasher
|
||||
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
||||
|
||||
// Open the file to get its options
|
||||
let file = std::fs::File::open(&file_path).map_err(AssetParseError::FailedToReadAsset)?;
|
||||
let modified = file
|
||||
.metadata()
|
||||
.and_then(|metadata| metadata.modified())
|
||||
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||
|
||||
// Hash a bunch of metadata
|
||||
// name, options, modified time, and maybe the version of our crate
|
||||
// Hash the last time the file was updated and the file source. If either of these change, we need to regenerate the unique name
|
||||
modified.hash(&mut hash);
|
||||
file_path.hash(&mut hash);
|
||||
|
||||
let uuid = hash.finish();
|
||||
let file_name = file_path
|
||||
.file_stem()
|
||||
.expect("file_path should have a file_stem")
|
||||
.to_string_lossy();
|
||||
|
||||
let extension = file_path
|
||||
.extension()
|
||||
.map(|f| f.to_string_lossy())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(format!("{file_name}-{uuid:x}.{extension}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AssetParseError {
|
||||
ParseError(String),
|
||||
AssetDoesntExist {
|
||||
err: std::io::Error,
|
||||
path: std::path::PathBuf,
|
||||
},
|
||||
FailedToReadAsset(std::io::Error),
|
||||
FailedToReadMetadata(std::io::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AssetParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AssetParseError::ParseError(err) => write!(f, "Failed to parse asset: {}", err),
|
||||
AssetParseError::AssetDoesntExist { err, path } => {
|
||||
write!(f, "Asset at {} doesn't exist: {}", path.display(), err)
|
||||
}
|
||||
AssetParseError::FailedToReadAsset(err) => write!(f, "Failed to read asset: {}", err),
|
||||
AssetParseError::FailedToReadMetadata(err) => {
|
||||
write!(f, "Failed to read asset metadata: {}", err)
|
||||
}
|
||||
impl BundledAsset {
|
||||
#[doc(hidden)]
|
||||
/// This should only be called from the macro
|
||||
/// Create a new asset
|
||||
pub const fn new(
|
||||
absolute_source_path: &'static str,
|
||||
bundled_path: &'static str,
|
||||
options: AssetOptions,
|
||||
) -> Self {
|
||||
Self {
|
||||
absolute_source_path: ConstStr::new(absolute_source_path),
|
||||
bundled_path: ConstStr::new(bundled_path),
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// This should only be called from the macro
|
||||
/// Create a new asset but with a relative path
|
||||
///
|
||||
/// This method is deprecated and will be removed in a future release.
|
||||
#[deprecated(
|
||||
note = "Relative asset!() paths are not supported. Use a path like `/assets/myfile.png` instead of `./assets/myfile.png`"
|
||||
)]
|
||||
pub const fn new_relative(
|
||||
absolute_source_path: &'static str,
|
||||
bundled_path: &'static str,
|
||||
options: AssetOptions,
|
||||
) -> Self {
|
||||
Self::new(absolute_source_path, bundled_path, options)
|
||||
}
|
||||
|
||||
/// Get the bundled name of the asset. This identifier cannot be used to read the asset directly
|
||||
pub fn bundled_path(&self) -> &str {
|
||||
self.bundled_path.as_str()
|
||||
}
|
||||
|
||||
/// Get the absolute path of the asset source. This path will not be available when the asset is bundled
|
||||
pub fn absolute_source_path(&self) -> &str {
|
||||
self.absolute_source_path.as_str()
|
||||
}
|
||||
/// Get the options for the asset
|
||||
pub const fn options(&self) -> &AssetOptions {
|
||||
&self.options
|
||||
}
|
||||
}
|
||||
|
||||
/// A bundled asset with some options. The asset can be used in rsx! to reference the asset.
|
||||
/// It should not be read directly with [`std::fs::read`] because the path needs to be resolved
|
||||
/// relative to the bundle
|
||||
///
|
||||
/// ```rust
|
||||
/// # use manganis::{asset, Asset};
|
||||
/// # use dioxus::prelude::*;
|
||||
/// const ASSET: Asset = asset!("/assets/image.png");
|
||||
/// rsx! {
|
||||
/// img { src: ASSET }
|
||||
/// };
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct Asset {
|
||||
/// The bundled asset
|
||||
bundled: BundledAsset,
|
||||
/// The link section for the asset
|
||||
keep_link_section: fn() -> u8,
|
||||
}
|
||||
|
||||
impl Asset {
|
||||
#[doc(hidden)]
|
||||
/// This should only be called from the macro
|
||||
/// Create a new asset from the bundled form of the asset and the link section
|
||||
pub const fn new(bundled: BundledAsset, keep_link_section: fn() -> u8) -> Self {
|
||||
Self {
|
||||
bundled,
|
||||
keep_link_section,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the bundled asset
|
||||
pub const fn bundled(&self) -> &BundledAsset {
|
||||
&self.bundled
|
||||
}
|
||||
|
||||
/// Return a canonicalized path to the asset
|
||||
///
|
||||
/// Attempts to resolve it against an `assets` folder in the current directory.
|
||||
/// If that doesn't exist, it will resolve against the cargo manifest dir
|
||||
pub fn resolve(&self) -> PathBuf {
|
||||
// Force a volatile read of the asset link section to ensure the symbol makes it into the binary
|
||||
(self.keep_link_section)();
|
||||
|
||||
#[cfg(feature = "dioxus")]
|
||||
// If the asset is relative, we resolve the asset at the current directory
|
||||
if !dioxus_core_types::is_bundled_app() {
|
||||
return PathBuf::from(self.bundled.absolute_source_path.as_str());
|
||||
}
|
||||
|
||||
// Otherwise presumably we're bundled and we can use the bundled path
|
||||
PathBuf::from("/assets/").join(PathBuf::from(
|
||||
self.bundled.bundled_path.as_str().trim_start_matches('/'),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Asset> for String {
|
||||
fn from(value: Asset) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
impl From<Asset> for Option<String> {
|
||||
fn from(value: Asset) -> Self {
|
||||
Some(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Asset {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.resolve().display())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dioxus")]
|
||||
impl dioxus_core_types::DioxusFormattable for Asset {
|
||||
fn format(&self) -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Owned(self.to_string())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user