代替 Electron 的几种方案
electron 的劣势
- 安装包体积大,每装一个 app 等同于装一个 chrome
- electron 的窗口是自绘的,因此无法自适应系统主题、控制按钮扩展以及透明效果
- electron 的主进程也是 js 写的,涉及到 CPU 较为密集的场景性能烂
代替方案一: tauri
相对于 electron 的优势
- tauri 使用了系统自带的 WebView,安装体积非常小
- tauri 的窗口仍然是系统的原生窗口
- tauri 主进程是 rust 写的,对于 CPU 密集的场景也没有性能问题
tauri 的劣势
- rust 有一定的学习成本,大部分前端不会去接触 rust。
tauri 示例
rs
// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// 定义一个greet方法暴露给前端
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
vue
<script setup>
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/tauri";
const greetMsg = ref("");
const name = ref("");
async function greet() {
// 调用 rust 定义的 greet 方法
greetMsg.value = await invoke("greet", { name: name.value });
}
</script>
<template>
<form class="row" @submit.prevent="greet">
<input id="greet-input" v-model="name" placeholder="Enter a name..." />
<button type="submit">Greet</button>
</form>
<p>{{ greetMsg }}</p>
</template>
代替方案二: dart + webview
dart 不是只能写 flutter,通过 ffi 调用 win32 与 webview2 也是一种方案。
相对于 electron 的优势
- dart + webview 使用了系统自带的 WebView,安装体积非常小
- dart 是调用 win32 的窗体
- 主进程是 dart 写的,通过 compile 可以编译成 native 程序
dart + webview 的劣势
- 没有完整的一套解决方案,很多轮子需要自己造
- dart 语言学习成本(不过我感觉比 rust 容易学很多,而且可以用于 flutter)
dart + webview 示例
安装依赖
sh
dart pub add webview_dart
或
yaml
dependencies:
webview_dart: ^1.0.0
文件结构
创建如下的结构:
myapp
|
+---- out
| |
| +--- webview.dll
| |
| +--- webview2loader.dll
|
+--- bin
| |
| +--- myapp.dart
|
+---- all other files
编写主入库
dart
import 'package:webview_dart/webview_dart.dart';
void main() {
final url = "https://www.google.com";
Webview(false)
.setTitle("title")
.setSize(1280, 800, SizeHint.none)
.navigate(url)
.run();
}
缺失 dll 的问题
可以从 https://github.com/shreyassanthu77/webview_dart/releases 去下载。
注入 JS 脚本(可选)
通过 webview.init("A valid string of Js")
可以向 webview 注入脚本。
比如可以注入禁用 Web 控制台的脚本
js
(function () {
const keys = {};
document.addEventListener("keydown", function (event) {
keys[event.key] = true;
const cond = (keys["I"] && keys["Shift"] && keys["Control"]) || keys["F12"];
!cond || event.preventDefault();
});
document.addEventListener("keyup", function (event) {
keys[event.key] = false;
});
document.addEventListener("contextmenu", function (event) {
event.preventDefault();
});
})();
如何在 win 上隐藏黑窗口
sh
dart pub add win32
dart
import 'package:win32/win32.dart';
//...
ShowWindow(GetConsoleWindow(), SW_HIDE);
//...
绑定原生方法
dart
webview.bind("jsName", (List<dynamic> args) {
// some code
})
除了绑定原生方法,也可以通过 http 服务来与 dart 交互
我使用的是 alfred
yaml
name: dart_webview
description: A sample command-line application.
version: 1.0.0
environment:
sdk: ">=3.0.0"
dependencies:
alfred: ^1.1.1
webview_dart: ^1.0.0
win32: ^5.0.7
dev_dependencies:
lints: ^2.0.0
test: ^1.21.0
将主入口改造为:
dart
import 'dart:isolate';
import 'package:webview_dart/webview_dart.dart';
import 'package:alfred/alfred.dart';
import 'package:win32/win32.dart';
void main() {
Isolate.spawn(init, []).then((value) {
ShowWindow(GetConsoleWindow(), SW_HIDE);
final url = "http://127.0.0.1:8080/example";
Webview(false).setTitle("title").setSize(1280, 800, SizeHint.none).navigate(url).run();
});
}
void init(List<dynamic> args) async {
final app = Alfred();
app.get('/example', (req, res) => 'Hello world');
await app.listen(8080);
}
附录
- rust 编程语言:https://www.rust-lang.org/
- dart 编程语言:https://dart.dev/