Browse Source

feat(core): fallback to file system for AssetResolver::get, closes #8411 (#10357)

* feat(core): fallback to file system for AssetResolver::get, closes #8411

Ports #10356 to v2

* fix test

---------

Co-authored-by: Chip Reed <chip@chip.sh>
Lucas Fernandes Nogueira 1 year ago
parent
commit
1e0793b682

+ 6 - 0
.changes/asset-resolver-dev-fallback.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch:enhance
+"tauri-codegen": patch:enhance
+---
+
+Enhance `AssetResolver::get` in development mode by reading distDir directly as a fallback to the embedded assets.

+ 12 - 0
core/tauri-codegen/src/context.rs

@@ -483,6 +483,15 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
     quote!(::std::option::Option::None)
   };
 
+  let maybe_config_parent_setter = if dev {
+    let config_parent = config_parent.to_string_lossy();
+    quote!({
+      context.with_config_parent(#config_parent);
+    })
+  } else {
+    quote!()
+  };
+
   Ok(quote!({
     #[allow(unused_mut, clippy::let_and_return)]
     let mut context = #root::Context::new(
@@ -496,7 +505,10 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
       #runtime_authority,
       #plugin_global_api_script
     );
+
     #with_tray_icon_code
+    #maybe_config_parent_setter
+
     context
   }))
 }

+ 38 - 0
core/tauri/src/app.rs

@@ -269,7 +269,45 @@ pub struct AssetResolver<R: Runtime> {
 
 impl<R: Runtime> AssetResolver<R> {
   /// Gets the app asset associated with the given path.
+  ///
+  /// Resolves to the embedded asset that is part of the app
+  /// in dev when [`devPath`](https://tauri.app/v1/api/config/#buildconfig.devpath) points to a folder in your filesystem
+  /// or in production when [`distDir`](https://tauri.app/v1/api/config/#buildconfig.distdir)
+  /// points to your frontend assets.
+  ///
+  /// Fallbacks to reading the asset from the [distDir] folder so the behavior is consistent in development.
+  /// Note that the dist directory must exist so you might need to build your frontend assets first.
   pub fn get(&self, path: String) -> Option<Asset> {
+    #[cfg(dev)]
+    {
+      // on dev if the devPath is a path to a directory we have the embedded assets
+      // so we can use get_asset() directly
+      // we only fallback to reading from distDir directly if we're using an external URL (which is likely)
+      if let (Some(_), Some(crate::utils::config::FrontendDist::Directory(dist_path))) = (
+        &self.manager.config().build.dev_url,
+        &self.manager.config().build.frontend_dist,
+      ) {
+        let asset_path = std::path::PathBuf::from(&path)
+          .components()
+          .filter(|c| !matches!(c, std::path::Component::RootDir))
+          .collect::<std::path::PathBuf>();
+
+        let asset_path = self
+          .manager
+          .config_parent()
+          .map(|p| p.join(dist_path).join(&asset_path))
+          .unwrap_or_else(|| dist_path.join(&asset_path));
+        return std::fs::read(asset_path).ok().map(|bytes| {
+          let mime_type = crate::utils::mime_type::MimeType::parse(&bytes, &path);
+          Asset {
+            bytes,
+            mime_type,
+            csp_header: None,
+          }
+        });
+      }
+    }
+
     self.manager.get_asset(path).ok()
   }
 

+ 12 - 0
core/tauri/src/lib.rs

@@ -379,6 +379,8 @@ impl<R: Runtime> Assets<R> for EmbeddedAssets {
 #[tauri_macros::default_runtime(Wry, wry)]
 pub struct Context<R: Runtime> {
   pub(crate) config: Config,
+  #[cfg(dev)]
+  pub(crate) config_parent: Option<std::path::PathBuf>,
   /// Asset provider.
   pub assets: Box<dyn Assets<R>>,
   pub(crate) default_window_icon: Option<image::Image<'static>>,
@@ -507,6 +509,8 @@ impl<R: Runtime> Context<R> {
   ) -> Self {
     Self {
       config,
+      #[cfg(dev)]
+      config_parent: None,
       assets,
       default_window_icon,
       app_icon,
@@ -519,6 +523,14 @@ impl<R: Runtime> Context<R> {
       plugin_global_api_scripts,
     }
   }
+
+  #[cfg(dev)]
+  #[doc(hidden)]
+  pub fn with_config_parent(&mut self, config_parent: impl AsRef<std::path::Path>) {
+    self
+      .config_parent
+      .replace(config_parent.as_ref().to_owned());
+  }
 }
 
 // TODO: expand these docs

+ 9 - 0
core/tauri/src/manager/mod.rs

@@ -179,6 +179,8 @@ pub struct AppManager<R: Runtime> {
   pub listeners: Listeners,
   pub state: Arc<StateManager>,
   pub config: Config,
+  #[cfg(dev)]
+  pub config_parent: Option<std::path::PathBuf>,
   pub assets: Box<dyn Assets<R>>,
 
   pub app_icon: Option<Vec<u8>>,
@@ -278,6 +280,8 @@ impl<R: Runtime> AppManager<R> {
       listeners: Listeners::default(),
       state: Arc::new(state),
       config: context.config,
+      #[cfg(dev)]
+      config_parent: context.config_parent,
       assets: context.assets,
       app_icon: context.app_icon,
       package_info: context.package_info,
@@ -458,6 +462,11 @@ impl<R: Runtime> AppManager<R> {
     &self.config
   }
 
+  #[cfg(dev)]
+  pub fn config_parent(&self) -> Option<&std::path::PathBuf> {
+    self.config_parent.as_ref()
+  }
+
   pub fn package_info(&self) -> &PackageInfo {
     &self.package_info
   }

+ 3 - 0
core/tauri/src/test/mod.rs

@@ -135,6 +135,9 @@ pub fn mock_context<R: Runtime, A: Assets<R>>(assets: A) -> crate::Context<R> {
     pattern: Pattern::Brownfield,
     runtime_authority: RuntimeAuthority::new(Default::default(), Resolved::default()),
     plugin_global_api_scripts: None,
+
+    #[cfg(dev)]
+    config_parent: None,
   }
 }