搜索
首页web前端css教程我如何与Svelte,Redis和Rust构建跨平台桌面应用程序

How I Built a Cross-Platform Desktop Application with Svelte, Redis, and Rust

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支持也可用。准备好了!

该应用程序需要两个视图:

  1. 输入连接详细信息的屏幕(默认/欢迎/主页)
  2. 实际查看数据的屏幕

在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中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
我们如何标记Google字体并创建Goofonts.com我们如何标记Google字体并创建Goofonts.comApr 12, 2025 pm 12:02 PM

Goofonts是由开发人员和设计师丈夫签名的附带项目,它们都是版式的忠实拥护者。我们一直在标记Google

永恒的Web开发文章永恒的Web开发文章Apr 12, 2025 am 11:44 AM

Pavithra Kodmad向人们询问了他们认为是关于网络开发的一些最永恒的文章的建议

与部分元素的交易与部分元素的交易Apr 12, 2025 am 11:39 AM

同一天发表了两篇文章:

使用JavaScript API的状态练习GraphQl查询使用JavaScript API的状态练习GraphQl查询Apr 12, 2025 am 11:33 AM

学习如何构建GraphQL API可能具有挑战性。但是您可以学习如何在10分钟内使用GraphQL API!碰巧的是,我得到了完美的

组件级CMS组件级CMSApr 12, 2025 am 11:09 AM

当一个组件生活在数据查询居住在附近的数据查询的环境中时,视觉组件与

将类型设置在圆上...带偏移路径将类型设置在圆上...带偏移路径Apr 12, 2025 am 11:00 AM

这里是Yuanchuan的一些合法CSS骗局。有此CSS属性偏移路径。曾几何时,它被称为Motion-Path,然后被更名。我

'恢复”在CSS中有什么作用?'恢复”在CSS中有什么作用?Apr 12, 2025 am 10:59 AM

Miriam Suzanne在Mozilla开发人员的视频中解释了该主题。

现代恋人现代恋人Apr 12, 2025 am 10:58 AM

我喜欢这样的东西。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版