This proposal is not finished yet! Please participate by reviewing the proposed API.
Is your feature request related to a problem? Please describe.
There are several observations I made about the current Plugin trait that I would like to address:
- Defining a custom struct for a plugin has little realworld use as plugin state is better stored using the
app.manage and app.state methods. (This state is also accessible in commands while plugin struct fields are not)
- the
Plugin method names are not super self explanatory and naming is somewhat inconsistent (created is an event callback but is not named on_created while on_event and on_page_load are)
- The practice of having an
invoke_handler field and a extend_api to register commands is not obvious
- In "real-world" usage (the official plugins) the
config property taken from the tauri.conf.json file had little use and makes plugins more confusing rather than easier to use. It has shown that exporting a builder and having configuration "in-code" is better.
In general I think the current Plugin api leads to unnecessarily verbose and complex plugins, so I would like to propose an improved builder based API:
Describe the solution you'd like
Introduce a PluginBuilder struct to streamline the plugin creation:
/// Mock command
#[command]
async fn execute<R: Runtime>(_app: AppHandle<R>) -> Result<String> {
Ok("success".to_string())
}
pub fn init() -> Plugin { // not the Plugin trait
PluginBuilder::new("example")
.commands(vec![execute])
.setup(|app| {
app.manage(MyState::default());
Ok(())
})
}
Instead of the current api:
/// Mock command
#[command]
async fn execute<R: Runtime>(_app: AppHandle<R>) -> Result<String> {
Ok("success".to_string())
}
pub struct ExamplePlugin<R: Runtime> {
invoke_handler: Box<dyn Fn(Invoke<R>) + Send + Sync>,
}
impl<R: Runtime> Default for ExamplePlugin<R> {
fn default() -> Self {
Self {
invoke_handler: Box::new(tauri::generate_handler![execute]),
}
}
}
impl<R: Runtime> Plugin<R> for ExamplePlugin<R> {
fn name(&self) -> &'static str {
"example"
}
fn initialize(&mut self, app: &AppHandle<R>, _config: JsonValue) -> tauri::plugin::Result<()> {
app.manage(MyState::default());
Ok(())
}
fn extend_api(&mut self, message: Invoke<R>) {
(self.invoke_handler)(message)
}
}
The proposed builder methods closely resemble the traits methods, with a few differences:
- rename methods to better communicate their purpose
js instead of initiailization_script, commands instead of extend_api and setup instead of initialize.
- name event callbacks consistently
with the prefix on_
trait PluginBuilder {
// creates the builder using the plugin `name`
fn new(name: &'static str) -> Self;
// Renamed `initialization_script` fn to better communicate it takes a JavaScript string
fn js(&mut self, js_code: String) -> &mut self;
// Register an array of tauri commands. This hides all the ugly Boxing and macro usage
fn commands(&mut self, commands: Vec<?>) -> &mut self;
// Renamed `initialize` fn to highlight the similarity to the `AppBuilder`s setup fn
fn setup(setup: Fn(&AppHandle<R>) -> Result<(), Box<dyn Error + Send>> + Send + 'static);
// set the `on_event ` method
fn on_event(cb: Fn(&AppHandle<R>, &Event)) -> &mut self;
// set the `on_page_load` method
fn on_page_load(cb: Fn(Window<R>, payload: PageLoadPayload)) -> &mut self;
// set the `created` method
fn on_webview_ready(cb: Fn(Window<R>)) -> &mut self;
// builds the plugin
fn build(self) -> Plugin; // not the Plugin trait
}
The PluginBuilder struct could just be wrapper returning Plugin trait implementations, so this proposal would have a clear implementation path.
Notes
- Having a builder struct allows us to improve and extend the plugin API in the future with less hassle
- I propose plugins exporting a creation function called
init (as shown in the example above). This function is free to receive any arguments, so plugin authors may use this to provide configuration options for end users.
- I'm considering to remove the
js/initialization_script methods all together. Plugins should expose corresponding js packages and not rely on extending the global object. The overwhelming majority of tauri projects also uses bundlers which make providing api packages is a no-brainer. For the few projects that don't, plugin can provide prebuilt browser bundles. See tauri-plugin-store for an example of this.
Describe alternatives you've considered
Leave the trait as is.
Additional context
The builder pattern is inspired by deno_cores ExtensionBuilder.
The initfunction is inspired by denos extensions and the convention among rollup plugins to export a single creation function.
Is your feature request related to a problem? Please describe.
There are several observations I made about the current
Plugintrait that I would like to address:app.manageandapp.statemethods. (This state is also accessible in commands while plugin struct fields are not)Pluginmethod names are not super self explanatory and naming is somewhat inconsistent (createdis an event callback but is not namedon_createdwhileon_eventandon_page_loadare)invoke_handlerfield and aextend_apito register commands is not obviousconfigproperty taken from thetauri.conf.jsonfile had little use and makes plugins more confusing rather than easier to use. It has shown that exporting a builder and having configuration "in-code" is better.In general I think the current Plugin api leads to unnecessarily verbose and complex plugins, so I would like to propose an improved builder based API:
Describe the solution you'd like
Introduce a
PluginBuilderstruct to streamline the plugin creation:Instead of the current api:
The proposed builder methods closely resemble the traits methods, with a few differences:
jsinstead ofinitiailization_script,commandsinstead ofextend_apiandsetupinstead ofinitialize.with the prefix
on_The
PluginBuilderstruct could just be wrapper returningPlugintrait implementations, so this proposal would have a clear implementation path.Notes
init(as shown in the example above). This function is free to receive any arguments, so plugin authors may use this to provide configuration options for end users.js/initialization_scriptmethods all together. Plugins should expose corresponding js packages and not rely on extending the global object. The overwhelming majority of tauri projects also uses bundlers which make providing api packages is a no-brainer. For the few projects that don't, plugin can provide prebuilt browser bundles. See tauri-plugin-store for an example of this.Describe alternatives you've considered
Leave the trait as is.
Additional context
The builder pattern is inspired by
deno_coresExtensionBuilder.The
initfunction is inspired by denos extensions and the convention among rollup plugins to export a single creation function.