tw93-pake
https://github.com/tw93/Pake
https://github.dev/tw93/Pake
https://deepwiki.com/tw93/Pake
- 代码量比较小,项目思路很好;
$ 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;
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 中;
async function mergeConfig(url, options, tauriConf) {
...
...
const configJsonPath = path.join(tauriConfigDirectory, 'tauri.conf.json');
await fsExtra.outputJSON(configJsonPath, tauriConf2, { spaces: 4 });
}
- 配置初始化后,执行 tauri build 命令,构建 app;
async buildAndCopy(url, target) {
...
...
await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand()}`);
...
...
}
tauri部分
src-tauri/tauri.conf.json
- tauri项目的核心配置文件;
src-tauri/pake.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项目使用;
#![allow(unused)] fn main() { #[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函数;
#![allow(unused)] fn main() { 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窗口,加载远程网页或者本地网页,这一段是核心功能逻辑;
#![allow(unused)] fn main() { 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使用;
#![allow(unused)] fn main() { #[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()) } } } }