Skip to content

Commit

Permalink
feat(core): allow defining global API script on plugin build (#9156)
Browse files Browse the repository at this point in the history
* feat(core): allow defining global API script on plugin build

Adds `tauri_plugin::Builder::global_api_script_path` so plugin authors can define the JavaScript global API bindings (supposed to be injected to `window.__TAURI__`) at compile time, so the string is only part of the binary when withGlobalTauri is true.
Currently this needs to be done manually at runtime (and it's always added to the binary via include_str).

* prefix variable

* use list of scripts instead of combining them

* static str

* header [skip ci]

* slice
  • Loading branch information
lucasfernog authored Mar 12, 2024
1 parent 3f039c1 commit e227fe0
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 10 deletions.
8 changes: 8 additions & 0 deletions .changes/global-api-script-path-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"tauri": patch:feat
"tauri-codegen": patch:feat
"tauri-build": patch:feat
"tauri-plugin": patch:feat
---

Allow plugins to define (at compile time) JavaScript that are initialized when `withGlobalTauri` is true.
5 changes: 5 additions & 0 deletions .changes/plugin-global-api-script.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-plugin": patch:feat
---

Added `Builder::global_api_script_path` to define a JavaScript file containing the initialization script for the plugin API bindings when `withGlobalTauri` is used.
5 changes: 5 additions & 0 deletions .changes/tauri-utils-plugin-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": patch:feat
---

Added the `plugin` module.
2 changes: 2 additions & 0 deletions core/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ pub fn try_build(attributes: Attributes) -> Result<()> {

acl::save_acl_manifests(&acl_manifests)?;

tauri_utils::plugin::load_global_api_scripts(&out_dir);

println!("cargo:rustc-env=TAURI_ENV_TARGET_TRIPLE={target_triple}");

// TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/5457>
Expand Down
34 changes: 33 additions & 1 deletion core/tauri-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use tauri_utils::html::{
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef,
};
use tauri_utils::platform::Target;
use tauri_utils::plugin::GLOBAL_API_SCRIPT_FILE_LIST_PATH;
use tauri_utils::tokens::{map_lit, str_lit};

use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError};
Expand Down Expand Up @@ -456,6 +457,36 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved));

let plugin_global_api_script_file_list_path = out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH);
let plugin_global_api_script =
if config.app.with_global_tauri && plugin_global_api_script_file_list_path.exists() {
let file_list_str = std::fs::read_to_string(plugin_global_api_script_file_list_path)
.expect("failed to read plugin global API script paths");
let file_list = serde_json::from_str::<Vec<PathBuf>>(&file_list_str)
.expect("failed to parse plugin global API script paths");

let mut plugins = Vec::new();
for path in file_list {
plugins.push(std::fs::read_to_string(&path).unwrap_or_else(|e| {
panic!(
"failed to read plugin global API script {}: {e}",
path.display()
)
}));
}

Some(plugins)
} else {
None
};

let plugin_global_api_script = if let Some(scripts) = plugin_global_api_script {
let scripts = scripts.into_iter().map(|s| quote!(#s));
quote!(::std::option::Option::Some(&[#(#scripts),*]))
} else {
quote!(::std::option::Option::None)
};

Ok(quote!({
#[allow(unused_mut, clippy::let_and_return)]
let mut context = #root::Context::new(
Expand All @@ -466,7 +497,8 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
#package_info,
#info_plist,
#pattern,
#runtime_authority
#runtime_authority,
#plugin_global_api_script
);
#with_tray_icon_code
context
Expand Down
14 changes: 14 additions & 0 deletions core/tauri-plugin/src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub fn plugin_config<T: DeserializeOwned>(name: &str) -> Option<T> {
pub struct Builder<'a> {
commands: &'a [&'static str],
global_scope_schema: Option<schemars::schema::RootSchema>,
global_api_script_path: Option<PathBuf>,
android_path: Option<PathBuf>,
ios_path: Option<PathBuf>,
}
Expand All @@ -40,6 +41,7 @@ impl<'a> Builder<'a> {
Self {
commands,
global_scope_schema: None,
global_api_script_path: None,
android_path: None,
ios_path: None,
}
Expand All @@ -51,6 +53,14 @@ impl<'a> Builder<'a> {
self
}

/// Sets the path to the script that is injected in the webview when the `withGlobalTauri` configuration is set to true.
///
/// This is usually an IIFE that injects the plugin API JavaScript bindings to `window.__TAURI__`.
pub fn global_api_script_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.global_api_script_path.replace(path.into());
self
}

/// Sets the Android project path.
pub fn android_path<P: Into<PathBuf>>(mut self, android_path: P) -> Self {
self.android_path.replace(android_path.into());
Expand Down Expand Up @@ -118,6 +128,10 @@ impl<'a> Builder<'a> {
acl::build::define_global_scope_schema(global_scope_schema, &name, &out_dir)?;
}

if let Some(path) = self.global_api_script_path {
tauri_utils::plugin::define_global_api_script_path(path);
}

mobile::setup(self.android_path, self.ios_path)?;

Ok(())
Expand Down
1 change: 1 addition & 0 deletions core/tauri-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod html;
pub mod io;
pub mod mime_type;
pub mod platform;
pub mod plugin;
/// Prepare application resources and sidecars.
#[cfg(feature = "resources")]
pub mod resources;
Expand Down
51 changes: 51 additions & 0 deletions core/tauri-utils/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

//! Compile-time and runtime types for Tauri plugins.
#[cfg(feature = "build")]
pub use build::*;

#[cfg(feature = "build")]
mod build {
use std::{
env::vars_os,
path::{Path, PathBuf},
};

const GLOBAL_API_SCRIPT_PATH_KEY: &str = "GLOBAL_API_SCRIPT_PATH";
/// Known file name of the file that contains an array with the path of all API scripts defined with [`define_global_api_script_path`].
pub const GLOBAL_API_SCRIPT_FILE_LIST_PATH: &str = "__global-api-script.js";

/// Defines the path to the global API script using Cargo instructions.
pub fn define_global_api_script_path(path: PathBuf) {
println!(
"cargo:{GLOBAL_API_SCRIPT_PATH_KEY}={}",
path
.canonicalize()
.expect("failed to canonicalize global API script path")
.display()
)
}

/// Collects the path of all the global API scripts defined with [`define_global_api_script_path`]
/// and saves them to the out dir with filename [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`].
pub fn load_global_api_scripts(out_dir: &Path) {
let mut scripts = Vec::new();

for (key, value) in vars_os() {
let key = key.to_string_lossy();

if key.starts_with("DEP_") && key.ends_with(GLOBAL_API_SCRIPT_PATH_KEY) {
let script_path = PathBuf::from(value);
scripts.push(script_path);
}
}

std::fs::write(
out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH),
serde_json::to_string(&scripts).expect("failed to serialize global API script paths"),
)
.expect("failed to write global API script");
}
}
6 changes: 5 additions & 1 deletion core/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ pub struct Context<R: Runtime> {
pub(crate) _info_plist: (),
pub(crate) pattern: Pattern,
pub(crate) runtime_authority: RuntimeAuthority,
pub(crate) plugin_global_api_scripts: Option<&'static [&'static str]>,
}

impl<R: Runtime> fmt::Debug for Context<R> {
Expand All @@ -397,7 +398,8 @@ impl<R: Runtime> fmt::Debug for Context<R> {
.field("default_window_icon", &self.default_window_icon)
.field("app_icon", &self.app_icon)
.field("package_info", &self.package_info)
.field("pattern", &self.pattern);
.field("pattern", &self.pattern)
.field("plugin_global_api_scripts", &self.plugin_global_api_scripts);

#[cfg(all(desktop, feature = "tray-icon"))]
d.field("tray_icon", &self.tray_icon);
Expand Down Expand Up @@ -500,6 +502,7 @@ impl<R: Runtime> Context<R> {
info_plist: (),
pattern: Pattern,
runtime_authority: RuntimeAuthority,
plugin_global_api_scripts: Option<&'static [&'static str]>,
) -> Self {
Self {
config,
Expand All @@ -512,6 +515,7 @@ impl<R: Runtime> Context<R> {
_info_plist: info_plist,
pattern,
runtime_authority,
plugin_global_api_scripts,
}
}

Expand Down
4 changes: 4 additions & 0 deletions core/tauri/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ pub struct AppManager<R: Runtime> {
/// Application pattern.
pub pattern: Arc<Pattern>,

/// Global API scripts collected from plugins.
pub plugin_global_api_scripts: Arc<Option<&'static [&'static str]>>,

/// Application Resources Table
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
}
Expand Down Expand Up @@ -274,6 +277,7 @@ impl<R: Runtime> AppManager<R> {
app_icon: context.app_icon,
package_info: context.package_info,
pattern: Arc::new(context.pattern),
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
resources_table: Arc::default(),
}
}
Expand Down
6 changes: 6 additions & 0 deletions core/tauri/src/manager/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ impl<R: Runtime> WebviewManager<R> {
);
}

if let Some(plugin_global_api_scripts) = &*app_manager.plugin_global_api_scripts {
for script in plugin_global_api_scripts.iter() {
webview_attributes = webview_attributes.initialization_script(script);
}
}

pending.webview_attributes = webview_attributes;

let mut registered_scheme_protocols = Vec::new();
Expand Down
1 change: 1 addition & 0 deletions core/tauri/src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub fn mock_context<R: Runtime, A: Assets<R>>(assets: A) -> crate::Context<R> {
_info_plist: (),
pattern: Pattern::Brownfield,
runtime_authority: RuntimeAuthority::new(Default::default(), Resolved::default()),
plugin_global_api_scripts: None,
}
}

Expand Down
16 changes: 8 additions & 8 deletions examples/api/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions examples/api/src-tauri/tauri-plugin-sample/api-iife.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

if ('__TAURI__' in window) {
window.__TAURI__.sample = {}
}
1 change: 1 addition & 0 deletions examples/api/src-tauri/tauri-plugin-sample/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ fn main() {
tauri_plugin::Builder::new(COMMANDS)
.android_path("android")
.ios_path("ios")
.global_api_script_path("./api-iife.js")
.build();
}

0 comments on commit e227fe0

Please sign in to comment.