class =
"handle-container" >
<div class ="minimize" @click ="minimize" > div >
class =
"maximize" @click=
"maximize" >
div > class =
"close" @click=
"close" >
div ></div>
然后定义一下icon的样式:
.minimize {
background : center / 20px no-repeat url ("./assets/minimize.svg" );
}
.maximize {
background : center / 20px no-repeat url ("./assets/maximize.svg" );
}
.unmaximize {
background : center / 20px no-repeat url ("./assets/unmaximize.svg" );
}
.close {
background : center / 20px no-repeat url ("./assets/close.svg" );
}
.close :hover {
background-color : #e53935 ;
background-image : url ("./assets/close-hover.svg" );
}
但是在Tauri中,要实现自定窗口首先需要在窗口创建的时候设置decoration无装饰样式,比如这样:(也可以在tauri.config.json中设置,道理是一样的)
let window = WindowBuilder::new (
&app,
"main" ,
WindowUrl ::App("/src/index.html" .into()),
)
.inner_size(400. , 300. )
.visible(true )
.resizable(false )
.decorations(false )
.build()
.unwrap();
然后就是和Electron类似,自己画一个控制栏,详细的代码可以参考这里:https://v1.tauri.app/v1/guides/features/window-customization/
<div data-tauri-drag-region class ="titlebar" >
<div class ="titlebar-button" id ="titlebar-minimize" >
<img
src ="https://api.iconify.design/mdi:window-minimize.svg"
alt ="minimize"
/>
div >
<div class ="titlebar-button" id ="titlebar-maximize" >
<img
src ="https://api.iconify.design/mdi:window-maximize.svg"
alt ="maximize"
/>
div >
<div class ="titlebar-button" id ="titlebar-close" >
<img src ="https://api.iconify.design/mdi:close.svg" alt ="close" />
div >
div >
通过使用窗口单例模式,可以确保应用程序在用户尝试多次打开时只会有一个主窗口实例,从而提高用户体验并避免不必要的资源占用。在Electron中可以很容易做到这一点:
app.on ('second-instance' , (event , commandLine, workingDirectory) => {
if (myWindow) {
mainWindow.show()
if (myWindow.isMinimized()) myWindow.restore()
myWindow.focus()
}
})
但是,在Tauri中,我需要引入一个单例插件才可以:
use tauri::{Manager};
#[derive(Clone, serde::Serialize)]
struct Payload {
args: Vec,
cwd: String,
}
fn main () {
tauri::Builder::default ()
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
app.emit("single-instance" , Payload { args: argv, cwd }).unwrap();
}))
.run(tauri::generate_context!())
.expect("error while running tauri application" );
}
其在Windows下判断单例的核心原理是借助了windows_sys这个Crate中的CreateMutexW API来创建一个互斥体,确保只有一个实例可以运行,并在用户尝试启动多个实例时,聚焦于已经存在的实例并传递数据,简化后的代码大致如下:
pub fn init(f: Box>) -> TauriPlugin {
plugin::Builder::new("single-instance" )
.setup(|app| {
let hmutex = unsafe {
CreateMutexW(std::ptr::null(), true .into(), mutex_name.as_ptr())
};
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe {
let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr());
if hwnd != 0 {
SendMessageW(hwnd, WM_COPYDATA, 0 , &cds as *const _ as _);
app.exit (0 );
}
}
}
Ok(())
})
.build()
}
(注意:这里有坑,如果你的应用需要实现一个重新启动功能,那么在单例模式下将不会生效,核心原因是因为应用重启的逻辑是先打开一个新的实例再关闭旧的运行实例。而打开新的实例在单例模式下就被阻止了,这块的详细原因和解决方案我们已经给Tauri提了PR:https://github.com/tauri-apps/tauri/pull/11684)
消息通知是商家客服桌面端应用必不可少的能力,消息通知能力一般可以分为以下两种:
前面我们有提到,在Electron中,我们需要显示来自渲染进程的通知,那么可以直接使用HTML5的Web API来发送一条系统消息通知:
function notifyMe ( ) {
if (!("Notification" in window )) {
alert("当前浏览器不支持桌面通知" );
} else if (Notification.permission === "granted" ) {
const notification = new Notification("你好!" );
} else if (Notification.permission !== "denied" ) {
Notification.requestPermission().then((permission ) => {
if (permission === "granted" ) {
const notification = new Notification("你好!" );
}
});
}
}
如果我们需要为消息通知添加点击回调事件,那么我们可以这么写:
notification.onclick = (event ) => {};
当然,Electron也提供了主进程使用的API,更多的能力可以直接参考Electron的官方文档:https://www.electronjs.org/zh/docs/latest/api/%E9%80%9A%E7%9F%A5。
然而,对于Tauri来说,只实现了第1个能力,也就是消息触达。Tauri本身不支持点击回调的功能,这就导致了用户发来了一个消息,但是业务无法感知客服点击消息的事件。而且原生的Web API也是Tauri自己写的,原理还是调用了Rust的通知能力。接下来,我也会详细介绍一下我们是如何扩展消息点击回调能力的。
Tauri在Rust层,我们可以通过下面这段代码来调用Notification:
use tauri::api::notification::Notification;
let app = tauri::Builder::default ()
.build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json" ))
.expect("error while building tauri application" );
Notification::new (&app.config().tauri.bundle.identifier)
.title("New message" )
.body("You've got a new message." )
.show();
Notification::new (&app.config().tauri.bundle.identifier)
.title("Tauri" )
.body("Tauri is awesome!" )
.notify(&app.handle())
.unwrap();
app.run(|_app_handle, _event| {});
Tauri的Notification Rust实现源码位置在:https://github.com/tauri-apps/tauri/blob/1.x/core/tauri/src/api/notification.rs这个文件中,其中看一下show函数的实现:
pub fn show(self ) -> crate::api::Result {
return Ok(());
{
let mut notification = notify_rust::Notification::new();
if let Some(body) = self .body {
notification.body(&body);
}
if let Some(title) = self .title {
notification.summary(&title);
}
if let Some(icon) = self .icon {
notification.icon(&icon);
} else {
notification.auto_icon();
}
crate::async_runtime::spawn(async move {
let _ = notification.show();
});
Ok(())
}
}
pub fn notify(self , app: &crate::AppHandle) -> crate::api::Result {
{
if crate::utils::platform::is_windows_7() {
self .notify_win7(app)
} else {
self .show()
}
}
{
self .show()
}
}
fn notify_win7(self , app: &crate::AppHandle) -> crate::api::Result {
let app = app.clone ();
let default_window_icon = app.manager.inner.default_window_icon.clone ();
let _ = app.run_on_main_thread(move || {
let mut notification = win7_notifications::Notification::new();
if let Some(body) = self .body {
notification.body(&body);
}
if let Some(title) = self .title {
notification.summary(&title);
}
notification.silent(self .sound.is_none());
if let Some(crate::Icon::Rgba {
rgba,
width,
height,
}) = default_window_icon
{
notification.icon(rgba, width, height);
}
let _ = notification.show();
});
Ok(())
}
}
这里,我们可以看到notify
函数非win7环境下show
函数调用的是notify_rust这个库,而在win7环境下调用的是win7_notifications这个库。而notify_rust
这个库,本身确实未完成实现对MacOS和Windows点击回调事件。
所以我们需要自定义一个Notification
的Tauri插件,实现对点击回调的能力。(因为篇幅原因,这里只介绍一些核心的实现逻辑)
MacOS 支持消息点击回调能力
notify_rust
在Mac上实现消息通知是基于Mac_notification_sys
这个库的,这个库本身是支持对点击action
的response,只是notify_rust
没有处理而已,所以我们可以为notify_rust
增加对Mac
上点击回调的处理能力:
fn show_mac_action(
window: tauri::Window,
app_id: String,
notification: Notification,
action_id: String,
action_name: String,
handle: CallbackFn,
sid: String,
) {
let window_ = window.clone ();
use mac_notification_sys ::{
Notification as MacNotification ,
MainButton ,
Sound ,
NotificationResponse ,
};
match MacNotification::default()
.title(notification.summary.as_str())
.message(¬ification.body)
.sound(Sound::Default)
.maybe_subtitle(notification.subtitle.as_deref())
.main_button(MainButton::SingleAction(&action_name))
.send()
{
Ok(response) => match response {
NotificationResponse::ActionButton(id) => {
if action_name.eq(&id) {
let js = tauri::api::ipc::format_callback(handle, &id)
.expect("点击 action 报错" );
window_.eval (js.as_str());
};
}
NotificationResponse::Click => {
let data = &sid;
let js = tauri::api::ipc::format_callback(handle, &data)
.expect("消息点击报错" );
window_.eval (js.as_str());
}
_ => {}
},
Err(err) => println!("Error handling notification {}" , err),
}
}
Win 10上支持消息点击回调能力
在Windows 10操作系统中,notify_rust
则是通过winrt_notification
这个Crate来发送消息通知,winrt_notification 则是调用的windows
这个crate来实现消息通知,windows这个crate的官方描述是:为Rust开发人员提供了一种自然和习惯的方式来调用Windows API。这里,主要会用到以下几个方法:
windows::UI::Notifications::ToastNotification::CreateToastNotification:这个函数的作用是根据指定的参数创建一个Toast通知对象,可以设置通知的标题、文本内容、图标、音频等属性,并可以指定通知被点击时的响应行为。通过调用这个函数,可以在Windows应用程序中创建并显示自定义的Toast通知,向用户展示相关信息。
windows::Data::Xml::Dom::XmlDocument:这是一个用于在Windows应用程序中创建和处理XML文档的类。它主要提供了一种方便的方式来创建、解析和操作XML数据。
windows::UI::Notifications::ToastNotificationManager::CreateToastNotifierWithId:通过调用CreateToastNotifierWithId函数,可以创建一个Toast通知管理器对象,并指定一个唯一的标识符。这个标识符通常用于标识应用程序或者特定的通知渠道,以确保通知的正确分发和管理。创建了Toast通知管理器之后,就可以使用它来生成和发送Toast通知,向用户展示相关信息,并且可以根据标识符进行个性化的通知管理。
windows::Foundation::TypedEventHandler:这是Windows Runtime API中的一个委托(delegate)类型。在Windows Runtime中,委托类型用于表示事件处理程序,允许开发人员编写事件处理逻辑并将其附加到特定的事件上。
所以,要想在> win7的操作系统中显示消息同时的主要流程大致是:
通过XmlDocument来创建一个Xml消息通知模板。
然后将创建好的Xml消息模板作为CreateToastNotification的入参来创建一个toast通知。
最后调用CreateToastNotifierWithId来创建一个Toast通知管理器对象,创建成功后显示toast。
通过TypedEventHandler监听用户点击事件并完成回调触发
但是winrt_notification
这个库,只完成了1-3步骤,所以我们需要手动实现步骤4。核心代码如下:
fn show_win_action(
window: tauri::Window,
app_id: String,
notification: Notification,
action_id: String,
action_name: String,
handle: CallbackFn,
sid: String,
) {
let window_ = window.clone ();
let duration = match notification.timeout {
notify_rust::Timeout::Default => "duration=\"short\"" ,
notify_rust::Timeout::Never => "duration=\"long\"" ,
notify_rust::Timeout::Milliseconds(t) => {
if t >= 25000 {
"duration=\"long\""
} else {
"duration=\"short\""
}
}
};
let template_binding = "ToastGeneric" ;
let toast_xml = windows::Data::Xml::Dom::XmlDocument::new().unwrap();
if let Err(err) = toast_xml.LoadXml(&windows::core::HSTRING::from(format!(
"
{}
{}
{}{}
" ,
duration,
String::new(),
template_binding,
¬ification.icon,
¬ification.summary,
notification.subtitle.as_ref().map_or("" , AsRef::as_ref),
¬ification.body,
))) {
println!("Error creating windows toast xml {}" , err);
return ;
};
let toast_notification =
match windows::UI::Notifications::ToastNotification::CreateToastNotification(&toast_xml)
{
Ok(toast_notification) => toast_notification,
Err(err) => {
println!("Error creating windows toast {}" , err);
return ;
}
};
let handler = windows::Foundation::TypedEventHandler::new(
move |_sender: &Option<:ui::notifications::toastnotification>,
result: &Option<:core::iinspectable>| {
let event: Option<
windows::core::Result<:ui::notifications::toastactivatedeventargs>,
> = result.as_ref().map(windows::core::Interface::cast);
let arguments = event
.and_then(|val| val.ok())
.and_then(|args| args.Arguments().ok());
if let Some(val) = arguments {
let mut js;
if val.to_string_lossy().eq(&action_id) {
js = tauri::api::ipc::format_callback(handle, &action_id)
.expect("消息点击报错" );
} else {
let data = &sid;
js = tauri::api::ipc::format_callback(handle, &data)
.expect("消息点击报错" );
}
let _ = window_.eval (js.as_str());
};
Ok(())
},
);
match windows::UI::Notifications::ToastNotificationManager::CreateToastNotifierWithId(
&windows::core::HSTRING::from(&app_id),
) {
Ok(toast_notifier) => {
if let Err(err) = toast_notifier.Show(&toast_notification) {
println!("Error showing windows toast {}" , err);
}
}
Err(err) => println!("Error handling notification {}" , err),
}
}
Win 7上支持消息通知点击回调能力
在Windows 7中,Tauri调用的是win7_notifications
这个库,这个库本身也没有实现对消息点击的回调处理,我们需要扩展win7_notifications
的能力来实现对消息通知的回调事件。我们希望这个库可以这样调用:
win7_notify::Notification::new()
.appname(&app_name)
.body(&body)
.summary(&title)
.timeout(duration)
.click_event(move |str| {
let data = &sid;
let js = tauri::api::ipc::format_callback(handle, &data)
.expect("消息点击报错" );
let _ = window_.eval (js.as_str());
})
.show();
而我们要做的,就是为win7_notify
这个库中的Notification结构体增加一个click_event
函数,这个函数支持传入一个闭包,这个闭包在点击消息通知的时候执行。
pub struct Notification {
pub click_event: Option <Arc Fn (&str) + Send >>,
}
impl Notification {
pub fn click_event<F : Fn (&str) + Send + 'static >(&mut self , func : F ) -> &mut Notification {
self .click_event = Some (Arc ::new(func ));
self
}
// 支持对 click_event 的调用
fn perform_click_event (&self , message: &str) {
if let Some (ref click_event) = self .click_event {
click_event(message);
}
}
}
pub unsafe extern "system" fn window_proc(
hwnd: HWND ,
msg: u32,
wparam: WPARAM ,
lparam: LPARAM ,
) -> LRESULT {
let mut userdata = GetWindowLongPtrW (hwnd, GWL_USERDATA );
match msg {
w32wm::WM_LBUTTONDOWN => {
let (x, y) = (GET_X_LPARAM (lparam), GET_Y_LPARAM (lparam));
let userdata = userdata as *mut WindowData ;
let notification = &(*userdata).notification;
let data = "default" ;
notification.perform_click_event(&data);
if util::rect_contains(CLOSE_BTN_RECT_EXTRA , x as i32, y as i32) {
println !("close" );
close_notification(hwnd)
}
DefWindowProcW (hwnd, msg, wparam, lparam)
}
}
}
Tauri本身不支持Notification的点击事件,需要自行实现。
需要对不同操作系统分别实现点击回调能力。
MacOS mac_notification_sys库本来就有点击回调,只是Tauri没有捕获处理,需要自定义捕获处理逻辑就好了。
Windows > 7中,通过windows
这个crate,来完成调用Windows
操作系统API的能力,但是winrt_notification
这个库并没有实现对Windows
API回调点击的捕获处理,所以需要重写winrt_notification
这个库。
Windows 7中,消息通知其实是通过绘制窗口和监听鼠标点击来触发的,但是win7_notify
本身也没有支持用户对点击回调的捕获,也需要扩展这个库的点击捕获能力。
Tauri 1.3版本之前,应用程序在Windows上使用的是WiX(Windows Installer)Toolset v3工具进行构建,构建产物是Microsoft安装程序(.msi文件)。1.3之后,使用的是NSIS来构建应用的xxx-setup.exe
安装包。
Tauri CLI默认情况下使用当前编译机器的体系结构来编译可执行文件。假设当前是在64位计算机上开发,CLI将生成64位应用程序。如果需要支持32位计算机,可以使用--target
标志使用不同的Rust目标编译应用程序:
tauri build --target i686-pc-windows-msvc
为了支持不同架构的编译,需要为Rust添加对应的环境支持,比如:
rustup target add i686-pc-windows-msvc
其次,需要为构建增加不同的环境变量,以便为了在不同的环境进行代码测试,对应到package.json
中的构建代码:
{
"scripts" : {
"tauri-build-win:t1" : "tauri build -t i686-pc-windows-msvc -c src-tauri/t1.json" ,
"tauri-build-win:pre" : "tauri build -t i686-pc-windows-msvc -c src-tauri/pre.json" ,
"tauri-build-win:prod" : "tauri build -t i686-pc-windows-msvc" ,
}
}
-c
参数指定了构建的配置文件路径,Tauri会和src-tauri
中的tarui.conf.json
文件进行合并。除此之外,还可以通过tarui.{{platform}}.conf.json
的形式指定不同平台的独特配置,优先级关系:
-c path
>> tarui.{{platform}}.conf.json
>> tarui.conf.json
Webview 2
Tauri在Windows 7
上运行有两个东西需要注意,一个是Tauri的前端跨平台在Windows
上依托于Webview2
但是Windows 7
中并不会内置Webview2
因此我们需要在构建时指明引入Webview
的方式:
综合比较下来,embedBootstrapper
目前是比较好的方案,一方面可以减少安装包体积,一方面减少不必要的静态资源下载。
Windows 7一些特性
在Tauri中,会通过"Windows7-compat
"来构建一些Win7
特有的环境代码,比如:
#[cfg(feature = "windows7-compat" )]
{
}
在Tauri文档中也有相关介绍,主要是在使用Notification
的时候,需要加入Windows7-compat
特性。不过,因为 Tauri 对Notification
的点击事件回调是不支持,所以我重写了Tauri的所有Notification
模块,已经内置了Windows7-compat
能力,因此可以不用设置了。
MacOS操作系统也有M1和Intel的区分,所以为了可以构建出兼容两个版本的产物,我们需要使用 universal-apple-darwin
模式来编译:
{ "scripts": { "tauri-build:t1": "tauri build -t universal-apple-darwin -c src-tauri/t1.json", "tauri-build:pre": "tauri build -t universal-apple-darwin -c src-tauri/pre.json", "tauri-build:prod": "tauri build -t universal-apple-darwin" }}br
对于Tauri来说,应用更新的详细配置步骤可以直接看官网的介绍:https://tauri.app/zh-cn/v1/guides/distribution/updater/。这里为了方便大家理解,简单画了个更新流程图:
pub const EVENT_INSTALL_UPDATE: &str = "tauri://update-install";br
Tauri主进程Updater模块会响应这个事件,执行download_and_install
函数
通过tauri.config.json中配置的endpoints
来寻找下载地址
下载endpoints
服务器上的zip包内容并解压存储到一个临时文件夹,Windows中大概位置在C:\Users\admin\AppData\Local\Temp
这里。
然后通过PowerShe ll来执行下载的 setup.exe
文件:
["-NoProfile", "-WindowStyle", "Hidden", "Start-Process"],这些参数告诉PowerShell在后台运行,不显示任何窗口,并启动一个新的进程。
if found_path.extension() == Some(OsStr::new("exe" )) {
let mut installer_path = std::ffi::OsString::new();
installer_path.push("\"" );
installer_path.push(&found_path);
installer_path.push("\"" );
let installer_args = [
config
.tauri
.updater
.windows
.install_mode
.nsis_args()
.iter()
.map(ToString::to_string)
.collect(),
vec!["/ARGS" .to_string()],
current_exe_args,
config
.tauri
.updater
.windows
.installer_args
.iter()
.map(ToString::to_string)
.collect::>(),
]
.concat();
let mut cmd = Command::new(powershell_path);
cmd
.args(["-NoProfile" , "-WindowStyle" , "Hidden" , "Start-Process" ])
.arg(installer_path);
if !installer_args.is_empty() {
cmd.arg("-ArgumentList" ).arg(installer_args.join(", " ));
}
cmd
.spawn()
.expect("Running NSIS installer from powershell has failed to start" );
exit (0 );
}
在通过PowerShell
启动应用安装程序的时候,就会使用到tauri.config.json
中配置的updater.windows.installMode
功能:
"basicUi":指定安装过程中包括最终对话框在内的基本用户界面,需要用户手动点击下一步。
"quiet":安静模式表示无需用户交互。如果安装程序需要管理员权限(WiX),则需要管理员权限。
"passive":会显示一个只有安装进度条的UI,安装过程用户无需参与。
需要注意的是:如果以为更新是增量更新,不会卸载之前已经安装好的应用程序只更新需要变更的部分。其实是不对的,整个安装过程可以理解为Tauri在后台帮你重新下载了一个最新的安装包,然后帮你重新安装了一下。
总结:更新的核心原理就是通过使用Windows的PowerShell来对下载后的安装包进行open
。然后由安装包进行安装。
为什么我要花这么大的篇幅来介绍 Tauri 的更新原理呢?
这是因为我们在更新的过程中碰到了两个比较大的问题:
这些都是因为Tauri
直接使用 Powershell
的问题,那需要怎么改呢?很简单,那就是使用Windows
操作系统提供的ShellExecuteW
来运行安装程序,核心代码如下:
windows ::Win32 ::UI ::Shell ::ShellExecuteW(
0,
operation .as_ptr (),
file .as_ptr (),
parameters .as_ptr (),
std ::ptr ::null() ,
SW_SHOW ,
)
但是这块是Tauri
的源码,我们没法直接修改,但这个问题的解决方法我们已经给Tauri
提了PR
并已合入到官方的1.6.8
正式版本当中:https://github.com/tauri-apps/tauri/pull/9818
所以,你要做的就是确保Tauri升级到v1.6.8
及以后版本。
Tauri应用程序签名可以分成2个部分,第一部分是应用程序签名,第二部分是安装包程序签名,官网上介绍的签名方法需要配置tauri.config.json
中如下字段:
"windows" : {
"certificateThumbprint" : "xxx" ,
"digestAlgorithm" : "sha256" ,
"timestampUrl" : "http://timestamp.comodoca.com"
}
如果你按照官方的步骤来进行签名:https://v1.tauri.app/zh-cn/v1/guides/distribution/sign-windows/,很快就会发现问题所在:官网中签名有一个重要的步骤就是导出一个.pfx
文件,但是现在业界签名工具基本上都是采用签名狗的方式进行的,这是一个类似于U盾
签名工具,需要插入电脑中才可以进行签名,不支持直接导出.pfx
格式的文件:
所以我们需要额外处理一下:
签名狗支持导出一个.ce
rt
证书,可以查看到证书的指纹:
这里证书的指纹对应的就是certificateThumbprint
字段。
然后需要插入我们在签名机构购买的USB key。这样,在构建的时候,就会提示让我们输入密码:
到这里就可以完成对应用程序的签名。
不过对于我们而言,USB key签名狗是整个公司共享的,通常不在前端开发手里(尤其是异地办公)。一种做法是在Tauri
构建的过程中,对于需要签名的软件提供一个signCommand
命令钩子,并为这个命令传入文件的路径,然后交由开发者对文件进行自行签名(比如上传到拥有签名工具的电脑,上传上去后,远程进行签名,签名完成再下载)。所以这就需要让Tauri
将签名功能暴露出来,让我们自行进行签名,比如这样:
{
"signCommand" : "signtool.exe --host xxxx %1"
}
该命令中包含一个%1
,它只是二进制路径的占位符,Tauri
在构建的时候会将需要签名的文件路径替换掉%1
。
这个功能官网上还没有更新相关的介绍,所以你可能看不到这块的使用方式,因为也是我们最近提交的PR:https://github.com/tauri-apps/tauri/pull/9902。不过目前,这个PR已经被合入Tauri
的主版本中,你要做的就是就是升级Tauri
到1.7.0
升级@tauri-apps/cli
到1.6.0
。
经过我们的不懈努力(不断地填坑)到目前,得物商家客服Tauri
版本终于如期上线,基于Tauri
迁移带来的收益如下:
整体性能测试相比之前的Electron
应用有比较明显的提升:
包体积7M,Electron 80M下降91.25%。
平均内存占用249M Electron 497M下降49.9%。
平均CPU占用百分比20%,Electron 63.5%下降 63.19%。
整体在性能体验上有一个非常显著改善。但是,这里也暴露出使用Tauri
的一些问题。
直到2024年的今天,Tauri
依然还不是特别完美,目前官方主要精力是放在了2.0
的开发上,对于1.x
的版本维护显得力不从心,主要原因也是因为官方人少。
比如,Tauri: dev
分支上,主要贡献者(> 30 commit)只有4个人;相对于Electron:主要贡献者有10人。
除此之外,Electron实现了对Chromium
的高级定制,因此在Electron中,我们可以使用BrowserView
这样的功能,相对于Electron来说,Tauri目前所做的仅仅是对Webview的封装,Webview不支持的功能暂时都不行。另外,系统性的API确实少的可怜。如果要实现一些其他的功能,比如自动更新显示进度条等能力,就不得不使用Rust来扩展API。然后Rust
语言学习成本是有一点的,所以,也给我们日常开发带来了不少挑战。
因为Tauri在Windows系统上比较依托于Webview2作为渲染的容器,虽然Tauri提供了检测本地电脑是否有安装Webview2以及提供联网下载的能力,但是因为Windows电脑千奇百怪,经常会出现未内置Webview2的Windows电脑下载不成功而导致程序无法启动的情况:
对于这种情况,我们虽然可以将Webview2内置到安装包里面,在用户安装的时候进行内置解压安装,但是这样包体积就跟Electron相差不大。
我们在将得物商家客服迁移到Tauri的过程中,就遇到了非常多的问题,有些问题是Tauri
的bug
。有些问题是Tauri
的feature不够,有的是Rust
社区的问题。单纯这一个迁移工作,我们就为Tauri
社区共享了7个左右的PR:
多窗口失去焦点无法闪烁:https://github.com/tauri-apps/tao/pull/931
单窗口最小化后消息通知无法闪烁:https://github.com/tauri-apps/tao/pull/947
在有病毒防护的Windows电脑中,应用无法正常更新:https://github.com/tauri-apps/tauri/pull/9818
拓展tauri v1.x自定义签名的能力:https://github.com/tauri-apps/tauri/pull/9902
win7下消息提醒会导致主窗口失去焦点:https://github.com/tauri-apps/win7-notifications/pull/65
win7下消息提醒过多会导致窗口崩溃:https://github.com/tauri-apps/win7-notifications/pull/69
Windows单例模式下重启功能不生效:https://github.com/tauri-apps/tauri/pull/11684
在遇到这些问题时,真的特别让人头大,因为社区几乎没有这些问题的答案,需要我们自己去翻Tauri的源码实现,有些是涉及到操作系统底层的一些API,因此我们必须要去看一些操作系统的API介绍和出入参说明,才能更好的理解Tauri的代码实现意图,才能解决我们碰到的这些问题。
另外,Tauri和操作系统系统相关的源码都是基于Rust来编写的,也为我们的排查和解决增加了不少难度。最后一句名言和读者共勉:纸上得来终觉浅,绝知此事要躬行。
预览时标签不可点