Build1 publisher2 min readPublished
Tauri trades Electron's 150 MB floor for a Node sidecar you have to supervise
Size was the only reason to pick Tauri over Electron for this app, and the discount is real. What you take on in exchange is a second process whose lifetime and paths you now manage from Rust.
The Engineer · Build desk

What happened
- MeghXL is a LAN file-transfer tool of about 2,000 lines, an Express server with a WebSocket hub, that its author wanted to ship as a double-clickable app for macOS, Windows and Linux.
- Electron ships Chromium, so the equivalent build would start near 150 MB before any application code, which the author judged fatal to a tool pitched as small enough to audit in an evening.
- Tauri is Rust on the OS webview, so the server cannot run inside the app and instead ships as a separate binary listed under bundle.externalBin and spawned by the Rust side.
- bun build --compile turned the server into an 82 MB self-contained executable in about half a second, running Express, ws, multer and mDNS discovery with no Node runtime installed.
- Force-quitting the app left the sidecar alive holding the port, so later launches attached to the stale process and served old code, making fresh builds look like they changed nothing.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Browser behaviour the Chromium runtime gave you free becomes per-platform Rust you write and test: downloads need an explicit on_download handler, and on macOS the Finished event withholds the path, so you track destinations yourself.
- constraint The server can no longer assume it owns a filesystem, because __dirname inside a compiled binary is virtual and app bundles get replaced wholesale on update, so static paths and write locations both have to arrive from the host process.
- decision The single-file build now rides on one tool, since pkg is archived and Node SEA is awkward with multi-file CommonJS, which makes Bun's compile target a dependency of your release process rather than a convenience.
The installers ship at 28 to 41 MB, by the author's account [1], which is roughly a third to a half of what the compiled server weighs on its own before packaging [1]. Compression accounts for part of that. The rest is a UI rendered by a webview that was already on the machine.
Whether the figure transfers to your server depends on one property of your dependency tree. All seven of this app's runtime dependencies are pure JavaScript with no native addons, and the author is explicit that a single `.node` file would have collapsed the approach [12]. The precondition is testable in one line, `find node_modules -name "*.node" | head`, where empty output is the green light [13]. It matters because `externalBin` takes one file, and a `server.js` next to a `node_modules` tree is not a binary [7]. Fail that check and Electron is the honest answer: it bundles Node, so the server just runs in the main process, with no sidecar and no IPC puzzle [23].
Then there is the webview, which is not a browser you fully control. Tauri installs its own native file-drop handler on every window, swallowing OS drops and re-emitting them as Tauri events, so an HTML5 dropzone fires with an empty `dataTransfer.files` until you call `.disable_drag_drop_handler()` [18]. The shell around the app is what breaks that dropzone; the server inside it works fine.
The lifecycle fix is the part I would copy first. Tauri gives the sidecar a piped stdin, so the child can watch for EOF and treat it as proof the parent is gone [22]. The check belongs in the child, because the parent is the process that just died, and a force-quit gives it no chance to clean up after itself.
Naming is the trap that costs least to avoid and most to discover. Tauri resolves sidecars by target triple, so the binary has to be `<name>-<triple>` matching what `rustc -vV` reports as `host`, and a mismatch fails late, at bundling, with a confusing message [14], after you have paid for the full compile.
Where I land: for a pure-JavaScript server whose UI already works in a browser, keeping 109 to 122 MB off every download [3] buys back a day of Rust you will rarely reopen. It's the dependency tree that makes this trade work here, more than the framework itself.
What to watch
- Whether Node SEA's handling of CommonJS across many files improves enough that the compile step stops depending on Bun.
- Whether Tauri exposes downloads and file drops as bundle configuration rather than per-window Rust callbacks.
- Whether sidecar lifetime becomes something Tauri's spawn side enforces, instead of an EOF check written inside the Node process.