Skip to main content

Tauri instead of Puppeteer or Playwright?


Tauri and Puppeteer/Playwright have different use cases. So it seems nothing to compare. But there is (at least) one use case where you may try to use Tuari instead of a headless browser - “snapshots”. For example:

Why? Because Tauri uses provided by OS browser and produces small binaries (let’s say ~10 MB). While Puppeteer and Playwright need a full browser (let’s say ~200 MB). And Tauri probably can be faster.

Experiment #

Let’s see if this will work in practice. For an experiment, I decided to recreate cytosnap with Tauri. Functionality is trivial:

  • read input from file or STDIN (Elements JSON)
  • generate a graph with the help of Cytoscape.js
  • write result (image from canvas) to file or STDOUT
  • and this is supposed to be CLI, not GUI-app


CLI applications are not a typical use case for Tauri, but it is possible.

Hide window (tauri.conf.json):

"windows": [{ "visible": false, ...

Hide icon from Dock bar (Mac OS only):

#[cfg(target_os = "macos")]
use tauri::ActivationPolicy;

	.setup(|app| {
		#[cfg(target_os = "macos")]

Add CLI arguments (tauri.conf.json):

 "cli": {
      "description": "Render graphs on the server side with Cytoscape.js, getting image file as output",
      "args": [
          "name": "source",
          "short": "s",
          "takesValue": true,
          "multiple": false,
          "multipleOccurrences": false
          "name": "destination",
          "short": "d",
          "takesValue": true,
          "multiple": false,
          "multipleOccurrences": false

Overcome security fences #

Because Tauri’s main use case is GUI, they care about security and limit what files/folders the application can access. Which doesn’t work for the way people use CLI. So I had to write my own Tauri commands to write and read files, for example:

fn write_destination(dst: String, res: String) -> Result<String, String> {
    if dst == "" {
        print!("{}", res);
    } else {
        let bytes = base64::engine::general_purpose::STANDARD.decode(res);
        let path = absolute_path(PathBuf::from(dst)).unwrap();
        match bytes {
            std::result::Result::Ok(v) => {
                std::fs::write(path, v).unwrap();
            std::result::Result::Err(e) => Err(e.to_string()),

Note: I need to pass binary data from the front end to the rust, so I use Base64.

Distribution #

Initially, I wanted to distribute this CLI as binary inside the npm package. But then I realized that Tauri can’t really produce portable binaries. Tauri relies on the OS’s browser - which is a neat trick to shrink down the size of the binary, but this is what makes it less portable. Trade-offs as always.

Not to stop an experiment I decided to distribute at least binary for Mac OS in npm. And it works (Mac OS only):

npx @stereobooster/cyto-snap -s g2.json -d g2.png

Size of the npm package #

npm notice package size:  2.9 MB
npm notice unpacked size: 7.4 MB

I didn’t do any Tauri optimizations, so it probably can be smaller. Also, this is Mac OS-only binary, it will be bigger if we pack binaries for all platforms in one npm package.

And it has 0 dependencies.

Source code #

Warning: because this is an experiment, I didn’t try to make it perfect. Just made it work.

Source code:

Next steps #

Homebrew #

The idea of distributing binary in npm failed, so I think to try to distribute it via Homebrew. Homebrew works on Mac OS, Windows (WSL 2), and Linux (though they have their own package managers).

  • Produce standard desktop installers for Tauri (tauri-apps/tauri-action)
  • Upload installers to GitHub releases
  • Run installers in silent mode with the Homebrew formula

Full automation with GitHub Actions #

It has a lot of steps to produce the final package. I started automation but didn’t finish it. Ideally, it should:

  • Build application
  • Run tests (I do integration tests with odiff)
  • Create a tag and push it
  • Create release and upload all binaries
  • Update the Homebrew formula and publish it
  • Publish the npm package (but it is not very useful without portable binaries)

Conclusion #

  • This approach may work with Homebrew distribution
    • It is sad that you can’t publish binaries to npm, which would make JS-developer use other means to install it (Homebrew, apt-get, cURL, etc)
    • On the other hand, it is fully independent of npm (and Node.js ecosystem in general), so can be used by none-JS developers
  • This was my first time using Tauri, and it is awesome

Read more: Run Cystoscape.js with Node.js, Hugo ideal image