Blog

How Pake works: from a URL to a desktop app

How it works · · 7 min read

Pake does not rewrite a website. The pake command copies a prebuilt Tauri app template, writes your options into its config, compiles it with Rust, and bundles it as an installer that opens your URL in the web view your operating system already has. The app is small because that engine is not inside it, and the same fact explains most of its limits.

The build, step by step

You run pake https://example.com --name Example after installing the CLI with pnpm install -g pake-cli (npm works too). The getting started guide lists the prerequisites: Node.js 20.9 or later, Rust 1.85 or later, and each platform's build tools. If Rust is missing, Pake offers to install it on the first run.

The CLI then does five things in order. It checks the URL and options. It makes a temporary workspace under your system temp folder and copies the Tauri template into it, so your installed CLI is never edited. It settles the app name and icon, fetching the site's favicon unless you pass --icon. It merges your options into the config files in that workspace and runs the Tauri build there. And it copies the finished installer into the folder where you ran the command, then deletes the workspace.

On a machine that has never built a Pake app, expect the first run to take several minutes; the CLI says 5 to 10 minutes on macOS and Linux and 10 to 15 on Windows, because Rust compiles Tauri and its dependencies. The compiled output is kept in the CLI package's src-tauri/target folder, which is why later builds are faster.

What the generated config contains

The template lives in the CLI package's src-tauri folder. It holds a Rust project, a pake.json with defaults, a tauri.conf.json plus one per platform, and a folder of scripts in src-tauri/src/inject. Pake copies these files into a .pake folder in the workspace and rewrites them.

pake.json carries what Pake adds on top of Tauri: the URL, whether it is a web page or a local file, window size (1200 by 780 by default), always on top, dark mode, tray options, user agent, the files you asked to inject, and so on. tauri.conf.json carries what Tauri needs: the product name, the bundle identifier, the icon paths and the bundle targets. Pake derives the identifier from the URL and name, which is why two apps with different --name values for the same site install separately.

A few options are platform-specific, and the generator applies them per platform rather than failing. --hide-title-bar only applies on macOS, --hide-window-decorations only on Windows and Linux, and --hide-on-close defaults to on for macOS and off elsewhere. The options reference lists the full set, and Customize the window of a web app walks through the common ones.

What is compiled into the app

This is the part that surprises people. The config is not read from a file when the app starts. For a CLI build, the Rust code embeds .pake/pake.json and .pake/tauri.conf.json into the binary at compile time with include_str!, under a Cargo feature called cli-build. Changing an option means building again.

The same goes for the injected scripts. At run time the app creates its window with Tauri's window builder, passes a window.pakeConfig object to the page, and registers the scripts from src-tauri/src/inject as initialization scripts: link handling and OAuth detection (link_policy.js, auth.js, frame_links.js), a toast for download status, a fullscreen bridge on macOS and Linux, keyboard and navigation handling (event.js), the CSS that hides scrollbars and styles the drag strip (style.js), theme tracking, and an optional find bar when --enable-find is set. Your own --inject files are concatenated into custom.js, which is empty otherwise.

Diagram in two groups: at build time the pake command writes a generated config, which is compiled with the Tauri template and Rust into an installer; at run time the installed app opens a native window that hosts the system web view, runs Pake's injected scripts and loads the website URL

The config and scripts are compiled into the installer at build time; at run time the app is only a window around the web view your system already has.

The system web view

Pake's window is a Tauri window, and Tauri draws web content with the web view of the platform. The Tauri webview reference lists WKWebView on macOS, WebView2 on Windows and WebKitGTK on Linux. Pake adds no browser engine of its own, so the website runs in that engine, with Pake's scripts layered on top.

That is why a page can look or behave slightly differently from your browser, why a Cloudflare challenge may loop on Linux, and why Google sign-in may be refused in embedded views regardless of the flags you set. Tauri vs Electron for wrapping a website compares what each choice costs, What is WebView2 covers the Windows runtime, and Setting a user agent covers one of the few levers you have over how a site sees the app.

Bundling and why the app is small

The bundle step uses the target for your platform. On macOS the default is a .dmg, and --targets app gives a .app instead. On Windows it is an .msi. On Linux the default is distro-aware, deb and appimage on Debian and Ubuntu and rpm and appimage on Fedora and similar, with zst available too. The CLI can also print the result as JSON, including each artifact's path, size and format.

The installer holds a compiled Rust binary, your icon, and the embedded config and scripts. It does not hold Chromium. Pake's README says installers are typically under 10 MB on disk, and the FAQ is careful about what that means: it is installer size, not memory. A heavy single-page app opens with a similar memory cost in any browser on the same engine, and Pake adds little on top.

What runs when

At build time, Node and Rust run: the CLI, the Tauri build and the bundler. None of that is needed to run the finished app, and the people you give it to do not need Node or Rust.

At run time there is the Rust binary, the system web view and the website itself, loaded from your URL on each launch. The packaged URL is the home address, but web apps remember the main window's last URL by default and reopen it, unless you pass --no-restore-last-url. That is saved in a last-url.txt file in the app's data directory. Incognito apps and local HTML apps do not restore. Cookies, local storage and login sessions belong to the web view; their location depends on the platform and the web view's data store.

For a local folder, pake ./dist --name MyTool stages your files into the template's dist folder before the build and restores the original afterwards. The folder needs an index.html at its root.

When it does not work

If a build fails, the usual causes are on the toolchain side. Rust older than 1.85, missing Visual Studio Build Tools on Windows, missing WebKitGTK development packages on Linux, or a first Windows build that times out are all covered in the FAQ. When an AppImage build fails, the CLI retries it once with NO_STRIP=1, since the strip step is the most common cause, and if that fails too it suggests --targets deb.

If the app builds but the site misbehaves, the cause is almost always the engine or the site, not the build. Try the page in Safari on macOS or Edge on Windows first; if it fails there too, packaging will not help. If it works there, check whether the site needs another domain to stay inside the app (--safe-domain) or pop-up windows (--new-window), as the getting started guide describes.

To confirm what you built, open the installer, look at the file size, and launch the app. To confirm the config was applied, check the window size, title bar and always-on-top behavior against the options you gave. If you need a different engine, such as one version of Chromium on every system, Pake is the wrong tool and an Electron-based wrapper fits better.

FAQ

Does a Pake app need Node.js or Rust to run?

No. They are only needed to build it. The finished app is a compiled binary that uses the system web view.

Can I change an app's settings after it is built?

Not through a settings file for a CLI build, because the config is compiled in. Build again with the new option. A different name creates a separate app.

Where does the website's data live?

In the system web view's storage for that app. On Windows, the CLI docs say the packaged app uses its own WebView2 profile, not Chrome's or Edge's.