mirror of
https://github.com/tauri-apps/tauri-docs.git
synced 2026-01-31 00:35:16 +01:00
feat: add documentation for plugins (#1412)
* feat: add documentation for plugins * update prettierignore Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> * update contributing guide Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> * add notification guide stub Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> * update guide Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> * revise lifecycle events Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> * some more updates and revisions Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> * Formatting Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> * resolve some todos * Update src/content/docs/2/guide/plugins/index.mdx * Update src/content/docs/2/guide/plugins/index.mdx * format * The big refactor * Fix broken links, add todo * rework sidebar * typos and minor fixes * fix link * Apply suggestions from code review * why?? * fill "develop an iOS plugin" section * Apply suggestions from code review * enable i18n * fix typo --------- Signed-off-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> Co-authored-by: Lorenzo Lewis <lorenzo_lewis@icloud.com>
This commit is contained in:
committed by
GitHub
parent
8d8a05a146
commit
2d970c97d0
14
.github/CONTRIBUTING.md
vendored
14
.github/CONTRIBUTING.md
vendored
@@ -52,7 +52,17 @@ While Tauri 2.0 is still in the prerelease stage people follow these guidelines
|
||||
|
||||
### Writing Style
|
||||
|
||||
Any ideas? Put them here!
|
||||
**Dictionary**
|
||||
|
||||
| Word | Description |
|
||||
| -------- | ------------------------------------ |
|
||||
| app | A Tauri app, prefer over application |
|
||||
| web view | Where the UI is rendered |
|
||||
|
||||
- Use an [oxford comma](https://www.grammarly.com/blog/what-is-the-oxford-comma-and-why-do-people-care-so-much-about-it/) in paragraphs, but not in headings and titles
|
||||
- Use [title case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case) for headings and titles
|
||||
- Make headings as succinct as possible to help the reader quickly find the content they need
|
||||
- Use [simple present tense](https://www.grammarly.com/blog/simple-present/) for verbs
|
||||
|
||||
### Guide
|
||||
|
||||
@@ -90,4 +100,4 @@ Topics that are around understanding something can be written as a blog post (we
|
||||
|
||||
Thanks for your interest in helping to translate the documentation! Visit the [translation status page](https://beta.tauri.app/contribute/translate-status) to see which docs are ready for translation, need updated, or need reviewed.
|
||||
|
||||
Read the [Translating Guide](./TRANSLATING.md) for more information.
|
||||
Read the [Translating Guide](./TRANSLATING.md) for more information.
|
||||
|
||||
@@ -14,3 +14,4 @@ pnpm-lock.yaml
|
||||
|
||||
# Configs
|
||||
.github
|
||||
!.github/**.md
|
||||
|
||||
@@ -115,7 +115,7 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Workflow',
|
||||
label: 'Guides',
|
||||
items: [
|
||||
{
|
||||
label: 'Develop',
|
||||
@@ -137,6 +137,10 @@ export default defineConfig({
|
||||
label: 'Distribute',
|
||||
link: '2/guide/distribute',
|
||||
},
|
||||
{
|
||||
label: 'Plugin Development',
|
||||
link: '2/guide/plugins',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
7
src/content/docs/2/guide/commands.mdx
Normal file
7
src/content/docs/2/guide/commands.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Commands
|
||||
---
|
||||
|
||||
import Stub from '@components/Stub.astro';
|
||||
|
||||
<Stub />
|
||||
385
src/content/docs/2/guide/plugins/develop-mobile.mdx
Normal file
385
src/content/docs/2/guide/plugins/develop-mobile.mdx
Normal file
@@ -0,0 +1,385 @@
|
||||
---
|
||||
title: Mobile Plugin Development
|
||||
i18nReady: true
|
||||
---
|
||||
|
||||
:::tip[Plugin Development]
|
||||
|
||||
Be sure that you're familiar with the concepts covered in the [Plugin Development guide](/2/guide/plugins) as many concepts in this guide build on top of foundations covered there.
|
||||
|
||||
:::
|
||||
|
||||
Plugins can run native mobile code written in Kotlin (or Java) and Swift. The default plugin template includes an Android library project using Kotlin and a Swift package including an example mobile command showing how to trigger its execution from Rust code.
|
||||
|
||||
## Initialize Plugin Project
|
||||
|
||||
Follow the steps in the [Plugin Development guide](/2/guide/plugins#initialize-plugin-project) to initialize a new plugin project.
|
||||
|
||||
If you have an existing plugin and would like to add Android or iOS capabilities to it, you can use `plugin android add` and `plugin ios add` to bootstrap the mobile library projects and guide you through the changes needed.
|
||||
|
||||
The default plugin template splits the plugin's implementation into two separate modules: `desktop.rs` and `mobile.rs`.
|
||||
|
||||
The desktop implementation uses Rust code to implement a functionality, while the mobile implementation sends a message to the native mobile code to execute a function and get a result back. If shared logic is needed across both implementations, it can be defined in `lib.rs`:
|
||||
|
||||
```rust
|
||||
// lib.rs
|
||||
use tauri::Runtime;
|
||||
|
||||
impl<R: Runtime> <plugin-name><R> {
|
||||
pub fn do_something(&self) {
|
||||
// do something that is a shared implementation between desktop and mobile
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This implementation simplifies the process of sharing an API that can be used both by commands and Rust code.
|
||||
|
||||
### Develop an Android Plugin
|
||||
|
||||
A Tauri plugin for Android is defined as a Kotlin class that extends `app.tauri.plugin.Plugin` and is annoted with `app.tauri.annotation.TauriPlugin`. Each method annotated with `app.tauri.annotation.Command` can be called by Rust or JavaScript.
|
||||
|
||||
Tauri uses Kotlin by default for the Android plugin implementation, but you can switch to Java if you prefer. After generating a plugin, right click the Kotlin plugin class in Android Studio and select the "Convert Kotlin file to Java file" option from the menu. Android Studio will guide you through the project migration to Java.
|
||||
|
||||
### Develop an iOS Plugin
|
||||
|
||||
A Tauri plugin for iOS is defined as a Swift class that extends the `Plugin` class from the `Tauri` package. Each function with the `@objc` attribute and the `(_ invoke: Invoke)` parameter (for example `@objc private func download(_ invoke: Invoke) { }`) can be called by Rust or JavaScript.
|
||||
|
||||
The plugin is defined as a [Swift package](https://www.swift.org/package-manager/) so that you can use its package manager to manage dependencies.
|
||||
|
||||
## Plugin Configuration
|
||||
|
||||
Refer to the [Plugin Configuration section](/2/guide/plugins/#plugin-configuration) of the Plugin Development guide for more details on developing plugin configurations.
|
||||
|
||||
The plugin instance on mobile has a getter for the plugin configuration:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Android">
|
||||
|
||||
```java
|
||||
@TauriPlugin
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
|
||||
override fun load(webView: WebView) {
|
||||
val timeout = this.config.getInt("timeout", 30)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="iOS">
|
||||
|
||||
```swift
|
||||
class ExamplePlugin: Plugin {
|
||||
@objc public override func load(webview: WKWebView) {
|
||||
let timeout = self.config["timeout"] as? Int ?? 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Lifecycle Events
|
||||
|
||||
Plugins can hook into several lifecycle events:
|
||||
|
||||
- [load](#load): When the plugin is loaded into the web view
|
||||
- [onNewIntent](#onnewintent): Android only, when the activity is re-launched
|
||||
|
||||
There are also the additional [lifecycle events for plugins](/2/guide/plugins#lifecycle-events) in the Plugin Development guide.
|
||||
|
||||
### load
|
||||
|
||||
- **When**: When the plugin is loaded into the web view
|
||||
- **Why**: Execute plugin initialization code
|
||||
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Android">
|
||||
|
||||
```java
|
||||
@TauriPlugin
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
|
||||
override fun load(webView: WebView) {
|
||||
// perform plugin setup here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="iOS">
|
||||
|
||||
```swift
|
||||
class ExamplePlugin: Plugin {
|
||||
@objc public override func load(webview: WKWebView) {
|
||||
let timeout = self.config["timeout"] as? Int ?? 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### onNewIntent
|
||||
|
||||
**Note**: This is only available on Android.
|
||||
|
||||
- **When**: When the activity is re-launched. See [Activity#onNewIntent](<https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)>) for more information.
|
||||
- **Why**: Handle application re-launch such as when a notification is clicked or a deep link is accessed.
|
||||
|
||||
|
||||
```java
|
||||
// import android.content.Intent
|
||||
|
||||
@TauriPlugin
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
// handle new intent event
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Mobile Commands
|
||||
|
||||
There is a plugin class inside the respective mobile projects where commands can be defined that can be called by the Rust code:
|
||||
|
||||
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Android">
|
||||
|
||||
```java
|
||||
@TauriPlugin
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
|
||||
@Command
|
||||
fun openCamera(invoke: Invoke) {
|
||||
val allowEdit = invoke.getBoolean("allowEdit", false)
|
||||
val quality = invoke.getInt("quality", 100)
|
||||
|
||||
val ret = JSObject()
|
||||
ret.put("path", "/path/to/photo.jpg")
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="iOS">
|
||||
|
||||
```swift
|
||||
class ExamplePlugin: Plugin {
|
||||
@objc public func openCamera(_ invoke: Invoke) {
|
||||
let allowEdit = invoke.getBool("allowEdit", false)
|
||||
let quality = invoke.getInt("quality", 100)
|
||||
|
||||
invoke.resolve(["path": "/path/to/photo.jpg"])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Use the [`tauri::plugin::PluginHandle`](https://docs.rs/tauri/2.0.0-alpha/tauri/plugin/struct.PluginHandle.html) to call a mobile command from Rust:
|
||||
```rust
|
||||
use std::path::PathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::Runtime;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CameraRequest {
|
||||
quality: usize,
|
||||
allow_edit: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Photo {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
|
||||
impl<R: Runtime> <plugin-name;pascal-case><R> {
|
||||
pub fn open_camera(&self, payload: CameraRequest) -> crate::Result<Photo> {
|
||||
self
|
||||
.0
|
||||
.run_mobile_plugin("openCamera", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Permissions
|
||||
|
||||
If a plugin requires permissions from the end user, Tauri simplifies the process of checking and requesting permissions.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Android">
|
||||
|
||||
First define the list of permissions needed and an alias to identify each group in code. This is done inside the `TauriPlugin` annotation:
|
||||
|
||||
```java
|
||||
@TauriPlugin(
|
||||
permissions = [
|
||||
Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "postNotification")
|
||||
]
|
||||
)
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) { }
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="iOS">
|
||||
|
||||
First override the `checkPermissions` and `requestPermissions` functions:
|
||||
|
||||
```swift
|
||||
class ExamplePlugin: Plugin {
|
||||
@objc open func checkPermissions(_ invoke: Invoke) {
|
||||
invoke.resolve(["postNotification": "prompt"])
|
||||
}
|
||||
|
||||
@objc public override func requestPermissions(_ invoke: Invoke) {
|
||||
// request permissions here
|
||||
// then resolve the request
|
||||
invoke.resolve(["postNotification": "granted"])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Tauri automatically implements two commands for the plugin: `checkPermissions` and `requestPermissions`. Those commands can be directly called from JavaScript or Rust:
|
||||
|
||||
{/* TODO: PermissionState type should be exported in Tauri */}
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="JavaScript">
|
||||
|
||||
```javascript
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
type PermissionState = 'granted' | 'denied' | 'prompt' | 'prompt-with-rationale'
|
||||
|
||||
interface Permissions {
|
||||
postNotification: PermissionState
|
||||
}
|
||||
|
||||
// check permission state
|
||||
const permission = await invoke<Permissions>('plugin:<plugin-name>|checkPermissions')
|
||||
|
||||
if (permission.postNotification === 'prompt-with-rationale') {
|
||||
// show information to the user about why permission is needed
|
||||
}
|
||||
|
||||
// request permission
|
||||
if (permission.postNotification.startsWith('prompt')) {
|
||||
const state = await invoke<Permissions>('plugin:<plugin-name>|requestPermissions', { permissions: ['postNotification'] })
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Rust">
|
||||
|
||||
```rust
|
||||
use serde::{Serialize, Deserialize};
|
||||
use tauri::Runtime;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PermissionResponse {
|
||||
pub post_notification: PermissionState,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct RequestPermission {
|
||||
post_notification: bool,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Notification<R> {
|
||||
pub fn request_post_notification_permission(&self) -> crate::Result<PermissionState> {
|
||||
self.0
|
||||
.run_mobile_plugin::<PermissionResponse>("requestPermissions", RequestPermission { post_notification: true })
|
||||
.map(|r| r.post_notification)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn check_permissions(&self) -> crate::Result<PermissionResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin::<PermissionResponse>("checkPermissions", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Plugin Events
|
||||
|
||||
{/* TODO: Is this section a duplicate of Lifecycle Events above? */}
|
||||
|
||||
Plugins can emit events at any point of time using the `trigger` function:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Android">
|
||||
|
||||
```java
|
||||
@TauriPlugin
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
|
||||
override fun load(webView: WebView) {
|
||||
trigger("load", JSObject())
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
// handle new intent event
|
||||
if (intent.action == Intent.ACTION_VIEW) {
|
||||
val data = intent.data.toString()
|
||||
val event = JSObject()
|
||||
event.put("data", data)
|
||||
trigger("newIntent", event)
|
||||
}
|
||||
}
|
||||
|
||||
@Command
|
||||
fun openCamera(invoke: Invoke) {
|
||||
val payload = JSObject()
|
||||
payload.put("open", true)
|
||||
trigger("camera", payload)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="iOS">
|
||||
|
||||
```swift
|
||||
class ExamplePlugin: Plugin {
|
||||
@objc public override func load(webview: WKWebView) {
|
||||
trigger("load", data: [:])
|
||||
}
|
||||
|
||||
@objc public func openCamera(_ invoke: Invoke) {
|
||||
trigger("camera", data: ["open": true])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The helper functionss can then be called from the NPM package by using the [`addPluginListener`](/2/reference/js/core/namespacetauri/#addpluginlistener) helper function:
|
||||
|
||||
```javascript
|
||||
import { addPluginListener, PluginListener } from '@tauri-apps/api/tauri';
|
||||
|
||||
export async function onRequest(
|
||||
handler: (url: string) => void
|
||||
): Promise<PluginListener> {
|
||||
return await addPluginListener(
|
||||
'<plugin-name>',
|
||||
'event-name',
|
||||
handler
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
title: Develop a Tauri Plugin
|
||||
---
|
||||
|
||||
import Stub from '@components/Stub.astro';
|
||||
|
||||
<Stub />
|
||||
|
||||
## `plugin init`
|
||||
|
||||
```sh
|
||||
cargo tauri plugin init
|
||||
```
|
||||
|
||||
```
|
||||
Initializes a Tauri plugin project
|
||||
|
||||
Usage: cargo tauri plugin init [OPTIONS] --name <PLUGIN_NAME>
|
||||
|
||||
Options:
|
||||
-n, --name <PLUGIN_NAME> Name of your Tauri plugin
|
||||
-v, --verbose... Enables verbose logging
|
||||
--no-api Initializes a Tauri plugin without the TypeScript API
|
||||
-d, --directory <DIRECTORY> Set target directory for init [default: C:\Users\Fabian-Lars]
|
||||
-t, --tauri-path <TAURI_PATH> Path of the Tauri project to use (relative to the cwd)
|
||||
-a, --author <AUTHOR> Author name
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
## `plugin android`
|
||||
|
||||
```sh
|
||||
cargo tauri plugin android
|
||||
```
|
||||
|
||||
```
|
||||
Manage the Android project for Tauri plugins
|
||||
|
||||
Usage: cargo tauri plugin android [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
add Adds the Android project to an existing Tauri plugin
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-v, --verbose... Enables verbose logging
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
## `plugin ios`
|
||||
|
||||
```sh
|
||||
cargo tauri plugin ios
|
||||
```
|
||||
|
||||
```
|
||||
Manage the iOS project for Tauri plugins
|
||||
|
||||
Usage: cargo tauri plugin ios [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
add Adds the iOS project to an existing Tauri plugin
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-v, --verbose... Enables verbose logging
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
@@ -1,13 +1,303 @@
|
||||
---
|
||||
title: Plugins
|
||||
title: Plugin Development
|
||||
i18nReady: true
|
||||
---
|
||||
|
||||
import Stub from '@components/Stub.astro';
|
||||
{/* TODO: Add a CLI section */}
|
||||
|
||||
<Stub>
|
||||
import CommandTabs from '@components/CommandTabs.astro';
|
||||
|
||||
- What is a Plugin?
|
||||
- [Developing a Plugin](/2/guide/plugins/develop-plugin)
|
||||
- Guide/Recipe for each plugin in https://github.com/tauri-apps/plugins-workspace/tree/v2
|
||||
{/* TODO: Link to windowing system, commands for sending messages, and event system */}
|
||||
|
||||
</Stub>
|
||||
:::tip[Plugin Development]
|
||||
|
||||
This guide is for developing Tauri plugins. If you're looking for a list of the currently available plugins and how to use them then visit the [Features and Recipes list](/2/guide/list).
|
||||
|
||||
:::
|
||||
|
||||
Plugins are able to hook into the Tauri lifecycle, expose Rust code that relies on the web view APIs, handle commands with Rust, Kotlin or Swift code, and much more.
|
||||
|
||||
Tauri offers a windowing system with web view functionality, a way to send messages between the Rust process and the web view, and an event system along with several tools to enhance the development experience. By design, the Tauri core does not contain features not needed by everyone. Instead it offers a mechanism to add external functionalities into a Tauri application called plugins.
|
||||
|
||||
A Tauri plugin is composed of a Cargo crate and an optional NPM package that provides API bindings for its commands and events. Additionally, a plugin project can include an Android library project and a Swift package for iOS. You can learn more about developing plugins for Android and iOS in the [Mobile Plugin Development guide](/2/guide/plugins/develop-mobile).
|
||||
|
||||
{/* TODO: https://github.com/tauri-apps/tauri/issues/7749 */}
|
||||
|
||||
## Naming Convention
|
||||
|
||||
{/* TODO: Add link to allowlist */}
|
||||
|
||||
Tauri plugins have a prefix (`tauri-plugin-` prefix for the Rust crate name and `@tauri-apps/plugin-` for the NPM package) followed by the plugin name. The plugin name is specified on the plugin configuration under [`tauri.conf.json > plugin`](/2/reference/config/#pluginconfig) and on the allowlist configuration.
|
||||
|
||||
By default Tauri prefixes your plugin crate with `tauri-plugin-`. This helps your plugin to be discovered by the Tauri community, but is not not a requirement. When initializing a new plugin project, you must provide its name. The generated crate name will be `tauri-plugin-{plugin-name}` and the JavaScript NPM package name will be `tauri-plugin-{plugin-name}-api` (although we recommend using an [NPM scope](https://docs.npmjs.com/about-scopes) if possible). The Tauri naming convention for NPM packages is `@scope-name/plugin-{plugin-name}`.
|
||||
|
||||
## Initialize Plugin Project
|
||||
|
||||
To bootstrap a new plugin project, run `plugin init`. If you do not need the NPM package, use the `--no-api` CLI flag.
|
||||
|
||||
<CommandTabs
|
||||
npm="npm run tauri plugin init"
|
||||
yarn="yarn tauri plugin init"
|
||||
pnpm="pnpm tauri plugin init"
|
||||
cargo="cargo tauri plugin init"
|
||||
/>
|
||||
|
||||
This will initialize the plugin and the resulting code will look like this:
|
||||
|
||||
```
|
||||
. plugin-name/
|
||||
├── src/ - Rust code
|
||||
│ ├── commands.rs - defines the commands the webview can use
|
||||
| ├── desktop.rs - desktop implementation
|
||||
│ ├── lib.rs - re-exports appropriate implementation, setup state...
|
||||
│ └── mobile.rs - mobile implementation
|
||||
├── android - Android library
|
||||
├── ios - Swift package
|
||||
├── webview-src - source code of the JavaScript API bindings
|
||||
├── webview-dist - Transpiled assets from webview-src
|
||||
├── Cargo.toml - Cargo crate metadata
|
||||
└── package.json - NPM package metadata
|
||||
```
|
||||
|
||||
{/* TODO: https://github.com/tauri-apps/tauri/issues/7749 */}
|
||||
|
||||
If you have an existing plugin and would like to add Android or iOS capabilities to it, you can use `plugin android add` and `plugin ios add` to bootstrap the mobile library projects and guide you through the changes needed.
|
||||
|
||||
## Mobile Plugin Development
|
||||
|
||||
Plugins can run native mobile code written in Kotlin (or Java) and Swift. The default plugin template includes an Android library project using Kotlin and a Swift package. It includes an example mobile command showing how to trigger its execution from Rust code.
|
||||
|
||||
Read more about developing plugins for mobile in the [Mobile Plugin Development guide](/2/guide/plugins/develop-mobile).
|
||||
|
||||
## Plugin Configuration
|
||||
|
||||
In the Tauri application where the plugin is used, the plugin configuration is specified on `tauri.conf.json` where `plugin-name` is the name of the plugin:
|
||||
|
||||
```json
|
||||
{
|
||||
"build": { ... },
|
||||
"tauri": { ... },
|
||||
"plugins": {
|
||||
"plugin-name": {
|
||||
"timeout": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The plugin's configuration is set on the `Builder` and is parsed at runtime. Here is an example of the `Config` struct being used to specify the plugin configuration:
|
||||
|
||||
```rust
|
||||
// lib.rs
|
||||
|
||||
use tauri::plugin::{Builder, Runtime, TauriPlugin};
|
||||
use serde::Deserialize;
|
||||
|
||||
// Define the plugin config
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
timeout: usize,
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
// Make the plugin config optional
|
||||
// by using `Builder::<R, Option<Config>>` instead
|
||||
Builder::<R, Config>::new("<plugin-name>")
|
||||
.setup(|app, api| {
|
||||
let timeout = api.config.timeout;
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle Events
|
||||
|
||||
Plugins can hook into several lifecycle events:
|
||||
|
||||
- [setup](#setup): Plugin is being initialized
|
||||
- [on_navigation](#on_navigation): Web view is attempting to perform navigation
|
||||
- [on_webview_ready](#on_webview_ready): New window is being created
|
||||
- [on_event](#on_event): Event loop events
|
||||
- [on_drop](#on_drop): Plugin is being deconstructed
|
||||
|
||||
There are additional [lifecycle events for mobile plugins](/2/guide/plugins/develop-mobile#lifecycle-events).
|
||||
|
||||
### setup
|
||||
|
||||
- **When**: Plugin is being initialized
|
||||
- **Why**: Register mobile plugins, manage state, run background tasks
|
||||
|
||||
```rust
|
||||
use tauri::{Manager, plugin::Builder};
|
||||
use std::{collections::HashMap, sync::Mutex, time::Duration};
|
||||
|
||||
struct DummyStore(Mutex<HashMap<String, String>>);
|
||||
|
||||
Builder::new("<plugin-name>")
|
||||
.setup(|app, api| {
|
||||
app.manage(DummyStore(Default::default()));
|
||||
|
||||
let app_ = app.clone();
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
app_.emit("tick", ());
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
```
|
||||
|
||||
### on_navigation
|
||||
|
||||
- **When**: Web view is attempting to perform navigation
|
||||
- **Why**: Validate the navigation or track URL changes
|
||||
|
||||
Returning `false` cancels the navigation.
|
||||
|
||||
```rust
|
||||
use tauri::plugin::Builder;
|
||||
|
||||
Builder::new("<plugin-name>")
|
||||
.on_navigation(|window, url| {
|
||||
println!("window {} is navigating to {}", window.label(), url);
|
||||
// Cancels the navigation if forbidden
|
||||
url.scheme() != "forbidden"
|
||||
})
|
||||
```
|
||||
|
||||
### on_webview_ready
|
||||
|
||||
- **When**: New window has been created
|
||||
- **Why**: Execute an initialization script for every window
|
||||
|
||||
```rust
|
||||
use tauri::plugin::Builder;
|
||||
|
||||
Builder::new("<plugin-name>")
|
||||
.on_webview_ready(|window| {
|
||||
window.listen("content-loaded", |event| {
|
||||
println!("webview content has been loaded");
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
### on_event
|
||||
|
||||
- **When**: Event loop events
|
||||
- **Why**: Handle core events such as window events, menu events and application exit requested
|
||||
|
||||
With this lifecycle hook you can be notified of any event loop [events](https://docs.rs/tauri/2.0.0-alpha/tauri/enum.RunEvent.html).
|
||||
|
||||
```rust
|
||||
use std::{collections::HashMap, fs::write, sync::Mutex};
|
||||
use tauri::{plugin::Builder, Manager, RunEvent};
|
||||
|
||||
struct DummyStore(Mutex<HashMap<String, String>>);
|
||||
|
||||
Builder::new("<plugin-name>")
|
||||
.setup(|app, _api| {
|
||||
app.manage(DummyStore(Default::default()));
|
||||
Ok(())
|
||||
})
|
||||
.on_event(|app, event| {
|
||||
match event {
|
||||
RunEvent::ExitRequested { api, .. } => {
|
||||
// user requested a window to be closed and there's no windows left
|
||||
|
||||
// we can prevent the app from exiting:
|
||||
api.prevent_exit();
|
||||
}
|
||||
RunEvent::Exit => {
|
||||
// app is going to exit, you can cleanup here
|
||||
|
||||
let store = app.state::<DummyStore>();
|
||||
write(
|
||||
app.path().app_local_data_dir().unwrap().join("store.json"),
|
||||
serde_json::to_string(&*store.0.lock().unwrap()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### on_drop
|
||||
|
||||
- **When**: Plugin is being deconstructed
|
||||
- **Why**: Execute code when the plugin has been destroyed
|
||||
|
||||
See [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) for more information.
|
||||
|
||||
```rust
|
||||
use tauri::plugin::Builder;
|
||||
|
||||
Builder::new("<plugin-name>")
|
||||
.on_drop(|app| {
|
||||
// plugin has been destroyed...
|
||||
})
|
||||
```
|
||||
|
||||
## Exposing Rust APIs
|
||||
|
||||
The plugin APIs defined in the project's `desktop.rs` and `mobile.rs` are exported to the user as a struct with the same name as the plugin (in pascal case). When the plugin is setup, an instance of this struct is created and managed as a state so that users can retrieve it at any point in time with a `Manager` instance (such as `AppHandle`, `App`, or` Window`) through the extension trait defined in the plugin.
|
||||
|
||||
For example, the [`global-shortcut plugin`](/2/guide/global-shortcut) defines a `GlobalShortcut` struct that can be read by using the `global_shortcut` method of the `GlobalShortcutExt` trait:
|
||||
|
||||
```rust
|
||||
use tauri_plugin_global_shortcut::GlobalShortcutExt;
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_global_shortcut::init())
|
||||
.setup(|app| {
|
||||
app.global_shortcut().register(...);
|
||||
Ok(())
|
||||
})
|
||||
```
|
||||
|
||||
## Adding Commands
|
||||
|
||||
|
||||
Commands are defined in the `commands.rs` file. They are regular Tauri applications commands. They can access the AppHandle and Window instances directly, access state, and take input the same way as application commands. Read the [Commands guide](/2/guide/commands) for more details on Tauri commands.
|
||||
|
||||
This command shows how to get access to the `AppHandle` and `Window` instance via dependency injection, and takes two input parameters (`on_progress` and `url`):
|
||||
|
||||
```rust
|
||||
use tauri::{command, ipc::Channel, AppHandle, Runtime, Window};
|
||||
|
||||
#[command]
|
||||
async fn upload<R: Runtime>(app: AppHandle<R>, window: Window<R>, on_progress: Channel, url: String) {
|
||||
// implement command logic here
|
||||
on_progress.send(100).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
To expose the command to the webview, you must hook into the `invoke_handler()` call in `lib.rs`:
|
||||
|
||||
```rust
|
||||
// lib.rs
|
||||
Builder::new("<plugin-name>")
|
||||
.invoke_handler(tauri::generate_handler![commands::upload])
|
||||
```
|
||||
|
||||
Define a binding function in `webview-src/index.ts` so that plugin users can easily call the command in JavaScript:
|
||||
|
||||
```js
|
||||
// webview-src/index.ts
|
||||
import { invoke, Channel } from '@tauri-apps/api/tauri'
|
||||
|
||||
export async function upload(url: string, onProgressHandler: (progress: number) => void): Promise<void> {
|
||||
const onProgress = new Channel<number>()
|
||||
onProgress.onmessage = onProgressHandler
|
||||
await invoke('plugin:<plugin-name>|upload', { url, onProgress })
|
||||
}
|
||||
```
|
||||
|
||||
Be sure to build the TypeScript code prior to testing it.
|
||||
|
||||
## Managing State
|
||||
|
||||
A plugin can manage state in the same way a Tauri application does. Read the [State Management guide](/2/guide/state-management) for more information.
|
||||
|
||||
28
src/content/docs/2/guide/state-management.mdx
Normal file
28
src/content/docs/2/guide/state-management.mdx
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: State Management
|
||||
---
|
||||
|
||||
import Stub from '@components/Stub.astro';
|
||||
|
||||
<Stub>
|
||||
|
||||
Define a state struct and let Tauri manage it:
|
||||
|
||||
```rust
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
use tauri::{Manager, plugin::Builder};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Store(Mutex<HashMap<usize, String>>);
|
||||
|
||||
Builder::new("<plugin-name>")
|
||||
.setup(|app, api| {
|
||||
app.manage(Store::default());
|
||||
|
||||
// retrieve the store later
|
||||
let store = app.state::<Store>();
|
||||
Ok(())
|
||||
})
|
||||
```
|
||||
|
||||
</Stub>
|
||||
@@ -19,7 +19,7 @@ You can add the Tauri CLI to your current project using your package manager of
|
||||
|
||||
:::tip[Developing a Plugin]
|
||||
|
||||
For CLI commands related to developing plugins visit the [Develop a Tauri Plugin](/2/guide/plugins/develop-plugin) guide.
|
||||
For CLI commands related to developing plugins visit the [Develop a Tauri Plugin guide](/2/guide/plugins).
|
||||
|
||||
:::
|
||||
|
||||
|
||||
Reference in New Issue
Block a user