ES | EN

DESKTOP APPS WITHOUT ELECTRON: PYTHON + PYWEBVIEW AS A REAL ALTERNATIVE

TAGS: DESKTOP APPS / PYTHON / ARCHITECTURE READ_TIME: 16 MIN
Desktop apps without Electron: Python + pywebview as a real alternative

Electron has a problem its ecosystem refuses to admit: it consumes between 150MB and 400MB of RAM at idle, requires Node.js as a runtime, and ships a full Chromium instance bundled in every installation. A "simple" desktop app easily weighs 200MB just for the runtime. For projects where the core logic already lives in Python, there is an alternative few know about: pywebview. Same visual result, a fraction of the weight, no Node.js, no npm.

PROJECT_STATUS: STABLE

Stack: Python 3.10+ · pywebview 4.4 · HTML/CSS/JS
Reference project: FocOs v1.3 — Windows + Linux
Goal: Eliminate Electron from the stack — UI in HTML/CSS/JS, backend in pure Python, direct bridge without IPC

01. THE PROBLEM IT SOLVES

Electron forces you to duplicate logic: the UI in JavaScript/TypeScript and the backend in Node.js with IPC to communicate with the system. For Python projects, this means either rewriting the entire backend in Node or maintaining two separate processes. pywebview eliminates this duplication: UI is HTML/CSS/JS, backend is pure Python, and the bridge between them is direct.

// Non-technical explanation

Imagine you want a window with a web screen inside. Electron is like constructing an entire building to hold that window — it brings its own bricks, its own water supply, its own power generator. pywebview is like cutting a hole in your existing wall and fitting the window there — it uses what the house (the operating system) already has. Much lighter, much more efficient.

02. THE HONEST COMPARISON

The numbers make the decision. Electron has stronger support for specific edge cases — native notifications, advanced multi-window, DevTools in production. Outside those cases, pywebview wins on everything that matters for an independent project.

Factor Electron pywebview
Runtime Node.js + Chromium (~200MB) WebView2/WebKit native (0MB extra)
App size 150-300MB minimum 5-20MB (your code only)
Idle RAM 150-400MB 30-80MB
Backend Node.js required Pure Python
IPC ipcMain / ipcRenderer Direct bridge via js_api
Web engine Fixed Chromium WebView2 (Win) / WebKit (Linux/Mac)
Build electron-builder PyInstaller / Nuitka

03. INSTALLATION AND MINIMAL SETUP

Base installation is one line. Additional dependencies depend on the OS — Windows needs the WebView2 backend, Linux needs the system WebKitGTK.

# Base install
pip install pywebview

# Windows — WebView2 backend:
pip install pywebview[winforms]
# or explicit WebView2:
pip install pywebview[edgechromium]

# Linux — Ubuntu/Debian:
sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0

# Linux — Arch:
sudo pacman -S python-gobject webkit2gtk

# Minimal working app:
import webview

window = webview.create_window(
    title = 'My App',
    url = 'https://example.com',
    width = 1200,
    height = 800,
)
webview.start()

# Load local HTML:
from pathlib import Path
url = Path('web/index.html').resolve().as_uri()
window = webview.create_window(title='My App', url=url)
webview.start()

04. FOCOS ARCHITECTURE — REAL STRUCTURE

FocOs separates the project into two clear domains: the Python backend at the root and the complete frontend under web/. The SQLite database and LLM configuration live in data/, never mixed with UI code.

focos/
├── main.py # Entry point + FocOsAPI
├── focos_importer.py # Project import module
├── data/ # Database and config
│ ├── focos.db # SQLite — projects, tasks, workspaces
│ ├── llm_config.json # LLM configuration
│ └── projects_config.json
└── web/ # Complete frontend
    ├── index.html # Main shell
    ├── css/
    │ ├── main.css # Variables and reset
    │ ├── layout.css # Grid and structure
    │ └── theme.css # Dark-Amber theme
    └── js/
        ├── bridge.js # Python bridge abstraction
        ├── llm.js # LLM panel
        ├── editor.js # Monaco Editor with tabs
        ├── terminal.js # xterm.js terminal
        ├── browser.js # Embedded browser
        └── dashboard.js # Metrics and projects

05. THE FOCOSAPI CLASS — THE COMPLETE BRIDGE

Every method in FocOsAPI is accessible from JavaScript via window.pywebview.api.method_name(). Python types are serialized and deserialized automatically — no message protocol to maintain, no types to map manually.

# main.py
import webview, sqlite3, json, os
from pathlib import Path

BASE_DIR = Path(__file__).parent
DATA_DIR = BASE_DIR / 'data'
DB_PATH = DATA_DIR / 'focos.db'

class FocOsAPI:

    # -- SYSTEM ------------------------------------------------
    def get_version(self):
        return { 'version': '1.3', 'platform': os.name }

    def open_folder(self, path: str):
        '''Opens a folder in the native OS file explorer'''
        import subprocess
        if os.name == 'nt':
            subprocess.Popen(['explorer', path])
        else:
            subprocess.Popen(['xdg-open', path])
        return { 'ok': True }

    # -- FILES -------------------------------------------------
    def open_file_dialog(self):
        result = window.create_file_dialog(
            dialog_type = webview.FileDialog.OPEN,
            allow_multiple = False,
        )
        if result:
            return { 'ok': True, 'path': str(result[0]) }
        return { 'ok': False }

    def read_file(self, path: str):
        p = Path(path)
        text = p.read_text(encoding='utf-8', errors='replace')
        return { 'ok': True, 'text': text }

    def write_file(self, path: str, content: str):
        Path(path).write_text(content, encoding='utf-8')
        return { 'ok': True }

# -- STARTUP -----------------------------------------------
def main():
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    init_db()

    api = FocOsAPI()
    index_url = (BASE_DIR / 'web' / 'index.html').resolve().as_uri()
    global window
    window = webview.create_window(
        title = 'FocOs',
        url = index_url,
        js_api = api,
        width = 1400,
        height = 900,
        min_size = (900, 600),
        resizable = True,
        background_color = '#0A0A0A',
    )
    webview.start(on_start, debug=False)

if __name__ == '__main__':
    main()

06. DISTRIBUTING THE APP — PYINSTALLER

PyInstaller bundles the Python interpreter, the app, and all assets into a single executable. The result on Windows is a ~15MB .exe — no external runtime, no visible dependencies for the end user.

# Install PyInstaller
pip install pyinstaller

# Windows — separator ';'
pyinstaller --onefile --windowed \
  --add-data 'web;web' \
  --add-data 'data;data' \
  --name 'FocOs' \
  main.py

# Linux — separator ':'
pyinstaller --onefile --windowed \
  --add-data 'web:web' \
  --add-data 'data:data' \
  --name 'focos' \
  main.py

# Output:
dist/
  FocOs.exe # Windows -- ~15MB, no external runtime
  focos # Linux -- ~12MB
[!] NOTE: WEBVIEW2 ON WINDOWS

pywebview uses WebView2 on Windows. It comes pre-installed on Windows 11. On Windows 10 it may require additional installation by the end user. Check availability at: developer.microsoft.com/en-us/microsoft-edge/webview2/

07. REAL LIMITATIONS OF PYWEBVIEW

pywebview is not a universal Electron replacement. There are cases where Electron remains the correct choice. Knowing the constraints before committing to the architecture prevents rewrites.

Limitation Python workaround
DevTools only with debug=True Enable in development, disable in production build.
Multi-window with constraints Design the app as an SPA with panels — eliminates the need for multiple windows.
No native notification API plyer or notify2 from Python — independent of pywebview.
No system tray / dock API pystray from Python — integrates in the same process.

-- CONCLUSION

pywebview + Python is the right architecture for any desktop app where the core backend already lives in Python. Dropping Electron reduces distribution size from ~200MB to ~15MB, idle RAM from ~300MB to ~50MB, and stack complexity from 3 layers to 2 direct layers. FocOs proves it is possible to build a premium desktop app — with a code editor, terminal, browser, and LLM panel — without a single line of Node.js.

> SYSTEM_READY > NODE_ONLINE

< session_end // node: exit >
> INFOGRATECH_CORE_SHELL X
$