Cloudflare提供了一個名為Workers KV的優秀產品,這是一個全局複製的鍵值存儲層。它可以處理數百萬個鍵,每個鍵都可以在Worker腳本中以極低的延遲訪問,無論請求來自世界哪個地方。 Workers KV令人驚嘆——它的定價也很誘人,包括一個慷慨的免費層級。
然而,作為Cloudflare產品線的長期用戶,我發現缺少一樣東西:本地自省。我的應用程序中經常擁有數千甚至數十萬個鍵,我常常希望有一種方法可以查詢所有數據、對其進行排序,或者只是查看實際存在的內容。
最近,我有幸加入了Cloudflare!更重要的是,我是在本季度“快速獲勝週”(又名一周黑客馬拉松)之前加入的。由於我還沒有積累足夠的積壓工作(尚未),相信我,我會抓住這個機會來實現自己的願望。
言歸正傳,讓我告訴你我是如何構建Workers KV GUI的,這是一個使用Svelte、Redis和Rust構建的跨平台桌面應用程序。
前端應用
作為一名Web開發者,這是我熟悉的環節。我很想稱之為“簡單部分”,但是,鑑於您可以使用任何和所有HTML、CSS和JavaScript框架、庫或模式,選擇癱瘓很容易發生……這可能也很熟悉。如果您有一個喜歡的前端技術棧,那就太好了,用它吧!對於這個應用程序,我選擇使用Svelte,因為對我來說,它確實讓事情變得簡單並保持簡單。
此外,作為Web開發者,我們希望隨身攜帶所有工具。你當然可以!同樣,項目的這一階段與典型的Web應用程序開發週期沒有什麼不同。您可以期望運行yarn dev(或某些變體)作為您的主要命令,並感到賓至如歸。為了保持“簡單”的主題,我選擇使用SvelteKit,這是Svelte官方的用於構建應用程序的框架和工具包。它包括一個優化的構建系統、出色的開發者體驗(包括HMR!)、基於文件系統的路由器,以及Svelte本身提供的所有功能。
作為一個框架,特別是自己負責工具的框架,SvelteKit允許我純粹地考慮我的應用程序及其需求。事實上,就配置而言,我唯一需要做的就是告訴SvelteKit我想構建一個只在客戶端運行的單頁應用程序(SPA)。換句話說,我必須明確地選擇退出SvelteKit假設我想要一個服務器,這實際上是一個合理的假設,因為大多數應用程序都可以從服務器端渲染中受益。這就像附加@sveltejs/adapter-static包一樣簡單,這是一個專門為此目的而創建的配置預設。安裝後,我的整個配置文件如下所示:
<code>// svelte.config.js import preprocess from 'svelte-preprocess'; import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: preprocess(), kit: { adapter: adapter({ fallback: 'index.html' }), files: { template: 'src/index.html' } }, }; export default config;</code>
index.html的更改是我的個人偏好。 SvelteKit使用app.html作為默認的基本模板,但舊習慣很難改。
僅僅幾分鐘,我的工具鏈就已經知道它正在構建一個SPA,已經有一個路由器就緒,並且一個開發服務器隨時可用。此外,由於svelte-preprocess,如果我想要(而且我確實想要),TypeScript、PostCSS和/或Sass支持也可用。準備好了!
該應用程序需要兩個視圖:
- 輸入連接詳細信息的屏幕(默認/歡迎/主頁)
- 實際查看數據的屏幕
在SvelteKit世界中,這轉化為兩個“路由”,SvelteKit規定這些路由應該作為src/routes/index.svelte(主頁)和src/routes/viewer.svelte(數據查看器頁面)存在。在一個真正的Web應用程序中,第二個路由將映射到/viewer URL。雖然情況仍然如此,但我知道我的桌面應用程序不會有導航欄,這意味著URL將不可見……這意味著我如何命名這個路由並不重要,只要它對我來說有意義即可。
這些文件的內容大多無關緊要,至少對於本文而言如此。對於那些好奇的人,整個項目是開源的,如果您正在尋找Svelte或SvelteKit示例,歡迎您查看。冒著像壞掉的唱片一樣的風險,這裡的重點是我正在構建一個普通的Web應用程序。
此時,我只是設計我的視圖並四處拋出虛假的、硬編碼的數據,直到我得到一些看起來有效的東西。我在這裡待了大約兩天,直到一切看起來都很漂亮,並且所有交互性(按鈕點擊、表單提交等)都被完善了。我會稱之為“可運行”的應用程序或模型。
桌面應用程序工具
此時,一個功能齊全的SPA已經存在。它在Web瀏覽器中運行——並且是在Web瀏覽器中開發的。也許與直覺相反,這使得它成為成為桌面應用程序的完美候選者!但是如何做到呢?
您可能聽說過Electron。它是使用Web技術構建跨平台桌面應用程序的最著名的工具。有很多非常流行和成功的應用程序都是用它構建的:Visual Studio Code、WhatsApp、Atom和Slack,僅舉幾例。它的工作原理是將您的Web資源與其自身的Chromium安裝和自身的Node.js運行時捆綁在一起。換句話說,當您安裝基於Electron的應用程序時,它會附帶一個額外的Chrome瀏覽器和一整套編程語言(Node.js)。這些都嵌入在應用程序內容中,無法避免,因為這些是應用程序的依賴項,保證它在任何地方都能一致地運行。正如您可能想像的那樣,這種方法有一些權衡——應用程序相當龐大(即超過100MB)並且使用大量系統資源來運行。為了使用該應用程序,後台會運行一個全新的/單獨的Chrome——這與打開一個新標籤頁並不完全相同。
幸運的是,有一些替代方案——我評估了Svelte NodeGui和Tauri。通過依賴操作系統提供的原生渲染器,而不是嵌入Chrome副本來完成相同的工作,這兩個選擇都提供了顯著的應用程序大小和利用率節省。 NodeGui通過依賴Qt來實現這一點,Qt是另一個編譯為原生視圖的桌面/GUI應用程序框架。但是,為了做到這一點,NodeGui需要對您的應用程序代碼進行一些調整,以便它可以將您的組件轉換為Qt組件。雖然我相信這肯定可以工作,但我對這個解決方案不感興趣,因為我想使用我已知的確切內容,而不需要對我的Svelte文件進行任何調整。相比之下,Tauri通過包裝操作系統的原生webviewer來實現其節省——例如,macOS上的Cocoa/WebKit、Linux上的gtk-webkit2以及Windows上的Edge上的Webkit。 Webviewer實際上是瀏覽器,Tauri使用它們是因為它們已經存在於您的系統上,這意味著我們的應用程序可以保持純Web開發產品。
有了這些節省,最小的Tauri應用程序小於4MB,平均應用程序重量小於20MB。在我的測試中,最小的NodeGui應用程序重約16MB。最小的Electron應用程序很容易達到120MB。
不用說,我選擇了Tauri。通過遵循Tauri集成指南,我在devDependencies中添加了@tauri-apps/cli包並初始化了項目:
<code>yarn add --dev @tauri-apps/cli yarn tauri init</code>
這會在src目錄(Svelte應用程序所在的位置)旁邊創建一個src-tauri目錄。這是所有Tauri特定文件所在的位置,這對於組織來說很好。
我以前從未構建過Tauri應用程序,但在查看其配置文檔後,我能夠保留大多數默認值——當然,除了package.productName和windows.title值之類的項目之外。實際上,我需要做的唯一更改是構建配置,它必須與SvelteKit對齊以進行開發和輸出信息:
<code>// src-tauri/tauri.conf.json { "package": { "version": "0.0.0", "productName": "Workers KV" }, "build": { "distDir": "../build", "devPath": "http://localhost:3000", "beforeDevCommand": "yarn svelte-kit dev", "beforeBuildCommand": "yarn svelte-kit build" }, // ... }</code>
distDir與構建的生產就緒資產所在位置相關。此值從tauri.conf.json文件位置解析,因此有../前綴。
devPath是在開發過程中代理的URL。默認情況下,SvelteKit在端口3000上生成一個開發服務器(可配置)。我在第一階段一直在瀏覽器中訪問localhost:3000地址,所以這沒有什麼不同。
最後,Tauri有其自身的dev和build命令。為了避免處理多個命令或構建腳本的麻煩,Tauri提供了beforeDevCommand和beforeBuildCommand鉤子,允許您在tauri命令運行之前運行任何命令。這是一個微妙但強大的便利!
SvelteKit CLI可以通過svelte-kit二進制名稱訪問。例如,編寫yarn svelte-kit build會告訴yarn獲取其本地的svelte-kit二進製文件(通過devDependency安裝),然後告訴SvelteKit運行其build命令。
有了這個,我的根級package.json包含以下腳本:
<code>{ "private": true, "type": "module", "scripts": { "dev": "tauri dev", "build": "tauri build", "prebuild": "premove build", "preview": "svelte-kit preview", "tauri": "tauri" }, // ... "devDependencies": { "@sveltejs/adapter-static": "1.0.0-next.9", "@sveltejs/kit": "1.0.0-next.109", "@tauri-apps/api": "1.0.0-beta.1", "@tauri-apps/cli": "1.0.0-beta.2", "premove": "3.0.1", "svelte": "3.38.2", "svelte-preprocess": "4.7.3", "tslib": "2.2.0", "typescript": "4.2.4" } }</code>
集成後,我的生產命令仍然是yarn build,它調用tauri build來實際捆綁桌面應用程序,但只有在yarn svelte-kit build成功完成之後(通過beforeBuildCommand選項)。我的開發命令仍然是yarn dev,它並行運行tauri dev和yarn svelte-kit dev命令。開發工作流程完全在Tauri應用程序內,現在它正在代理localhost:3000,允許我仍然獲得HMR開發服務器的好處。
重要提示:在我撰寫本文時,Tauri仍處於測試階段。也就是說,它感覺非常穩定且計劃周全。我沒有與該項目有任何關聯,但看起來Tauri 1.0可能會很快進入穩定版本。我發現Tauri Discord非常活躍且樂於助人,包括來自Tauri維護者的回复!他們甚至在整個過程中回答了我的一些Rust新手問題。 :)
連接到Redis
此時,是快速獲勝週的星期三下午,老實說,我開始對在星期五團隊演示之前完成感到緊張。為什麼?因為我已經度過了本週的一半時間,即使我有一個外觀良好的SPA在一個可運行的桌面應用程序中,它仍然什麼也不做。我整個星期都在看相同的假數據。
您可能認為因為我可以訪問webview,我可以使用fetch()為我想要Workers KV數據發出一些經過身份驗證的REST API調用,並將其全部轉儲到localStorage或IndexedDB表中……您完全正確!但是,這並不是我對桌面應用程序用例的想法。
將所有數據保存到某種瀏覽器內存儲中是完全可行的,但它會將其本地保存到您的機器上。這意味著如果您的團隊成員嘗試執行相同的操作,每個人都必須在他們自己的機器上獲取和保存所有數據。理想情況下,此Workers KV應用程序應該可以選擇連接到並與外部數據庫同步。這樣,在團隊設置中工作時,每個人都可以調整到相同的數據庫緩存以節省時間——以及一些資金。當處理數百萬個鍵時,這開始變得重要,正如前面提到的,這在使用Workers KV時並不少見。
考慮了一會兒,我決定使用Redis作為我的後端存儲,因為它也是一個鍵值存儲。這很棒,因為Redis已經將鍵作為一等公民對待,並提供了我想要的排序和過濾行為(也就是,我可以傳遞工作而不是自己實現!)。然後,當然,Redis很容易在本地或容器中安裝和運行,如果有人選擇走這條路,有很多託管Redis即服務提供商。
但是,我該如何連接到它呢?我的應用程序基本上是一個運行Svelte的瀏覽器標籤,對吧?是的——但它也遠不止於此。
您會看到,Electron成功的一部分原因是,是的,它保證Web應用程序在每個操作系統上都能很好地呈現,但它也帶來了Node.js運行時。作為一名Web開發者,這很像在我的客戶端中直接包含一個後端API。基本上,“……但它在我的機器上運行”問題消失了,因為所有用戶(不知不覺地)都在運行完全相同的localhost設置。通過Node.js層,您可以與文件系統交互,在多個端口上運行服務器,或者包含一堆node_modules來——我只是在這裡隨口說說——連接到Redis實例。強大的東西。
我們不會失去這種超能力,因為我們正在使用Tauri!它是一樣的,但略有不同。
Tauri應用程序不是包含Node.js運行時,而是使用Rust(一種低級系統語言)構建的。這就是Tauri本身與操作系統交互並“借用”其原生webviewer的方式。所有Tauri工具包都是編譯的(通過Rust),這使得構建的應用程序保持小巧高效。但是,這也意味著我們,應用程序開發者,可以將任何其他板條箱(“npm模塊”等效項)包含到構建的應用程序中。當然,還有一個恰如其分命名的redis板條箱,它作為一個Redis客戶端驅動程序,允許Workers KV GUI連接到任何Redis實例。
在Rust中,Cargo.toml文件類似於我們的package.json文件。這是定義依賴項和元數據的地方。在Tauri設置中,它位於src-tauri/Cargo.toml,因為同樣,與Tauri相關的所有內容都位於此目錄中。 Cargo還具有依賴項級別定義的“功能標誌”的概念。 (我能想到的最接近的類比是使用npm訪問模塊的內部結構或導入命名的子模塊,儘管它仍然不完全相同,因為在Rust中,功能標誌會影響包的構建方式。)
<code># src-tauri/Cargo.toml [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.0.0-beta.1", features = ["api-all", "menu"] } redis = { version = "0.20", features = ["tokio-native-tls-comp"] }</code>
上面將redis板條箱定義為依賴項,並選擇“tokio-native-tls-comp”功能,文檔說這是TLS支持所必需的。
好的,所以我終於擁有了我需要的一切。在星期三結束之前,我必須讓我的Svelte與我的Redis對話。四處查看後,我注意到所有重要的事情似乎都發生在src-tauri/main.rs文件中。我記下了#[command]宏,我知道我之前在當天的Tauri示例中見過它,所以我學習複製了示例文件中的各個部分,查看根據Rust編譯器哪些錯誤出現和消失。
最終,Tauri應用程序能夠再次運行,我了解到#[command]宏以某種方式包裝底層函數,以便它可以接收“上下文”值(如果您選擇使用它們)並接收預解析的參數值。此外,作為一種語言,Rust會進行大量類型轉換。例如:
<code>use tauri::{command}; #[command] fn greet(name: String, age: u8) { println!("Hello {}, {} year-old human!", name, age); }</code>
這會創建一個greet命令,當運行時,期望兩個參數:name和age。定義時,name值是一個字符串值,age是u8數據類型——也就是一個整數。但是,如果兩者都缺少,Tauri會拋出錯誤,因為命令定義沒有說明任何內容可以是可選的。
為了實際將Tauri命令連接到應用程序,它必須定義為tauri::Builder組合的一部分,位於main函數內。
<code>use tauri::{command}; #[command] fn greet(name: String, age: u8) { println!("Hello {}, {} year-old human!", name, age); } fn main() { // start composing a new Builder chain tauri::Builder::default() // assign our generated "handler" to the chain .invoke_handler( // piece together application logic tauri::generate_handler![ greet, // attach the command ] ) // start/initialize the application .run( // put it all together tauri::generate_context!() ) // print<message> if error while running .expect("error while running tauri application"); }</message></code>
Tauri應用程序編譯並知道它擁有一個“greet”命令。它還在控制webview(我們已經討論過),但這樣做時,它充當前端(webview內容)和後端之間的橋樑,後端由Tauri API和我們編寫的任何其他代碼(如greet命令)組成。 Tauri允許我們在該橋樑之間發送消息,以便這兩個世界可以相互通信。
前端可以通過導入來自任何(已包含的)@tauri-apps包的功能,或者通過依賴於window. TAURI全局變量(可用於整個客戶端應用程序)來訪問此“橋樑”。具體來說,我們對invoke命令感興趣,該命令接受命令名稱和一組參數。如果有任何參數,則必須將其定義為一個對象,其中鍵與我們的Rust函數期望的參數名稱匹配。
在Svelte層中,這意味著我們可以執行以下操作來調用在Rust層中定義的greet命令:
<code>function onclick() { __TAURI__.invoke('greet', { name: 'Alice', age: 32 }); } Click Me</code>
單擊此按鈕時,我們的終端窗口(tauri dev命令運行的位置)將打印:
<code>Hello Alice, 32 year-old human!</code>
同樣,這發生是因為println!函數(實際上是Rust的console.log),greet命令使用了該函數。它出現在終端的控制台窗口中——而不是瀏覽器控制台中——因為此代碼仍在Rust/系統端運行。
也可以從Tauri命令向客戶端發送一些內容,所以讓我們快速更改greet:
<code>use tauri::{command}; #[command] fn greet(name: String, age: u8) { // implicit return, because no semicolon! format!("Hello {}, {} year-old human!", name, age) } // OR #[command] fn greet(name: String, age: u8) { // explicit `return` statement, must have semicolon return format!("Hello {}, {} year-old human!", name, age); }</code>
意識到我將多次調用invoke,並且有點懶惰,我提取了一個輕量級的客戶端助手來整合這些內容:
<code>// @types/global.d.ts ///<reference types="@sveltejs/kit"></reference> type Dict<t> = Record<string t=""> ; declare const __TAURI__: { invoke: typeof import('@tauri-apps/api/tauri').invoke; } // src/lib/tauri.ts export function dispatch(command: string, args: Dict<string> ) { return __TAURI__.invoke(command, args); }</string></string></t></code>
然後將之前的Greeter.svelte重構為:
<code>import { dispatch } from '$lib/tauri'; async function onclick() { let output = await dispatch('greet', { name: 'Alice', age: 32 }); console.log('~>', output); //=> "~> Hello Alice, 32 year-old human!" } Click Me</code>
太棒了!所以現在是星期四,我還沒有編寫任何Redis代碼,但至少我知道如何將應用程序的大腦的兩半連接在一起。是時候梳理客戶端代碼並替換事件處理程序中的所有TODO,並將它們連接到實際內容了。
我將在這裡省略細節,因為從這裡開始它非常特定於應用程序——並且主要是關於Rust編譯器給我帶來打擊的故事。此外,探索細節正是項目開源的原因!
在高層次上,一旦使用給定的詳細信息建立了Redis連接,就可以在/viewer路由中訪問SYNC按鈕。單擊此按鈕時(並且只有那時——因為成本),將調用一個JavaScript函數,該函數負責連接到Cloudflare REST API並為每個鍵調度“redis_set”命令。此redis_set命令在Rust層中定義——所有基於Redis的命令也是如此——並且負責實際將鍵值對寫入Redis。
從Redis中讀取數據是一個非常相似的過程,只是反過來了。例如,當/viewer啟動時,所有鍵都應該列出並準備就緒。在Svelte術語中,這意味著我需要在/viewer組件安裝時調度Tauri命令。這幾乎逐字地發生在這裡。此外,單擊側邊欄中的鍵名稱將顯示有關該鍵的更多“詳細信息”,包括其到期時間(如果有)、其元數據(如果有)及其實際值(如果已知)。為了優化成本和網絡負載,我們決定只應按需獲取鍵的值。這引入了REFRESH按鈕,單擊該按鈕時,它會再次與REST API交互,然後調度一個命令,以便Redis客戶端可以單獨更新該鍵。
我並不是想倉促結束,但是一旦你看到JavaScript和Rust代碼之間的一個成功的交互,你就看到了所有交互!我星期四和星期五上午的其餘時間只是定義新的請求-回復對,這感覺很像給自己發送PING和PONG消息。
結論
對我來說——我想對許多其他JavaScript開發者來說也是如此——本週的挑戰是學習Rust。我相信你以前聽過這個,你以後也一定會再聽到。所有權規則、借用檢查以及單個字符語法標記的含義(順便說一句,這些標記不容易搜索)只是我遇到的幾個障礙。再次感謝Tauri Discord的幫助和善意!
這也意味著使用Tauri並非一項挑戰——而是一種巨大的解脫。我肯定計劃將來再次使用Tauri,尤其是在我知道如果我想使用,我可以只使用webviewer的情況下。深入研究和/或添加Rust部分是“額外材料”,只有在我的應用程序需要時才需要。
對於那些想知道的人,因為我找不到另一個地方提及它:在macOS上,Workers KV GUI應用程序的重量不到13 MB。我對這個結果非常興奮!
當然,SvelteKit也使這個時間表成為可能。它不僅節省了我半天配置工具帶的時間,而且即時的HMR開發服務器也可能節省了我幾個小時手動刷新瀏覽器——然後是Tauri查看器的時間。
如果您已經看到這裡——這令人印象深刻!非常感謝您的時間和關注。提醒一下,該項目可在GitHub上獲得,最新的預編譯二進製文件始終可通過其發布頁面獲得。
以上是我如何與Svelte,Redis和Rust構建跨平台桌面應用程序的詳細內容。更多資訊請關注PHP中文網其他相關文章!

在本週的綜述中:datepickers正在讓鍵盤用戶頭痛,一個新的Web組件編譯器,有助於與Fouc進行戰鬥,我們終於獲得了造型列表項目標記,以及在您的網站上獲得網絡攻擊的四個步驟。

在本週的Web平台新聞世界中,Google搜索控制台可以更輕鬆地查看爬行的標記,我們了解到自定義屬性


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3漢化版
中文版,非常好用

Dreamweaver Mac版
視覺化網頁開發工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。