Tauri:將Javascript與Rust結(jié)合構(gòu)建GUI桌面應(yīng)用
我們重新審視 Tauri,這是一個(gè)使用任何前端框架和 Rust 核心構(gòu)建桌面應(yīng)用程序的框架。我們查看了 2.0 beta 版。
譯自Tauri: Mixing JavaScript With Rust for GUI Desktop Apps,作者 David Eastman。
在我 2022 年 1 月對(duì) Tauri 的首次評(píng)論中,我指出它是一個(gè)框架,可以使用任何前端框架和 Rust 內(nèi)核構(gòu)建桌面應(yīng)用程序。由于 Rust 語(yǔ)言在過(guò)去兩年半的時(shí)間里在流行度方面取得了顯著進(jìn)步,我認(rèn)為再次回顧 Tauri 是值得的——尤其是因?yàn)樗罱l(fā)布了 2.0 版本。
Tauri 的宣傳語(yǔ)是“構(gòu)建一個(gè)針對(duì)多平臺(tái)部署的優(yōu)化、安全且與前端無(wú)關(guān)的應(yīng)用程序”,這與之前的說(shuō)法一致,但更多的部署目標(biāo)使其更符合我最近發(fā)布的其他產(chǎn)品。額外的好處是,可以使用熟悉的 Web 方法構(gòu)建桌面和移動(dòng)應(yīng)用程序。
我們獲得了 Rust 的安全性,但也獲得了 Web 開發(fā)的熟悉性和靈活性。
我們將嘗試看看構(gòu)建一個(gè)可以在我的 Mac 上完全打包運(yùn)行的 UI 應(yīng)用程序的路徑是否變得更加平滑。Tauri 仍然將自己稱為一個(gè)“工具包”,這仍然是事實(shí)。
從概念上講,Tauri 充當(dāng)一個(gè)靜態(tài) Web 主機(jī)。因此,Tauri 與 Rust 框架和系統(tǒng)的原生 Web 視圖協(xié)同工作,以輸出一個(gè)體積適中的可執(zhí)行應(yīng)用程序。理論上,我們獲得了 Rust 的安全性,但也獲得了 Web 開發(fā)的熟悉性和靈活性。
入門路線看起來(lái)更新了一些,現(xiàn)在流行的是單行啟動(dòng)。在我們開始之前,我懷疑我有一個(gè)舊的 Rust 安裝,所以我應(yīng)該更新它。使用先決條件說(shuō)明:
圖片
最后,它提醒您啟動(dòng)一個(gè)新的 shell 或使用 env 文件。我注意到所有這些都有一種新的更友好的口吻——就好像,也許,Rust 現(xiàn)在很流行!
好的,現(xiàn)在我應(yīng)該可以使用 Tauri 的單行命令:
圖片
請(qǐng)注意,我們已經(jīng)進(jìn)入了 Tauri 2.0 的測(cè)試版。
模板安裝選項(xiàng)認(rèn)識(shí)到工具包的多樣性。我可以使用 .NET,但我將使用 JavaScript 來(lái)獲得更通用的視圖。顯然,Rust 也可用。
圖片
我保留了我稍微舊的 npm/node 組合并構(gòu)建了我的模板:
圖片
然后我們?cè)陂_發(fā)環(huán)境中運(yùn)行模板:
圖片
這將構(gòu)建我們開始所需的所有包,第一次需要幾分鐘。這些將是 Rust 與您的操作系統(tǒng)窗口通信的方式。最終,它將啟動(dòng)應(yīng)用程序:
圖片
因此,我們啟動(dòng)了一個(gè)應(yīng)用程序,它彈出了,在我的托盤中顯示為一個(gè)標(biāo)準(zhǔn)的 Mac 應(yīng)用程序。
好的,讓我們看看它是如何組成的。在我們深入研究之前,請(qǐng)注意,點(diǎn)擊圖標(biāo)會(huì)啟動(dòng)一個(gè)瀏覽器頁(yè)面,在文本框中輸入您的姓名并按下按鈕會(huì)顯示一個(gè)問(wèn)候語(yǔ):
圖片
這將幫助我們稍后找出 Rust 的一部分。代碼結(jié)構(gòu)是人們對(duì) Web 應(yīng)用程序的期望:
圖片
我選擇了原生 JavaScript,因此我們?cè)谀0逯械玫搅艘粋€(gè)非常原生的index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri App</title>
<script type="module" src="/main.js" defer></script>
</head>
<body>
<div class="container">
<h1>Welcome to Tauri!</h1>
<div class="row">
<a target="_blank">
<img src="/assets/tauri.svg" class="logo tauri" alt="Tauri logo" />
</a>
<a target="_blank" >
<img src="/assets/javascript.svg" class="logo vanilla" alt="JavaScript logo" />
</a>
</div>
<p>Click on the Tauri logo to learn more about the framework</p>
<form class="row" id="greet-form">
<input id="greet-input" placeholder="Enter a name..." />
<button type="submit">Greet</button>
</form>
<p id="greet-msg"></p>
</div>
</body>
</html>
中央div顯示一個(gè)錨點(diǎn)中的圖像,該錨點(diǎn)處理鏈接行為。請(qǐng)注意,JavaScript 位于main.js中,窗口本身的應(yīng)用程序標(biāo)題與這里定義的標(biāo)題不同。我們有一個(gè)非常老式的form用于輸入文本。因此,我們知道我們將不得不處理該表單以提取輸入的名稱,并將結(jié)果放置在最終的p中。這是main.js的內(nèi)容:
const { invoke } = window.__TAURI__.core;
let greetInputEl;
let greetMsgEl;
async function greet() {
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
greetMsgEl.textContent = await invoke("greet", { name: greetInputEl.value });
}
window.addEventListener("DOMContentLoaded", () => {
greetInputEl = document.querySelector("#greet-input");
greetMsgEl = document.querySelector("#greet-msg");
document.querySelector("#greet-form").addEventListener("submit", (e) => {
e.preventDefault();
greet();
});
});
在選擇活動(dòng)元素并為表單按鈕添加事件偵聽器之后,我們會(huì)運(yùn)行一個(gè)處理輸入并將之粘貼到輸出段落的函數(shù)。這需要調(diào)用一些 Rust,所以我們了解一些它的工作原理。
如果我們回到在生成區(qū)域中的主目錄,我們會(huì)注意到 src-tauri:
而有些 Rust 代碼位于 src 中的 main.rs 中:
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
我們能夠看到 JavaScript 中的 invoke 調(diào)用到達(dá)處理字符串的 Rust greet 函數(shù)。這是很好的一點(diǎn),因?yàn)槲覀兛梢允褂?Tauri 幫我們管理的 Rust 函數(shù)。(我們還需要了解 greet 函數(shù)的生成器。)
要顯示的最終文件是以 JSON 格式的,用于控制窗口本身,tauri.conf.json:
{
"productName": "thenewstack",
"version": "0.0.0",
"identifier": "com.tauri.dev",
"build": {
"frontendDist": "../src"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "thenewstack",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
...
]
}
}
為了確保我們理解了一切,讓我們?cè)O(shè)定一個(gè)可識(shí)別的目標(biāo),并稱呼一個(gè)友好的新問(wèn)候者。
“我們改變上述目標(biāo)以使其更小,并添加一個(gè)唯一的標(biāo)識(shí)符:
{
...
"identifier": "io.thenewsatck",
...
"app": {
"windows": [
{
"title": "Welcome to TheNewStack",
"width": 600,
"height": 200
}
],
...
},
...
}
然后我們適當(dāng)?shù)馗南⒋a。這將強(qiáng)迫構(gòu)建檢查更改。
最后,我們運(yùn)行完整構(gòu)建,以查看它對(duì)可執(zhí)行文件所做的更改。
圖片
當(dāng)然,這是需要時(shí)間的,因?yàn)樗堑谝淮?。結(jié)果是dmg和app文件。一旦我們把a(bǔ)pp移動(dòng)到應(yīng)用程序文件夾中,我們可以像正常的mac應(yīng)用程序一樣執(zhí)行它:
圖片
應(yīng)用程序大小仍然有點(diǎn)胖(10.7 mb),但我沒(méi)有做任何事情來(lái)精簡(jiǎn)自動(dòng)添加到模板的板條箱。
結(jié)論
我認(rèn)為我們很快從零變英雄與模板,盡管允許一系列 JavaScript 框架的靈活性確實(shí)讓一切都變得有點(diǎn)復(fù)雜。我想知道更武斷的方法是否會(huì)更好。但總體而言,我認(rèn)為 Tauri 仍然是打造桌面應(yīng)用程序而無(wú)需擔(dān)心窗口內(nèi)部的一個(gè)非??煽康慕鉀Q方案。