Aplicaciones en Windows

Aplicaciones en Windows

Creando una aplicación con Pywebview y Svelte.

Colores usados:

#ff3e00
#d02e22
#fdfdfd
#333

Codigo: PythonSvelte

CMS: no

Puede encontrar el código en Github

En la búsqueda de crear una aplicación que no ocupara mucho espacio y que generara un archivo .exe con todo lo necesario para ejecutarse me encontré con la plataforma Pywebview y el paquete Pyinstaller y tras leer y entender el funcionamiento de ambos logre compilar mi primera app Snippets que es una muy simple aplicación que sirve para guardar trozos de código y estés se guardan en una base de datos Sqlite.

Después de esto se me ha ocurrido crear una aplicación base donde he usado Svelte que según ellos dicen:

Svelte es un enfoque radicalmente nuevo para crear interfaces de usuario. Mientras que los frameworks tradicionales como React y Vue hacen la mayor parte de su trabajo en el navegador, Svelte cambia ese trabajo a un paso de compilación que ocurre cuando construyes tu aplicación.

Pasos a seguir

Lo primero de todo es crear un entorno virtual asi tendrás los paquetes esenciales para que funcione la aplicación, sino tienes el paquete instalado, puedes hacerlo con pip install virtualenv.

Ahora en el terminal escribimos:

env\Scripts\activate.bat

A partir de este momento ya estaremos usando Python en el entorno virtual asi que procedemos a instalar los paquetes de Pywebview y Pyinstaller.

pip install pywebview

pip install pyinstaller

Ahora vamos a comprobar que tenemos instalados los paquetes con:

pip list

Nos creara una lista de los paquetes que tenemos instalados en el entorno virtual y deberíamos tener instalados Pywebview y Pyinstaller.

Estructura

Esta es la estructura del contenido de la aplicación:

Nombre app/
├── assets (Directorio donde se alojan los archivos html, css, etc)
├── dist 
├── src/ (Contenedor de Svelte)
│   ├── routes/
│   │   ├── About.svelte
│   │   └── Home.svelte
│   ├── App.svelte
│   ├── data.json
│   └── main.js
├── build.bat (Archivo para compilar la app)
├── icon.ico (Icono de la app)
├── main.py (Archivo de Python)
├── package.json
├── README.md
└── rollout.config.js (Configuración de Svelte)

En la carpeta assets es donde se alojaran todos los archivos de la aplicación y por ahora no hará falta ordenarlas por carpetas por que básicamente son 5 o 6.

Configuración build y main.py

El archivo build.bat contiene la configuración de Pyinstaller que a mi me ha funcionado.

pyinstaller main.py ^
    --onefile ^ (Se creara un ejecutable solo)
    --clean ^ (Se borrara la cache)
    --windowed --noconsole ^ (No se abrira la consola)
    --hidden-import "clr" ^ 
    --name "Svelte" ^ (Nombre de la app)
    --icon "icon.ico" ^ (Icono de la App)
    --paths "E:\PYTHON\pywebview\env\Scripts/" ^ (Donde esta Python)
    --add-data "assets;assets" ^ (Carpeta de archivos html,css etc..)

    (Archivos necesarios para que funcione la app)
    --add-data "E:\PYTHON\pywebview\env\Lib\site-packages\webview\lib\WebBrowserInterop.x64.dll;./" ^
    --add-data "E:\PYTHON\pywebview\env\Lib\site-packages\webview\lib\WebBrowserInterop.x86.dll;./" ^
    --add-data "E:\PYTHON\pywebview\env\Lib\site-packages\webview\lib\Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;./" ^

    --exclude-module "tkinter" ^ (Esto no lo vamos a usar)
    --exclude-module "pyinstaller" (Esto ya no lo usaremos)

(Borramos esto de paso)
rm -rf build/ 
rm -rf Svelte.spec
rm -rf __pycache__

Archivo main.py

Es el archivo donde se aloja el código de la aplicación de Python, lo mejor es separarlo en partes para tenerlo mejor estructurado pero para esta aplicación no se necesita.

Lo básico para que funcione una app den Pywebview seria esto:

import webview
webview.create_window('Hello world', 'https://pywebview.flowrl.com/')
webview.start()

Con esto ya podríamos tener una app creada con una web pero en este caso vamos a crear una con todo lo necesario offline.

Importamos lo necesario:

#!/usr/bin/python3
import sys, os, json
import threading
import webview
import time

Comprobamos si esta congelada o no asi los archivos como los que tenemos alojados en assets no será necesario incluirlos en la carpeta de distribución.

frozen = 'not'
if getattr(sys, 'frozen', False):
  frozen = 'ever so'
  bundle_dir = sys._MEIPASS
else:
  bundle_dir = os.path.dirname(os.path.abspath(__file__))

Inicializamos la variable window

window = {}

Ahora se crea una clase para la Api donde incluimos las funciones que transmitiremos a Javascript con `window.pywebview.api.

class Api:
    # Cerramos la app
    def close(self, params):
        window.destroy()
    # Inico
    def init(self, params):
        response = {
            'status': 'Ready'
        }
        return {'data': response}

Ahora crearemos la ventana de la app:

if __name__ == '__main__':
    # Usamos la clase
    api = Api()
    # Creamos la ventada de la app
    window = webview.create_window(
        'Svelte app', (titulo)
        'assets/index.html', (archivo html)
        js_api=api, (la api)
        width=500, (tamaño horizontal)
        height=400, (tamaño vertical)
        resizable=False, (no se puede redimensionar)
        min_size=(500, 400),(tamaño minimo)
        text_select=True, (el texto se puede selecionar)
        confirm_close=True, (saldra una ventana confirmando que cerramos)
        frameless=True, (ventana sin marcos)
        easy_drag=False, (false para poner nosotros donde la moveremos)
    )
    # iniciamos app
    webview.start(http_server=True)

Svelte

Ahora vamos a crear una interfaz con Svelte que tenga dos secciones, para ello necesitamos instalar varias dependencias como vemos en el archivo package.json

{
    "name": "Svelte-app",
    "version": "1.0.0",
    "scripts": {
        "build": "rollup -c",
        "dev": "rollup -c -w",
    },
    "devDependencies": {
        "@rollup/plugin-commonjs": "^14.0.0",
        "@rollup/plugin-node-resolve": "^8.0.0",
        "rollup": "^2.3.4",
        "rollup-plugin-json": "^4.0.0",
        "rollup-plugin-livereload": "^2.0.0",
        "rollup-plugin-svelte": "^6.0.0",
        "rollup-plugin-terser": "^7.0.0",
        "svelte": "^3.0.0"
    },
    "dependencies": {
        "@rollup/plugin-json": "^4.1.0",
        "html5-history-api": "^4.2.10",
        "page": "^1.11.6",
        "sirv-cli": "^1.0.0"
    }
}

Algunas de las dependencias de desarrollo no las vamos a necesitar pero las dejamos por si acaso ya que no se incluirán al compilar la aplicación final.

Los archivos que se alojaran en src/ son estos:

├── src/ (Contenedor de Svelte)
│   ├── routes/ (Las secciones iran aqui)
│   │   ├── About.svelte
│   │   └── Home.svelte
│   ├── App.svelte (Aqui estaran las rutas de las secciones)
│   ├── data.json (Donde estan los textos)
│   └── main.js (archivo base)

Tambien necesitaremos el archivo de configuracion de rollup (rollup.config.js).

// importamos lo necesario
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
... los demas imports

// variable de producción
const production = !process.env.ROLLUP_WATCH;

export default {
    input: 'src/main.js', // archivo base que se convertirá a app.js
    output: {
        sourcemap: true,
        format: 'iife',
        name: 'app',
        file: 'assets/app.js' // el output de main.js
    },
    plugins: [
        // usamos Json para el archivo data.json
        json({
            compact: true
        }),
        svelte({
            dev: !production,
            css: css => {
                css.write('app.css'); // el archivo Css final
            }
        }),
        resolve({
            browser: true,
            dedupe: ['svelte']
        }),
        commonjs(),
        !production && livereload('assets'), // la carpeta donde estan alojados los archivos finales
        production && terser()
    ],
    watch: {
        clearScreen: false
    }
};

Archivo App.svelte

Este archivo contendrá las rutas de las secciones y la estructura general de la aplicación.

<script>
  // importamos la transicion fade
  import { fade } from "svelte/transition";
  // el modulo page
  import page from "page";
  // las secciones
  import Home from "./routes/Home.svelte";
  import About from "./routes/About.svelte";
  // los datos json 
  import Data from "./data.json";

  // seccion por defecto
  let current = Home;
  let params;
  // rutas
  page("/", () => (current = Home));
  page("/about", () => (current = About));
  page("/*", () => (current = Home));

  // creamos un cargador para pywebview
  let status = false;
  setTimeout(
    () =>
      // cuando este iniciado cargaremos las rutas
      // y se vera la aplicación
      pywebview.api.init("Python is Ready")
      .then((r) => {
        page.start();
        status = true;
      }),
    800
  );
  // al hacer click cerraremos la app
  function closeApp()
  {
    return pywebview.api.close('bye :)');
  }
</script>

<div class="app">
  <div class="app-header pywebview-drag-region">
    <div class="app-title">{Data.title}</div>
    <div class="app-close" on:click={closeApp}>&times;</div>
  </div>
  <div class="app-content">
    <!-- si status es true -->
    {#if status}
      <!-- transicion -->
      <div transition:fade>
        <!-- las secciones se cargaran aqui -->
        <svelte:component this={current} {params} />
      </div>
    <!-- si status es false -->
    {:else}
      <!-- transicion -->
      <div id="loader" transition:fade>
        <!-- cargador -->
        <div class="preloader" />
      </div>
    {/if}
  </div>
</div>

Y con esto ya tendremos la aplicación terminada espero que les haya valido para algo.

Recuerda que puede encontrar el código en Github