Skip to content

Commit

Permalink
feat(tauri): webview bundle plugin for tauri v2 (#16)
Browse files Browse the repository at this point in the history
## Summary

Add `webview-bundle-tauri` crate is plugin for tauri v2.

See example code below:

```rust
use tauri::{Manager, WebviewUrl};
use webview_bundle_tauri::cache::NoopCache;
use webview_bundle_tauri::config::Config;
use webview_bundle_tauri::loader::FSLoader;

fn main() {
  tauri::Builder::default()
    .plugin(webview_bundle_tauri::init("app", |app, _api| {
      let mut dir = app.path().resource_dir()?;
      dir.push("webview-bundles");
      let config = Config::builder()
        .cache(NoopCache::default())
        .loader(FSLoader::from_dir(dir))
        .build();
      Ok(config)
    }))
    .setup(|app| {
      let window = tauri::window::WindowBuilder::new(app, "primary").build()?;
      let webview_builder = tauri::webview::WebviewBuilder::new(
        "primary",
        WebviewUrl::CustomProtocol(url::Url::parse("app://bundle_name").unwrap()),
      );
      let _webview = window.add_child(
        webview_builder,
        tauri::LogicalPosition::new(0, 0),
        window.inner_size().unwrap(),
      );
      Ok(())
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}
```

## Test Plan

Should pass CI.
  • Loading branch information
seokju-na authored Nov 1, 2024
1 parent dff213b commit 843bd84
Show file tree
Hide file tree
Showing 25 changed files with 535 additions and 4 deletions.
17 changes: 17 additions & 0 deletions .github/actions/tauri-linux-setup/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: "tauri linux setup"
description: "tauri linux setup"
runs:
using: "composite"
steps:
- run: |
sudo apt update && sudo apt install -y \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
shell: bash
2 changes: 2 additions & 0 deletions .github/workflows/prepare_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: "0"
- name: Setup tauri for linux
uses: ./.github/actions/tauri-linux-setup
- name: Setup Node.js
uses: ./.github/actions/node-setup
- name: Setup Rust
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/tauri-linux-setup
- uses: ./.github/actions/rust-setup
with:
components: clippy
Expand All @@ -74,6 +75,8 @@ jobs:
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v4
- if: ${{ matrix.settings.host == 'ubuntu-latest' }}
uses: ./.github/actions/tauri-linux-setup
- uses: ./.github/actions/rust-setup
with:
github-token: ${{ github.token }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ jobs:
steps:
- name: Git checkout
uses: actions/checkout@v4
- name: Setup tauri for linux
if: ${{ matrix.settings.host == 'ubuntu-latest' }}
uses: ./.github/actions/tauri-linux-setup
- name: Setup Node.js
uses: ./.github/actions/node-setup
if: ${{ !matrix.settings.docker }}
Expand Down Expand Up @@ -98,6 +101,8 @@ jobs:
steps:
- name: Git checkout
uses: actions/checkout@v4
- name: Setup tauri for linux
uses: ./.github/actions/tauri-linux-setup
- name: Setup Node.js
uses: ./.github/actions/node-setup
- name: Setup Rust
Expand Down
12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
[workspace]
members = ["crates/*", "packages/cli", "packages/node-binding"]
members = ["crates/*", "packages/cli", "packages/node-binding", "examples/tauri-simple"]
resolver = "2"

[workspace.dependencies]
anyhow = "1"
async-trait = "0.1.83"
bincode = "2.0.0-rc.3"
biome_console = "0.5.7"
bpaf = { version = "0.9.14", features = ["derive"] }
lz4_flex = "0.11.3"
mime_guess = "2.0.5"
napi = { version = "2.16.11", default-features = false, features = ["napi4", "async"] }
napi-build = "2.1.3"
napi-derive = "2.16.12"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri = "2"
tauri-build = "2"
thiserror = "1"
tokio = "1.40.0"
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
tracing-subscriber = "0.3.18"

# crates
webview-bundle = { version = "0.0.0", path = "./crates/webview-bundle" }
webview-bundle-cli = { version = "0.0.0", path = "./crates/webview-bundle-cli" }
webview-bundle = { version = "0.0.0", path = "./crates/webview-bundle" }
webview-bundle-cli = { version = "0.0.0", path = "./crates/webview-bundle-cli" }
webview-bundle-tauri = { version = "0.0.0", path = "./crates/webview-bundle-tauri" }

[profile.release]
lto = true
Expand Down
1 change: 1 addition & 0 deletions crates/webview-bundle-tauri/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
22 changes: 22 additions & 0 deletions crates/webview-bundle-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
authors = ["Seokju Na <[email protected]>"]
description = "TBD"
edition = "2021"
license = "MIT"
name = "webview-bundle-tauri"
repository = "https://github.com/seokju-na/webview-bundle"
version = "0.0.0"

[dependencies]
webview-bundle = { workspace = true }

async-trait = { workspace = true }
buildstructor = "0.5.4"
lru = { version = "0.12.5", optional = true }
mime_guess = { workspace = true }
tauri = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }

[features]
cache-lru = ["dep:lru"]
1 change: 1 addition & 0 deletions crates/webview-bundle-tauri/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# webview-bundle-tauri
61 changes: 61 additions & 0 deletions crates/webview-bundle-tauri/src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::hash::Hash;
use webview_bundle::Bundle;

pub trait Cache<K: Hash + Eq, V> {
fn has(&self, key: &K) -> bool;
fn get(&mut self, key: &K) -> Option<&V>;
fn set(&mut self, key: K, value: V);
}

#[derive(Clone, Default)]
pub struct NoopCache;

impl Cache<String, Bundle> for NoopCache {
fn has(&self, _key: &String) -> bool {
false
}

fn get(&mut self, _key: &String) -> Option<&Bundle> {
None
}

fn set(&mut self, _key: String, _value: Bundle) {}
}

#[cfg(feature = "cache-lru")]
#[derive(Clone)]
pub struct LruCache<K: Hash + Eq, V> {
cache: lru::LruCache<K, V>,
}

#[cfg(feature = "cache-lru")]
impl<K: Hash + Eq, V> LruCache<K, V> {
pub fn new(size: usize) -> Self {
Self {
cache: lru::LruCache::<K, V>::new(
std::num::NonZeroUsize::new(size).expect("size is not non zero"),
),
}
}

pub fn unbounded() -> Self {
Self {
cache: lru::LruCache::<K, V>::unbounded(),
}
}
}

#[cfg(feature = "cache-lru")]
impl Cache<String, Bundle> for LruCache<String, Bundle> {
fn has(&self, key: &String) -> bool {
self.cache.contains(key)
}

fn get(&mut self, key: &String) -> Option<&Bundle> {
self.cache.get(key)
}

fn set(&mut self, key: String, value: Bundle) {
self.cache.put(key, value);
}
}
32 changes: 32 additions & 0 deletions crates/webview-bundle-tauri/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use crate::cache::Cache;
use crate::loader::Loader;
use webview_bundle::Bundle;

pub struct Config<L, C>
where
L: Loader + Send + Sync,
C: Cache<String, Bundle> + Send + Sync,
{
loader: L,
cache: C,
}

#[buildstructor::buildstructor]
impl<L, C> Config<L, C>
where
L: Loader + Send + Sync,
C: Cache<String, Bundle> + Send + Sync,
{
#[builder]
pub fn new(loader: L, cache: C) -> Self {
Self { loader, cache }
}

pub fn loader(&self) -> &L {
&self.loader
}

pub fn cache(&self) -> &C {
&self.cache
}
}
7 changes: 7 additions & 0 deletions crates/webview-bundle-tauri/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
WebviewBundle(#[from] webview_bundle::Error),
}
74 changes: 74 additions & 0 deletions crates/webview-bundle-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
pub mod cache;
pub mod config;
pub mod error;
pub mod loader;

use crate::cache::Cache;
use crate::config::Config;
use crate::loader::Loader;
use std::path::Path;
use tauri::http::{Method, Response, Uri};
use tauri::plugin::{PluginApi, TauriPlugin};
use tauri::{plugin, AppHandle, Manager, Runtime};
use webview_bundle::Bundle;

pub fn init<R, L, C, F>(scheme: &'static str, config: F) -> TauriPlugin<R>
where
R: Runtime,
L: Loader + Send + Sync + 'static,
C: Cache<String, Bundle> + Send + Sync + 'static,
F: FnOnce(&AppHandle<R>, PluginApi<R, ()>) -> Result<Config<L, C>, Box<dyn std::error::Error>>
+ Send
+ 'static,
{
plugin::Builder::<R>::new("webview-bundle")
.setup(|app, api| {
let config = config(app, api)?;
app.manage(config);
Ok(())
})
.register_asynchronous_uri_scheme_protocol(scheme, move |ctx, request, responder| {
let method = request.method();
if method != Method::GET {
responder.respond(Response::builder().status(405).body(vec![]).unwrap());
return;
}
let uri = request.uri().clone();
let app = ctx.app_handle().clone();
tauri::async_runtime::spawn(async move {
let config = app.state::<Config<L, C>>();
let bundle = config.loader().load(&uri).await.unwrap();
let filepath = uri_to_filepath(&uri);
let buf = bundle.read_file(&filepath).unwrap(); // TODO: handle file not found error
responder.respond(
Response::builder()
.header("content-type", mime_types_from_filepath(&filepath))
.header("content-length", buf.len())
.status(200)
.body(buf)
.unwrap(),
);
});
})
.build()
}

fn uri_to_filepath(uri: &Uri) -> String {
let filepath = uri.path()[1..].to_string();
if Path::new(&filepath).extension().is_some() {
return filepath;
}
let index_html = "index.html".to_string();
if filepath.is_empty() {
return index_html;
}
[filepath, index_html].join("/")
}

fn mime_types_from_filepath(filepath: &String) -> String {
let guess = mime_guess::from_path(filepath);
guess
.first()
.map(|x| x.to_string())
.unwrap_or("text/plain".to_string())
}
59 changes: 59 additions & 0 deletions crates/webview-bundle-tauri/src/loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use async_trait::async_trait;
use std::path::{Path, PathBuf};
use tauri::http::Uri;
use webview_bundle::Bundle;

#[async_trait]
pub trait Loader: Send + Sync {
type Error: std::error::Error;
fn get_bundle_name(&self, _: &Uri) -> Option<String> {
None
}
async fn load(&self, uri: &Uri) -> Result<Bundle, Self::Error>;
}

pub struct FSLoader {
pub resolve_file_path: Box<dyn Fn(&Uri) -> PathBuf + Send + Sync>,
}

impl FSLoader {
pub fn new<R: Fn(&Uri) -> PathBuf + Send + Sync + 'static>(resolve_file_path: R) -> Self {
Self {
resolve_file_path: Box::new(resolve_file_path),
}
}

pub fn from_dir<P: AsRef<Path>>(dir: P) -> Self {
let dir_path_buf = dir.as_ref().to_path_buf();
Self::new(move |uri| {
let host = uri.host().unwrap_or_default();
let filename = match host.ends_with(".wvb") {
true => host.to_string(),
false => format!("{host}.wvb"),
};
let mut filepath = dir_path_buf.clone();
filepath.push(filename);
filepath
})
}
}

#[async_trait]
impl Loader for FSLoader {
type Error = crate::error::Error;

fn get_bundle_name(&self, uri: &Uri) -> Option<String> {
let filepath_buf = (self.resolve_file_path)(uri);
let filepath = Path::new(&filepath_buf);
filepath
.file_name()
.map(|x| x.to_string_lossy().to_string())
}

async fn load(&self, uri: &Uri) -> Result<Bundle, Self::Error> {
let filepath_buf = (self.resolve_file_path)(uri);
let buf = tokio::fs::read(&filepath_buf).await?;
let bundle = webview_bundle::decode(buf)?;
Ok(bundle)
}
}
2 changes: 2 additions & 0 deletions examples/tauri-simple/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gen/schemas/
bundle.wvb
15 changes: 15 additions & 0 deletions examples/tauri-simple/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
edition = "2021"
name = "example-tauri-simple"
publish = false
version = "0.1.0"

[build-dependencies]
tauri-build = { workspace = true }

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
tauri = { workspace = true, features = ["unstable", "tracing"] }
url = { version = "2", features = ["serde"] }
webview-bundle-tauri = { workspace = true }
3 changes: 3 additions & 0 deletions examples/tauri-simple/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
13 changes: 13 additions & 0 deletions examples/tauri-simple/bundle/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
<p>Test paragraph</p>
<button id="btn">Click Me</button>
<script src="./index.js"></script>
</body>
</html>
Loading

0 comments on commit 843bd84

Please sign in to comment.