Desktop app#

This page describes the decisions and learnings made during the development of the desktop app using electron. The app integrates the local file system and works fully offline. It is built automatically by GitLab Pipelines.

Tauri vs. Electron#

At the time of writing, there are two major tools for building cross-platform desktop-apps using web technologies: Electron and Tauri. Both have their advantages and disadvantages, which is why they were compared in depth before making a decision, which should be used for WebTP.

Electron was originally developed by GitHub and powers apps such as VS Code and Slack. It ships with an instance of Chromium which results in quite a large bundle size and high resource consumption.

Tauri is a modern alternative designed to address these efficiency issues. Tauri uses the operating system’s native Webview to render the frontend, and the backend is written in Rust rather than Node.js. Tauri was tested extensively, but finally the decision fell in favor of electron.

Why not Tauri?#

It was not possible to directly use the existing website in a Tauri App, since the current implementation using pyodide-worker-runner relies on Service Workers intercepting some specific requests. This is used to mimic blocking code such as delay(ms) or input() from python in the asynchronous execution model of the browser. The Service Worker never sees these requests, since they get routed directly to the Tauri backend and result in a 404 error.

While it is possible to register Service Workers using a custom secure protocol, the rerouting of the request is not possible to work around in a secure manner. Using Tauri’s localhost plugin, a web server is spun up and everything works fine. But this has the caveat, that every website could theoretically make requests to the said web server (by correctly guessing the localhost port) and thus gain access to the file system (since we want file system access in the app).

Electron setup#

The following tree shows the important files that are used to build the desktop app.

.
├── package.json
├── index.html
├── forge.config.ts
├── public/
│   └── pyodide/
└── electron/
    ├── main.ts
    ├── preload.ts
    └── icons/

package.json#

Contains the scripts to run and build the desktop app using electron-forge. npm run dev:electron runs the dev server and opens the desktop app for development. npm run build:electron compiles the web assets and npm run make:electron --platform [darwin|win32|linux] then packages the compiled code with the electron binary into an installer for the respective operating system.

index.html#

As in the browser, this is the file that is loaded when the app starts up.

forge.config.ts#

Electron-forge is used for the build pipeline of the app. It combines many different packages to create a full build pipeline that works out of the box. The config.makers field contains the makers for the different targets.

public/pyodide/#

This is the folder where all pyodide-related files are copied, which enable the app to work fully offline. See the section below.

electron/main.ts#

This is the backend of the app, where the main window is created. The events, through which the frontend communicates with the backend, are also handled here.

electron/preload.ts#

This file exposes the electron api to the frontend, which is then accessible through window.electronAPI.

electron/icons/#

This folder contains the app icons. The icon.icon is only supported when the app is build using xcode 26+, as long as the app is built on an intel mac, the filename cannot match icon.*.

Local file system integration#

It was decided that the file system integration should be just another option in the provider menu to keep the user experience as similar as possible. The goal was to instead of only importing a folder to open a directory and mirror any changes directly to the file system, similar to a standard text editor.

There are libraries that do the heavy lifting of syncing the app with the native file system. chokidar is very popular and well-tested, which is why it was chosen for this application. Using chokidar, the selected folder is watched for changes, which get mirrored into the in-app virtual file system. Changes from the editor are written immediately to the file system. Together, these two mechanisms ensure that the in-app virtual file system and the host file system are always in sync.

Offline support#

By default, pyodide loads all necessary files from a cdn at runtime. Since we want the app to work offline, we have to “self-host” all necessary files, so that they are included in the app bundle.

Following the “Working with Bundlers” guide in the pyodide docs, the contents of the pyodide package are copied to the public folder when building the app. This does not yet include the packages that can be additionally used in the python code. Using a script, all available packages are fetched from the cdn and copied to the public folder.

GitLab pipelines#

To build the app for a specific platform, the make script has to be executed on the correct architecture.

Windows and Linux#

The provided runners in the ETHZ GitLab are running on amd64 hardware. We can use respective images for the pipeline stages to build the windows and linux apps.

MacOS#

Since the ETHZ GitLab is self-hosted, we don’t have access to the GitLab managed runners. To build for MacOS, we thus need to connect a device running MacOS which can the execute the stage.

Requirements#

The mac should have the following installed:

Runner setup#

  1. In the GitLab repo, navigate to Settings > CI/CD > Runners

  2. Click Create project runner

    1. Add tag macos

    2. Set the maximum job timeout sufficiently high (e.g. 7200)

  3. Click Create runner

  4. Choose macOS as operating system

  5. Execute step 1 (gitlab-runner register ...)

    1. As instance url, specify https://gitlab.inf.ethz.ch

    2. Choose the shell executor

  6. Run sudo gitlab-runner install

  7. Run gitlab-runner start

Future work#

Mobile app#

Electron (in contrast to Tauri) cannot build for mobile targets. In mid 2026, the best solution would be to use a tool like Capacitor and reimplement the backend logic.