Deprecate relative asset paths, better warnings for asset!() (#3214)

* Deprecate relative asset paths

* fix paths, better warnings in asset parser
This commit is contained in:
Jonathan Kelley 2024-11-13 19:52:23 -05:00 committed by GitHub
parent 8a2922c663
commit ba4389567d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 95 additions and 36 deletions

View file

@ -5,7 +5,7 @@ pub(crate) fn ChildrenOrLoading(children: Element) -> Element {
rsx! { rsx! {
document::Link { document::Link {
rel: "stylesheet", rel: "stylesheet",
href: asset!("./public/loading.css") href: asset!("/public/loading.css")
} }
SuspenseBoundary { SuspenseBoundary {
fallback: |context: SuspenseContext| { fallback: |context: SuspenseContext| {

View file

@ -19,7 +19,7 @@ fn main() {
rsx! { rsx! {
document::Link { document::Link {
rel: "stylesheet", rel: "stylesheet",
href: asset!("./public/tailwind.css") href: asset!("/public/tailwind.css")
} }
ChildrenOrLoading { ChildrenOrLoading {

View file

@ -23,7 +23,7 @@ fn app() -> Element {
rsx! { rsx! {
document::Link { document::Link {
rel: "stylesheet", rel: "stylesheet",
href: asset!("./assets/fileexplorer.css") href: asset!("/assets/fileexplorer.css")
} }
div { div {
document::Link { href: "https://fonts.googleapis.com/icon?family=Material+Icons", rel: "stylesheet" } document::Link { href: "https://fonts.googleapis.com/icon?family=Material+Icons", rel: "stylesheet" }

View file

@ -36,7 +36,7 @@ pub fn App() -> Element {
#[component] #[component]
fn Homepage(story: ReadOnlySignal<PreviewState>) -> Element { fn Homepage(story: ReadOnlySignal<PreviewState>) -> Element {
rsx! { rsx! {
document::Link { rel: "stylesheet", href: asset!("./assets/hackernews.css") } document::Link { rel: "stylesheet", href: asset!("/assets/hackernews.css") }
div { display: "flex", flex_direction: "row", width: "100%", div { display: "flex", flex_direction: "row", width: "100%",
div { div {
width: "50%", width: "50%",

View file

@ -31,7 +31,7 @@ fn app() -> Element {
rsx! { rsx! {
document::Link { document::Link {
rel: "stylesheet", rel: "stylesheet",
href: asset!("./examples/assets/calculator.css"), href: asset!("/examples/assets/calculator.css"),
} }
div { id: "wrapper", div { id: "wrapper",
div { class: "app", div { class: "app",

View file

@ -32,7 +32,7 @@ fn main() {
} }
document::Link { document::Link {
rel: "stylesheet", rel: "stylesheet",
href: asset!("./examples/assets/crm.css"), href: asset!("/examples/assets/crm.css"),
} }
h1 { "Dioxus CRM Example" } h1 { "Dioxus CRM Example" }
Router::<Route> {} Router::<Route> {}

View file

@ -30,7 +30,7 @@ fn app() -> Element {
rsx! { rsx! {
document::Link { document::Link {
rel: "stylesheet", rel: "stylesheet",
href: asset!("./examples/assets/read_size.css"), href: asset!("/examples/assets/read_size.css"),
} }
div { div {
width: "50%", width: "50%",

View file

@ -17,7 +17,7 @@ fn app() -> Element {
rsx!( rsx!(
document::Link { document::Link {
rel: "stylesheet", rel: "stylesheet",
href: asset!("./examples/assets/read_size.css"), href: asset!("/examples/assets/read_size.css"),
} }
div { div {
width: "50%", width: "50%",

View file

@ -86,7 +86,7 @@ impl LinkProps {
/// // You can use the meta component to render a meta tag into the head of the page /// // You can use the meta component to render a meta tag into the head of the page
/// // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds /// // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds
/// document::Link { /// document::Link {
/// href: asset!("./assets/style.css"), /// href: asset!("/assets/style.css"),
/// rel: "stylesheet", /// rel: "stylesheet",
/// } /// }
/// } /// }

View file

@ -74,7 +74,7 @@ impl ScriptProps {
/// rsx! { /// rsx! {
/// // You can use the Script component to render a script tag into the head of the page /// // You can use the Script component to render a script tag into the head of the page
/// document::Script { /// document::Script {
/// src: asset!("./assets/script.js"), /// src: asset!("/assets/script.js"),
/// } /// }
/// } /// }
/// } /// }

View file

@ -24,11 +24,8 @@ pub struct ResourceAsset {
pub bundled: String, pub bundled: String,
} }
#[derive(Debug)]
pub struct AssetError {}
impl ResourceAsset { impl ResourceAsset {
pub fn parse_any(raw: &str) -> Result<Self, AssetError> { pub fn parse_any(raw: &str) -> Result<Self, AssetParseError> {
// get the location where the asset is absolute, relative to // get the location where the asset is absolute, relative to
// //
// IE // IE
@ -43,13 +40,17 @@ impl ResourceAsset {
let input = PathBuf::from(raw); let input = PathBuf::from(raw);
// 2. absolute path to the asset // 2. absolute path to the asset
let absolute = manifest_dir let absolute = manifest_dir.join(raw.trim_start_matches('/'));
.join(raw.trim_start_matches('/')) let absolute =
.canonicalize() absolute
.unwrap(); .canonicalize()
.map_err(|err| AssetParseError::AssetDoesntExist {
err,
path: absolute,
})?;
// 3. the bundled path is the unique name of the asset // 3. the bundled path is the unique name of the asset
let bundled = Self::make_unique_name(absolute.clone()); let bundled = Self::make_unique_name(absolute.clone())?;
Ok(Self { Ok(Self {
input, input,
@ -58,14 +59,16 @@ impl ResourceAsset {
}) })
} }
fn make_unique_name(file_path: PathBuf) -> String { fn make_unique_name(file_path: PathBuf) -> Result<String, AssetParseError> {
// Create a hasher // Create a hasher
let mut hash = std::collections::hash_map::DefaultHasher::new(); let mut hash = std::collections::hash_map::DefaultHasher::new();
// Open the file to get its options // Open the file to get its options
let file = std::fs::File::open(&file_path).unwrap(); let file = std::fs::File::open(&file_path).map_err(AssetParseError::FailedToReadAsset)?;
let metadata = file.metadata().unwrap(); let modified = file
let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH); .metadata()
.and_then(|metadata| metadata.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
// Hash a bunch of metadata // Hash a bunch of metadata
// name, options, modified time, and maybe the version of our crate // name, options, modified time, and maybe the version of our crate
@ -74,12 +77,42 @@ impl ResourceAsset {
file_path.hash(&mut hash); file_path.hash(&mut hash);
let uuid = hash.finish(); let uuid = hash.finish();
let file_name = file_path.file_stem().unwrap().to_string_lossy(); let file_name = file_path
.file_stem()
.expect("file_path should have a file_stem")
.to_string_lossy();
let extension = file_path let extension = file_path
.extension() .extension()
.map(|f| f.to_string_lossy()) .map(|f| f.to_string_lossy())
.unwrap_or_default(); .unwrap_or_default();
format!("{file_name}-{uuid:x}.{extension}") 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)
}
}
} }
} }

View file

@ -1,6 +1,6 @@
use manganis_core::ResourceAsset; use manganis_core::{AssetParseError, ResourceAsset};
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
LitStr, LitStr,
@ -8,7 +8,7 @@ use syn::{
pub struct AssetParser { pub struct AssetParser {
/// The asset itself /// The asset itself
asset: ResourceAsset, asset: Result<ResourceAsset, AssetParseError>,
/// The source of the trailing options /// The source of the trailing options
options: TokenStream2, options: TokenStream2,
@ -19,13 +19,13 @@ impl Parse for AssetParser {
// //
// This gives you the Asset type - it's generic and basically unrefined // This gives you the Asset type - it's generic and basically unrefined
// ``` // ```
// asset!("myfile.png") // asset!("/assets/myfile.png")
// ``` // ```
// //
// To narrow the type, use a method call to get the refined type // To narrow the type, use a method call to get the refined type
// ``` // ```
// asset!( // asset!(
// "myfile.png", // "/assets/myfile.png",
// asset::image() // asset::image()
// .format(ImageType::Jpg) // .format(ImageType::Jpg)
// .size(512, 512) // .size(512, 512)
@ -36,7 +36,7 @@ impl Parse for AssetParser {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
// And then parse the options // And then parse the options
let src = input.parse::<LitStr>()?; let src = input.parse::<LitStr>()?;
let asset = ResourceAsset::parse_any(&src.value()).unwrap(); let asset = ResourceAsset::parse_any(&src.value());
let options = input.parse()?; let options = input.parse()?;
Ok(Self { asset, options }) Ok(Self { asset, options })
@ -60,23 +60,39 @@ impl ToTokens for AssetParser {
// a limitation from rust itself. We technically could support them but not without some hoops // a limitation from rust itself. We technically could support them but not without some hoops
// to jump through // to jump through
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 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;
}
};
// 1. the link section itself // 1. the link section itself
let link_section = crate::generate_link_section(&self.asset); let link_section = crate::generate_link_section(&asset);
// 2. original // 2. original
let input = self.asset.input.display().to_string(); let input = asset.input.display().to_string();
// 3. resolved on the user's system // 3. resolved on the user's system
let local = self.asset.absolute.display().to_string(); let local = asset.absolute.display().to_string();
// 4. bundled // 4. bundled
let bundled = self.asset.bundled.to_string(); let bundled = asset.bundled.to_string();
// 5. source tokens // 5. source tokens
let option_source = &self.options; let option_source = &self.options;
// generate the asset::new method to deprecate the `./assets/blah.css` syntax
let method = if asset.input.is_relative() {
quote::quote! { new_relative }
} else {
quote::quote! { new }
};
tokens.extend(quote! { tokens.extend(quote! {
Asset::new( Asset::#method(
{ {
#link_section #link_section
manganis::Asset { manganis::Asset {

View file

@ -22,6 +22,16 @@ impl Asset {
self self
} }
/// 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(self) -> Self {
self
}
/// Get the path to the asset /// Get the path to the asset
pub fn path(&self) -> PathBuf { pub fn path(&self) -> PathBuf {
PathBuf::from(self.input.to_string()) PathBuf::from(self.input.to_string())