ES | EN

APPS DE ESCRITORIO SIN ELECTRON: PYTHON + PYWEBVIEW COMO ALTERNATIVA REAL

TAGS: DESKTOP APPS / PYTHON / ARQUITECTURA READ_TIME: 16 MIN
Apps de escritorio sin Electron: Python + pywebview como alternativa real

Electron tiene un problema que nadie en su ecosistema quiere admitir: consume entre 150MB y 400MB de RAM en reposo, requiere Node.js como runtime, y distribuye un Chromium completo empaquetado en cada instalación. Una app "simple" de escritorio pesa fácilmente 200MB solo por el runtime. Para proyectos donde la lógica principal ya está en Python, existe una alternativa que pocos conocen: pywebview. Mismo resultado visual, fracción del peso, sin Node.js, sin npm.

PROJECT_STATUS: STABLE

Stack: Python 3.10+ · pywebview 4.4 · HTML/CSS/JS
Proyecto de referencia: FocOs v1.3 — Windows + Linux
Objetivo: Eliminar Electron del stack — UI en HTML/CSS/JS, backend en Python puro, bridge directo sin IPC

01. EL PROBLEMA QUE RESUELVE

Electron obliga a duplicar la lógica: la UI en JavaScript/TypeScript y el backend en Node.js con IPC para comunicarse con el sistema. Para proyectos Python, esto significa reescribir todo el backend en Node o mantener dos procesos separados. pywebview elimina esta duplicación: la UI es HTML/CSS/JS, el backend es Python puro, y el bridge entre los dos es directo.

// Explicación no técnica

Imagina que quieres hacer una ventana con una pantalla web adentro. Electron es como construir un edificio entero para poner esa ventana — trae sus propios ladrillos, su propia agua, su propio generador de luz. pywebview es como abrir un hueco en la pared de tu casa y poner la ventana ahí — usa lo que la casa (el sistema operativo) ya tiene. Mucho más liviano, mucho más eficiente.

02. LA COMPARACIÓN HONESTA

Los números son los que toman la decisión. Electron tiene mejor soporte para casos de uso muy específicos — notificaciones nativas, multi-ventana avanzado, DevTools en producción. Fuera de esos casos, pywebview gana en todo lo que importa para un proyecto independiente.

Factor Electron pywebview
Runtime Node.js + Chromium (~200MB) WebView2/WebKit nativo (0MB extra)
Tamaño app 150-300MB mínimo 5-20MB (solo tu código)
RAM en reposo 150-400MB 30-80MB
Backend Node.js obligatorio Python puro
IPC ipcMain / ipcRenderer Bridge directo con js_api
WebEngine Chromium fijo WebView2 (Win) / WebKit (Linux/Mac)
Build electron-builder PyInstaller / Nuitka

03. INSTALACIÓN Y SETUP MÍNIMO

La instalación base es una línea. Las dependencias adicionales dependen del OS — en Windows se necesita el backend WebView2, en Linux el WebKitGTK del sistema.

# Instalación base
pip install pywebview

# Windows — backend WebView2:
pip install pywebview[winforms]
# o para WebView2 explícito:
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

# App mínima funcional:
import webview

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

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

04. LA ARQUITECTURA DE FOCOS — ESTRUCTURA REAL

FocOs separa el proyecto en dos dominios claros: el backend Python en la raíz y el frontend completo bajo web/. La base de datos SQLite y la configuración de LLM viven en data/, nunca mezclados con el código de UI.

focos/
├── main.py # Entrada principal + FocOsAPI
├── focos_importer.py # Módulo de importación de proyectos
├── data/ # Base de datos y configuración
│ ├── focos.db # SQLite — proyectos, tareas, workspaces
│ ├── llm_config.json # Configuración de LLM
│ └── projects_config.json
└── web/ # Frontend completo
    ├── index.html # Shell principal
    ├── css/
    │ ├── main.css # Variables y reset
    │ ├── layout.css # Grid y estructura
    │ └── theme.css # Dark-Amber tema
    └── js/
        ├── bridge.js # Abstracción del bridge Python
        ├── llm.js # Panel de LLM
        ├── editor.js # Monaco Editor con pestañas
        ├── terminal.js # Terminal xterm.js
        ├── browser.js # Browser embebido
        └── dashboard.js # Métricas y proyectos

05. LA CLASE FOCOSAPI — EL BRIDGE COMPLETO

Todos los métodos de FocOsAPI son accesibles desde JavaScript via window.pywebview.api.nombre_del_metodo(). Los tipos Python se serializan y deserializan automáticamente — no hay protocolo de mensajes que mantener, no hay tipos que mapear manualmente.

# 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:

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

    def open_folder(self, path: str):
        '''Abre una carpeta en el explorador nativo del OS'''
        import subprocess
        if os.name == 'nt':
            subprocess.Popen(['explorer', path])
        else:
            subprocess.Popen(['xdg-open', path])
        return { 'ok': True }

    # -- ARCHIVOS -----------------------------------------------
    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 }

# -- ARRANQUE -----------------------------------------------
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. DISTRIBUIR LA APP — PYINSTALLER

PyInstaller empaqueta el intérprete Python, la app y todos los assets en un único ejecutable. El resultado en Windows es un .exe de ~15MB — sin runtime externo, sin dependencias visibles para el usuario final.

# Instalar PyInstaller
pip install pyinstaller

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

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

# Resultado:
dist/
  FocOs.exe # Windows -- ~15MB sin runtime externo
  focos # Linux -- ~12MB

# Incluir icono:
# Windows: --icon=web/assets/focos.ico
# Linux: --icon=web/assets/focos.png
[!] NOTA: WEBVIEW2 EN WINDOWS

pywebview usa WebView2 en Windows. Viene preinstalado en Windows 11. En Windows 10 puede requerir instalación adicional por parte del usuario. Verificar disponibilidad en: developer.microsoft.com/en-us/microsoft-edge/webview2/

07. LIMITACIONES REALES DE PYWEBVIEW

pywebview no es un reemplazo universal de Electron. Hay casos donde Electron sigue siendo la opción correcta. Conocer los límites antes de comprometer la arquitectura evita reescrituras.

Limitación Workaround en Python
DevTools solo con debug=True Activar en desarrollo, desactivar en build de producción.
Multi-ventana con limitaciones Diseñar la app como SPA con paneles — evita la necesidad de ventanas múltiples.
Sin API para notificaciones nativas plyer o notify2 desde Python — independiente de pywebview.
Sin API para system tray / dock pystray desde Python — se integra en el mismo proceso.

-- CONCLUSION

pywebview + Python es la arquitectura correcta para cualquier app de escritorio donde el backend principal ya está en Python. La eliminación de Electron reduce el tamaño de distribución de ~200MB a ~15MB, el consumo de RAM en reposo de ~300MB a ~50MB, y la complejidad del stack de 3 capas a 2 capas directas. FocOs demuestra que es posible construir una app premium de escritorio — con editor de código, terminal, browser y panel LLM — sin una sola línea de Node.js.

> SYSTEM_READY > NODE_ONLINE

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