主题
tw93-pake
https://github.com/tw93/Pake
https://github.dev/tw93/Pake
https://deepwiki.com/tw93/Pake
- 代码量比较小,项目思路很好;
sh
$ tokei
===============================================================================
Language Files Lines Code Comments Blanks
===============================================================================
Dockerfile 1 56 38 12 6
JavaScript 8 1116 989 24 103
JSON 9 304 304 0 0
Python 1 35 33 0 2
Rust 9 532 460 3 69
TOML 2 51 40 6 5
TypeScript 24 1079 877 50 152
-------------------------------------------------------------------------------
Markdown 7 2043 0 1709 334
|- BASH 5 56 41 9 6
|- Shell 2 68 62 4 2
|- TypeScript 2 10 10 0 0
(Total) 2177 113 1722 342
===============================================================================
Total 61 5216 2741 1804 671
===============================================================================- 代码很值得一读,大致过程:
1. 预设了一个 tauri 的空置项目;
2. 写了一个 js 的 cli,读取配置,写入本地的 tauri 的配置 json 中 (关键参数是目标网页的 url);
3. 通过 js cli 调用 shell 命令执行 tauri build.
4. tauri build 过程中,关键逻辑:根据配置 json 中的 url,打开一个 webview 来渲染远程目标网页;app build 过程中也加载了一些插件来自定义 app; - 简单来讲就是一个:基于 tauri 实现的,套壳 webview 来加载 website 的客户端 app。 (和 ionic 的 capacitor 的https://capacitorjs.com/ 功能类似)
rust部分的代码量比较小,比较适合当rust的入门新手项目来读;js部分的cli也写的很好,值得借鉴;
关键代码
cli逻辑
dist/cli.js
- 关键配置信息:windows.url:配置要打包的网页的url;
js
var windows = [
{
url: "https://weread.qq.com",
url_type: "web",
hide_title_bar: true,
fullscreen: false,
width: 1200,
height: 780,
resizable: true,
always_on_top: false,
dark_mode: false,
activation_shortcut: "",
disabled_web_shortcuts: false
}
];
var pakeConf = {
windows: windows,
user_agent: user_agent,
system_tray: system_tray,
system_tray_path: system_tray_path,
inject: inject,
proxy_url: proxy_url
};- 将cli中的配置写入到预设项目的 json 中;
js
async function mergeConfig(url, options, tauriConf) {
...
...
const configJsonPath = path.join(tauriConfigDirectory, 'tauri.conf.json');
await fsExtra.outputJSON(configJsonPath, tauriConf2, { spaces: 4 });
}- 配置初始化后,执行 tauri build 命令,构建 app;
js
async buildAndCopy(url, target) {
...
...
await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand()}`);
...
...
}tauri部分
src-tauri/tauri.conf.json
- tauri项目的核心配置文件;
src-tauri/pake.json
- 自定义关键配置:
json
{
"windows": [
{
"url": "https://weread.qq.com",
"url_type": "web",
"hide_title_bar": true,
"fullscreen": false,
"width": 1200,
"height": 780,
"resizable": true,
"always_on_top": false,
"dark_mode": false,
"activation_shortcut": "",
"disabled_web_shortcuts": false
}
]
}src-tauri/src/app/config.rs
- 定义了一个配置结构体,主要是读取 pake.json 中的配置项, 然后给tauri项目使用;
rust
#[derive(Debug, Serialize, Deserialize)]
pub struct WindowConfig {
pub url: String,
pub hide_title_bar: bool,
pub fullscreen: bool,
pub width: f64,
pub height: f64,
pub resizable: bool,
pub url_type: String,
pub always_on_top: bool,
pub dark_mode: bool,
pub disabled_web_shortcuts: bool,
pub activation_shortcut: String,
}
...
...
#[derive(Debug, Serialize, Deserialize)]
pub struct PakeConfig {
pub windows: Vec<WindowConfig>,
pub user_agent: UserAgent,
pub system_tray: FunctionON,
pub system_tray_path: String,
pub proxy_url: String,
}src-tauri/src/lib.rs
- tauri app 的入口函数,主要是初始化一些插件和配置项, 以及调用自定义的核心逻辑: set_window函数;
rust
pub fn run_app() {
...
...
#[allow(deprecated)]
tauri_app
.plugin(window_state_plugin)
.plugin(tauri_plugin_oauth::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_single_instance::init(|_, _, _| ()))
.invoke_handler(tauri::generate_handler![
download_file,
download_file_by_binary,
send_notification,
])
.setup(move |app| {
let window = set_window(app, &pake_config, &tauri_config);
set_system_tray(app.app_handle(), show_system_tray).unwrap();
set_global_shortcut(app.app_handle(), activation_shortcut).unwrap();
// Prevent flickering on the first open.
window.show().unwrap();
Ok(())
})
.on_window_event(|_window, _event| {
#[cfg(target_os = "macos")]
if let tauri::WindowEvent::CloseRequested { api, .. } = _event {
let window = _window.clone();
tauri::async_runtime::spawn(async move {
if window.is_fullscreen().unwrap_or(false) {
window.set_fullscreen(false).unwrap();
tokio::time::sleep(Duration::from_millis(900)).await;
}
window.minimize().unwrap();
window.hide().unwrap();
});
api.prevent_close();
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
...
}src-tauri/src/app/window.rs
- tauri app 新建webview窗口,加载远程网页或者本地网页,这一段是核心功能逻辑;
rust
pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) -> WebviewWindow {
...
...
let url = match window_config.url_type.as_str() {
"web" => WebviewUrl::App(window_config.url.parse().unwrap()),
"local" => WebviewUrl::App(PathBuf::from(&window_config.url)),
_ => panic!("url type can only be web or local"),
};
let config_script = format!(
"window.pakeConfig = {}",
serde_json::to_string(&window_config).unwrap()
);
let mut window_builder = WebviewWindowBuilder::new(app, "pake", url)
.title("")
.visible(false)
.user_agent(user_agent)
.resizable(window_config.resizable)
.fullscreen(window_config.fullscreen)
.inner_size(window_config.width, window_config.height)
.always_on_top(window_config.always_on_top)
.disable_drag_drop_handler()
.initialization_script(&config_script)
.initialization_script(include_str!("../inject/component.js"))
.initialization_script(include_str!("../inject/event.js"))
.initialization_script(include_str!("../inject/style.js"))
.initialization_script(include_str!("../inject/custom.js"));
...
...
}src-tauri/src/app/invoke.rs
- 这一段主要实现了一个tauri的插件,供tauri app使用,有点类似:移动app中,原生实现的js bridge给h5使用;
rust
#[command]
pub async fn download_file(app: AppHandle, params: DownloadFileParams) -> Result<(), String> {
let window: WebviewWindow = app.get_webview_window("pake").unwrap();
show_toast(&window, &get_download_message(MessageType::Start));
let output_path = app.path().download_dir().unwrap().join(params.filename);
let file_path = check_file_or_append(output_path.to_str().unwrap());
let client = ClientBuilder::new().build().unwrap();
let response = client
.execute(Request::new(
Method::GET,
Url::from_str(¶ms.url).unwrap(),
))
.await;
match response {
Ok(res) => {
let bytes = res.bytes().await.unwrap();
let mut file = File::create(file_path).unwrap();
file.write_all(&bytes).unwrap();
show_toast(&window, &get_download_message(MessageType::Success));
Ok(())
}
Err(e) => {
show_toast(&window, &get_download_message(MessageType::Failure));
Err(e.to_string())
}
}
}