2 d’abril del 2018

Electron: aplicacions d'escriptori amb tecnologia web

Darrerament, he publicat una sèrie d'articles sobre com fer una aplicació híbrida, per Android, iOS i web utilitzant typescript (javascript), html i CSS3.

En aquest article, veurem com amb aquesta mateixa base de codi podem construir també una aplicació d'escriptori gràcies a Electron. Bàsicament, el que fa és empaquetar l'aplicació Ionic que ja teníem per poder construir una aplicació d'escriptori, que s'executa sobre una pàgina Chrome personalitzada. La part bona és que Electron s'ocupa de tot, així que els canvis a fer són mínims.

Ho mostrarem amb l'aplicació per saber l'hora de sortida i posta del sol que vàrem fer pas a pas:

El codi de l'aplicació Ionic final està disponible en el Github: v1.0.

Per desgràcia, no hi ha encara una plantilla per a un projecte Electron amb Ionic. Hi ha informació diversa, però és difícil extreure'n el gra de la palla. A mi m'han servit molt els articles del bloc de Rob Ferguson. Tot seguit us resumeixo el procés d'adaptar una aplicació Ionic que ja tenim funcionant per afegir-hi Electron i poder generar una aplicació d'escriptori.

En primer lloc, instal·lem electron:
 npm install -g electron

Tot seguit creem un directori electron a l'arrel del projecte hi hi posem la pàgina principal d'electron, main.js, modificada per carregar l'aplicació Ionic:
const electron = require('electron');
// Module to control application life.
const app = electron.app;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;

const path = require('path');
const url = require('url');

// Module for file manipulations
const fs = require('fs');

// config json
let config_file = path.dirname(process.execPath)+'/config_keys.json';

if(!fs.existsSync(config_file)) {
   config_file = app.getAppPath()+'/config_keys.json';
}

const config = JSON.parse(fs.readFileSync(config_file, 'utf-8'));

process.env.GOOGLE_API_KEY = config.GOOGLE_API_KEY;  //  = "YOUR_API_KEY";

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;

function createWindow() {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 1024, height: 768});

  // and load the index.html of the app.
  const startUrl = process.env.ELECTRON_START_URL || url.format({
    pathname: path.join(__dirname, '/../www/index.html'),
    protocol: 'file:',
    slashes: true
  });

  mainWindow.loadURL(startUrl);
  // mainWindow.loadURL("http://localhost:8100");

  // Open the DevTools.
  // mainWindow.webContents.openDevTools();

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
});

app.on('activate', function () {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow()
  }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

El contingut és bàsicament el d'un projecte inicial d'Electron. Els canvis que s'han fet són:
  • [10-22] Es llegeix un fitxer de configuració config_keys.json que inclou el codi de la clau de Google per a les API de les aplicacions de Maps i Geolocation. Sense aquesta clau, l'aplicació d'escriptori no té permisos per utilitzar geolocalització.
  • [32-37] S'indica a Electron d'on ha de carregar la pàgina inicial. Justament és la de l'aplicació Ionic.
El fitxer de claus config_keys.json es guarda a l'arrel del projecte, i el seu contingut serà com segueix (lògicament, canviant la clau per una de bona):
 {
 "GOOGLE_API_KEY": "You_API_Key_here" 
 }

Al fitxer src/index.html d'Ionic eliminem la referència a cordova.js, que no és necessària en una aplicació d'escriptori:
  <!-- cordova.js required for cordova apps (remove if not needed)
  <script src="cordova.js"></script>
 -->

Finalment, editem el package.json per afegir-hi les noves dependències i instruccions de com treballar amb electron:
 {
  ...
  "scripts": {
    ...
    "start": "ionic serve --no-open",
    "electron": "electron .",
    "dist": "electron-builder",
    ...
  },
  ...
  "devDependencies": {
    "@angular/cli": "^1.5.4",
    "@angular/router": "^5.0.3",
    "@ionic/app-scripts": "3.1.8",
    "@types/node": "^8.0.53",
    "concurrently": "^3.5.0",
    "electron": "^1.7.9",
    "electron-builder": "^19.45.1",
    "ionic-mocks": "^1.0.4",
    "typescript": "2.4.2",
    "wait-on": "^2.0.2"
  },
  "config": {
    "ionic_source_map_type": "source-map"
  },
  "main": "electron/main.js",
  "build": {
    "appId": "com.blogspot.anomenaidesa.sunsetsunrise",
    "files": [
      "electron/main.js",
      "www/**/*"
    ],
    "extraFiles": [
      "config_keys.json"
    ],
    "mac": {
      "category": "productivity"
    },
    "linux": {
      "category": "Utility"
    }
  },
  ...
 }

Ara només ens cal instal·lar les dependències amb:
 npm install

I provar l'aplicació executant primer en un terminal:
 npm start

I, quan acabi de preparar l'aplicació, en un altre terminal:
 npm run electron

Un cop comprovat que obtenim el mateix funcionament que abans amb Ionic, ja podem generar l'aplicació d'escriptori. És tan senzill com fer:
 npm run dist

En Linux, el resultat és una aplicació distribuïble, SunriseSunset-1.1.0-x86_64.AppImage, que encapsula totes les dependències i que es pot executar directament. El resultat és el següent:

 
Tal com es pot veure, té l'aspecte natiu d'una aplicació Ubuntu i el funcionament és idèntic al que teníem en l'aplicació Ionic original.

Noteu com una mateixa base de codi typescript/html/css3 permet generar aplicacions mòbils, web i d'escriptori. Si no necessitem les prestacions addicionals d'una aplicació nativa, la flexibilitat d'aquesta proposta resulta extremadament atractiva, no us sembla?

Com sempre, si voleu practicar, teniu el codi final a la vostre disposició en el meu Github: v1.1.0.

De fet, fins i tot podeu descarregar i provar directament l'aplicació d'escriptori: SunriseSunset-1.1.0-x86_64.AppImage (per Ubuntu, Debian o altres variants compatibles).

Ja em comentareu què us ha semblat !