feat(core): allow swapping the assets implementation (#9141)

This commit is contained in:
Lucas Fernandes Nogueira 2024-03-11 11:07:15 -03:00 committed by GitHub
parent db1ea98512
commit ba0206d8a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 83 additions and 42 deletions

View File

@ -0,0 +1,6 @@
---
"tauri-macros": patch:feat
"tauri-codegen": patch:feat
---
The `Context` codegen now accepts a `assets` input to define a custom `tauri::Assets` implementation.

View File

@ -0,0 +1,5 @@
---
"tauri": patch:breaking
---
`Context::assets` now returns `&dyn Assets` instead of `Box<&dyn Assets>`.

View File

@ -0,0 +1,5 @@
---
"tauri": patch:breaking
---
The `Context` type no longer uses the `<A: Assets>` generic so the assets implementation can be swapped with `Context::assets_mut`.

View File

@ -128,6 +128,7 @@ impl CodegenContext {
// outside the tauri crate, making the ::tauri root valid. // outside the tauri crate, making the ::tauri root valid.
root: quote::quote!(::tauri), root: quote::quote!(::tauri),
capabilities: self.capabilities, capabilities: self.capabilities,
assets: None,
})?; })?;
// get the full output file path // get the full output file path

View File

@ -12,6 +12,7 @@ use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use syn::Expr;
use tauri_utils::acl::capability::{Capability, CapabilityFile}; use tauri_utils::acl::capability::{Capability, CapabilityFile};
use tauri_utils::acl::manifest::Manifest; use tauri_utils::acl::manifest::Manifest;
use tauri_utils::acl::resolved::Resolved; use tauri_utils::acl::resolved::Resolved;
@ -36,6 +37,8 @@ pub struct ContextData {
pub root: TokenStream, pub root: TokenStream,
/// Additional capabilities to include. /// Additional capabilities to include.
pub capabilities: Option<Vec<PathBuf>>, pub capabilities: Option<Vec<PathBuf>>,
/// The custom assets implementation
pub assets: Option<Expr>,
} }
fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) { fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) {
@ -132,6 +135,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
config_parent, config_parent,
root, root,
capabilities: additional_capabilities, capabilities: additional_capabilities,
assets,
} = data; } = data;
let target = std::env::var("TARGET") let target = std::env::var("TARGET")
@ -163,10 +167,13 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
options = options.with_csp(); options = options.with_csp();
} }
let assets = if dev && config.build.dev_url.is_some() { let assets = if let Some(assets) = assets {
Default::default() quote!(#assets)
} else if dev && config.build.dev_url.is_some() {
let assets = EmbeddedAssets::default();
quote!(#assets)
} else { } else {
match &config.build.frontend_dist { let assets = match &config.build.frontend_dist {
Some(url) => match url { Some(url) => match url {
FrontendDist::Url(_url) => Default::default(), FrontendDist::Url(_url) => Default::default(),
FrontendDist::Directory(path) => { FrontendDist::Directory(path) => {
@ -190,7 +197,8 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
_ => unimplemented!(), _ => unimplemented!(),
}, },
None => Default::default(), None => Default::default(),
} };
quote!(#assets)
}; };
let out_dir = { let out_dir = {

View File

@ -17,6 +17,7 @@ pub(crate) struct ContextItems {
config_file: PathBuf, config_file: PathBuf,
root: syn::Path, root: syn::Path,
capabilities: Option<Vec<PathBuf>>, capabilities: Option<Vec<PathBuf>>,
assets: Option<Expr>,
} }
impl Parse for ContextItems { impl Parse for ContextItems {
@ -29,6 +30,7 @@ impl Parse for ContextItems {
let mut root = None; let mut root = None;
let mut capabilities = None; let mut capabilities = None;
let mut assets = None;
let config_file = input.parse::<LitStr>().ok().map(|raw| { let config_file = input.parse::<LitStr>().ok().map(|raw| {
let _ = input.parse::<Token![,]>(); let _ = input.parse::<Token![,]>();
let path = PathBuf::from(raw.value()); let path = PathBuf::from(raw.value());
@ -57,32 +59,44 @@ impl Parse for ContextItems {
root.replace(p); root.replace(p);
} }
Meta::NameValue(v) => { Meta::NameValue(v) => {
if *v.path.require_ident()? == "capabilities" { let ident = v.path.require_ident()?;
if let Expr::Array(array) = v.value { match ident.to_string().as_str() {
capabilities.replace( "capabilities" => {
array if let Expr::Array(array) = v.value {
.elems capabilities.replace(
.into_iter() array
.map(|e| { .elems
if let Expr::Lit(ExprLit { .into_iter()
attrs: _, .map(|e| {
lit: Lit::Str(s), if let Expr::Lit(ExprLit {
}) = e attrs: _,
{ lit: Lit::Str(s),
Ok(s.value().into()) }) = e
} else { {
Err(syn::Error::new( Ok(s.value().into())
input.span(), } else {
"unexpected expression for capability", Err(syn::Error::new(
)) input.span(),
} "unexpected expression for capability",
}) ))
.collect::<Result<Vec<_>, syn::Error>>()?, }
); })
} else { .collect::<Result<Vec<_>, syn::Error>>()?,
);
} else {
return Err(syn::Error::new(
input.span(),
"unexpected value for capabilities",
));
}
}
"assets" => {
assets.replace(v.value);
}
name => {
return Err(syn::Error::new( return Err(syn::Error::new(
input.span(), input.span(),
"unexpected value for capabilities", format!("unknown attribute {name}"),
)); ));
} }
} }
@ -113,6 +127,7 @@ impl Parse for ContextItems {
} }
}), }),
capabilities, capabilities,
assets,
}) })
} }
} }
@ -126,6 +141,7 @@ pub(crate) fn generate_context(context: ContextItems) -> TokenStream {
config_parent, config_parent,
root: context.root.to_token_stream(), root: context.root.to_token_stream(),
capabilities: context.capabilities, capabilities: context.capabilities,
assets: context.assets,
}) })
.and_then(|data| context_codegen(data).map_err(|e| e.to_string())); .and_then(|data| context_codegen(data).map_err(|e| e.to_string()));

View File

@ -19,7 +19,7 @@ use crate::{
}, },
sealed::{ManagerBase, RuntimeOrDispatch}, sealed::{ManagerBase, RuntimeOrDispatch},
utils::config::Config, utils::config::Config,
utils::{assets::Assets, Env}, utils::Env,
webview::PageLoadPayload, webview::PageLoadPayload,
Context, DeviceEventFilter, EventLoopMessage, Manager, Monitor, Runtime, Scopes, StateManager, Context, DeviceEventFilter, EventLoopMessage, Manager, Monitor, Runtime, Scopes, StateManager,
Theme, Webview, WebviewWindowBuilder, Window, Theme, Webview, WebviewWindowBuilder, Window,
@ -1581,7 +1581,7 @@ tauri::Builder::default()
feature = "tracing", feature = "tracing",
tracing::instrument(name = "app::build", skip_all) tracing::instrument(name = "app::build", skip_all)
)] )]
pub fn build<A: Assets>(mut self, context: Context<A>) -> crate::Result<App<R>> { pub fn build(mut self, context: Context) -> crate::Result<App<R>> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
if self.menu.is_none() && self.enable_macos_default_menu { if self.menu.is_none() && self.enable_macos_default_menu {
self.menu = Some(Box::new(|app_handle| { self.menu = Some(Box::new(|app_handle| {
@ -1749,7 +1749,7 @@ tauri::Builder::default()
} }
/// Runs the configured Tauri application. /// Runs the configured Tauri application.
pub fn run<A: Assets>(self, context: Context<A>) -> crate::Result<()> { pub fn run(self, context: Context) -> crate::Result<()> {
self.build(context)?.run(|_, _| {}); self.build(context)?.run(|_, _| {});
Ok(()) Ok(())
} }

View File

@ -343,9 +343,9 @@ pub fn dev() -> bool {
/// # Stability /// # Stability
/// This is the output of the [`generate_context`] macro, and is not considered part of the stable API. /// This is the output of the [`generate_context`] macro, and is not considered part of the stable API.
/// Unless you know what you are doing and are prepared for this type to have breaking changes, do not create it yourself. /// Unless you know what you are doing and are prepared for this type to have breaking changes, do not create it yourself.
pub struct Context<A: Assets> { pub struct Context {
pub(crate) config: Config, pub(crate) config: Config,
pub(crate) assets: Box<A>, pub(crate) assets: Box<dyn Assets>,
pub(crate) default_window_icon: Option<image::Image<'static>>, pub(crate) default_window_icon: Option<image::Image<'static>>,
pub(crate) app_icon: Option<Vec<u8>>, pub(crate) app_icon: Option<Vec<u8>>,
#[cfg(all(desktop, feature = "tray-icon"))] #[cfg(all(desktop, feature = "tray-icon"))]
@ -356,7 +356,7 @@ pub struct Context<A: Assets> {
pub(crate) runtime_authority: RuntimeAuthority, pub(crate) runtime_authority: RuntimeAuthority,
} }
impl<A: Assets> fmt::Debug for Context<A> { impl fmt::Debug for Context {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Context"); let mut d = f.debug_struct("Context");
d.field("config", &self.config) d.field("config", &self.config)
@ -372,7 +372,7 @@ impl<A: Assets> fmt::Debug for Context<A> {
} }
} }
impl<A: Assets> Context<A> { impl Context {
/// The config the application was prepared with. /// The config the application was prepared with.
#[inline(always)] #[inline(always)]
pub fn config(&self) -> &Config { pub fn config(&self) -> &Config {
@ -387,13 +387,13 @@ impl<A: Assets> Context<A> {
/// The assets to be served directly by Tauri. /// The assets to be served directly by Tauri.
#[inline(always)] #[inline(always)]
pub fn assets(&self) -> &A { pub fn assets(&self) -> &dyn Assets {
&self.assets self.assets.as_ref()
} }
/// A mutable reference to the assets to be served directly by Tauri. /// A mutable reference to the assets to be served directly by Tauri.
#[inline(always)] #[inline(always)]
pub fn assets_mut(&mut self) -> &mut A { pub fn assets_mut(&mut self) -> &mut Box<dyn Assets> {
&mut self.assets &mut self.assets
} }
@ -459,7 +459,7 @@ impl<A: Assets> Context<A> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
config: Config, config: Config,
assets: Box<A>, assets: Box<dyn Assets>,
default_window_icon: Option<image::Image<'static>>, default_window_icon: Option<image::Image<'static>>,
app_icon: Option<Vec<u8>>, app_icon: Option<Vec<u8>>,
package_info: PackageInfo, package_info: PackageInfo,

View File

@ -216,7 +216,7 @@ impl<R: Runtime> fmt::Debug for AppManager<R> {
impl<R: Runtime> AppManager<R> { impl<R: Runtime> AppManager<R> {
#[allow(clippy::too_many_arguments, clippy::type_complexity)] #[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub(crate) fn with_handlers( pub(crate) fn with_handlers(
#[allow(unused_mut)] mut context: Context<impl Assets>, #[allow(unused_mut)] mut context: Context,
plugins: PluginStore<R>, plugins: PluginStore<R>,
invoke_handler: Box<InvokeHandler<R>>, invoke_handler: Box<InvokeHandler<R>>,
on_page_load: Option<Arc<OnPageLoad<R>>>, on_page_load: Option<Arc<OnPageLoad<R>>>,

View File

@ -94,7 +94,7 @@ pub fn noop_assets() -> NoopAsset {
} }
/// Creates a new [`crate::Context`] for testing. /// Creates a new [`crate::Context`] for testing.
pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> { pub fn mock_context<A: Assets>(assets: A) -> crate::Context {
Context { Context {
config: Config { config: Config {
schema: None, schema: None,

View File

@ -78,7 +78,7 @@ mod ui {
} }
} }
fn context() -> tauri::Context<tauri::utils::assets::EmbeddedAssets> { fn context() -> tauri::Context {
tauri::generate_context!("../../examples/splashscreen/tauri.conf.json") tauri::generate_context!("../../examples/splashscreen/tauri.conf.json")
} }