Electron - construindo aplicações desktop com JavaScript
O ecossistema do JavaScript tem se demonstrado muito versátil ao longo dos anos, com grande inovações, uma delas é a possibilidade de construir aplicativos Desktop utilizando apenas Node.js, HTML, CSS e claro JavaScript.
⚛️ O que é?
Electron é um shell multiplataforma — uma interface do usuário para acessar serviços do sistema operacional tanto via linha de comando (CLI) e interface gráfica (GUI).
Com Electron podemos desenvolver aplicações desktop usando HTML, CSS e Javascript – As famosas tecnologias da Web. Possui integração com Node.js, podemos usá-lo para construir não apenas as telas mas toda a lógica de um BackEnd que acessa recursos do sistema operacional — diretórios locais, banco de dados de maneira mais simples.
É multiplataforma — podemos instalar a aplicação no Windows, Linux e MacOS.
📖 História
Electron foi criado por Cheng Zhao, e lançado em 11 de abril de 2013 com o nome Atom Shell. Logo em 6 de maio de 2014 o editor de códigos Atom do GitHub se tornou open source sob a licença MIT. Em 2015 o Atom Shell foi renomeado para Electron, e com passar do tempo foi se popularizando e em apenas dois anos conseguiram criar e disponibilizar as atualizações automáticas dos aplicativos, instaladores Windows, envio de logs de erros dos aplicativos, notificações no desktop, entre outras funcionalidades. Em 2016 a Windows Store já começou aceitar aplicativos Electron em sua loja.
No blog do Electron tem outras informações e curiosidades.
⏳ Aplicativos desktop em 2020?
Sim. São vários cases de sucesso e necessidades que possuímos — como programadores usamos o editor de código VsCode que foi feito com Electron, o próprio Atom o primeiro app feito com Electron.
Foi construído também com o Slack, Figma, Twitch, Whatsapp desktop, entre outros apps que você pode encontrar nesse site: https://www.electronjs.org/apps
🏛️ Arquitetura
Electron vem com navegador Chromium, um projeto open source de onde surgiu o Google Chrome. Toda a parte visual, janelas, etc são renderizadas nessa camada e o BackEnd executado em Node.js. Ambos tem acesso um ao outro via RPC (Remote Procedure Call).
Dois conceitos importantes em volta da tecnologia: processo principal (main process) e o processo de renderização (renderer process)
O arquivo que é definido na propriedade main
do package.json
é chamado de processo principal. Ele é único em toda a aplicação, responsável por criar a(s) janela(s) da aplicação através de instâncias de BrowserWindow.
Electron usa o Chromium para renderizar as páginas web, a arquitetura de multiprocesso do dessa ferramenta é utilizada também, cada página web (janela desktop do Electron) executa o seu próprio processo, que é chamado de processo de renderização.
Resumumindo, temos o Node.js no processo principal usando a biblioteca Electron que faz chamadas nativas no sistema operacional. E a API do Electron executando as janelas da aplicação com HTML, CSS, JavaScript e seus assets do por baixo dos panos com Chromium.
As janelas (FrontEnd) não podem invocar os recursos nativos para não quebrar a segurança da aplicação, porém a comunicação entre ambos processos — principal e renderização, é feita através de um conceito de comunicação entre processos (IPC) usando o método RPC (Chamada de Procedimentos Remotos). Para isso temos as APIs ipcRender e ipcMain.
Esse é um assunto avançado, podemos criar um post só para isso. Esse conhecimento é necessário quando formos acessar um banco de dados e outros recursos do sistema operacional com requisições feitas a partir das janelas da aplicação (renderer process).
📁 Estrutura do Projeto
Aplicação em Electron, é essencialmente uma aplicação em Node.js, precisa de:
- package.json - Aponta para o arquivo principal do app e lista as suas dependências.
- main.js - Inicia o app e cria uma janela do navegador para renderizar HTML. Este é o processo principal do app (main process.).
- index.html - Uma página da web que é renderizada. Este é o processo de renderização do app (renderer process).
👋 Criando o primeiro projeto
Pré-requisitos: Conhecimento básico em HTML, CSS, JavaScript e Node.js.
Para configurar o ambiente em seu sistema operacional basta seguir a documentação que já está em português.
Vamos criar esse App:
Aqui está o código fonte se precisar consultar durante o passo a passo.
Iniciando o projeto
Crie uma pasta para fazer seu projeto e inicialize-o em Node.js:
mkdir hi-electron && npm init -y
Abra a pasta hi-electron
no seu editor de código. Se estiver usando o VSCode: code .
Instale a dependência do electron:
npm install --save-dev electron
Após a instalação, vai ser criada a pasta node_modules
na raiz do projeto.
No package.json
vai ter uma propriedade "main" você pode alterar de index.js
para main.js
esse é o arquivo principal do projeto que vamos criar em seguida. E na propriedade scripts
altera o test
para start
e coloca electron main.js
para o Electron executar o arquivo main.js
.
{
"name": "hi-electron",
"version": "1.0.0",
"description": "Meu primeiro app em Electron",
"main": "main.js",
"scripts": {
"start": "electron main.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"electron": "^9.1.2"
}
}
O arquivo main.js
vai conter o código responsável por criar janelas e manipular todos os eventos do sistema operacional.
Vamos criar um arquivo main.js
e index.html
na raiz do projeto usando o terminal se preferir:
touch main.js index.html
🔃 Adicionando o Live Reload
Vamos adicionar uma lib para fazer live reload da aplicação a cada alteração de código que fizermos, isso ajuda bastante no desenvolvimento.
Sem ela teríamos que parar o processo do node e executar novamente a cada vírgula que altermos no código.
npm install electron-reload
Logo iremos ver o código main.js
que irá configurar a aplicação e usar essa lib que acabamos de instalar.
Adicionando um ícone no app
Crie a pasta build
e adicione a imagem icon.png
que você pode baixar aqui.
O arquivo main.js
Basicamente esse é o primeiro código do app com Electron e deixei comentado cada trecho para você entender. Esse é o arquivo de entrada que será executado quando rodarmos o comando npm start
.
Copie e cole esse código no arquivo main.js
que criamos e dê uma lida nos comentários:
const { app, BrowserWindow, nativeImage } = require("electron");
// Habilita o live reload no Electron e no FrontEnd da aplicação com a lib electron-reload
// Assim que alguma alteração no código é feita
require("electron-reload")(__dirname, {
// Note that the path to electron may vary according to the main file
electron: require(`${__dirname}/node_modules/electron`),
});
// Função que cria uma janela desktop
function createWindow() {
// Adicionando um ícone na barra de tarefas/dock
const icon = nativeImage.createFromPath(`${app.getAppPath()}/build/icon.png`);
if (app.dock) {
app.dock.setIcon(icon);
}
// Cria uma janela de desktop
const win = new BrowserWindow({
icon,
width: 800,
height: 600,
webPreferences: {
// habilita a integração do Node.js no FrontEnd
nodeIntegration: true,
},
});
// carrega a janela com o conteúdo dentro de index.html
win.loadFile("index.html");
// Abre o console do navegador (DevTools),
// manter apenas quando estiver desenvolvendo a aplicação,
// pode utilizar variáveis de ambiente do node para executar esse código apenas quando estiver em modo DEV
// win.webContents.openDevTools();
}
// Método vai ser chamado assim que o Electron finalizar sua inicialização
// e estiver pronto para abrir e manipular o nosso código.
// Algumas APIs podem ser usadas somente depois que este evento ocorre.
app.whenReady().then(createWindow);
// Quando clicarmos no botão de fechar a janela no app desktop
// O evento vai ser ouvido aqui no arquivo main.js e algum procedimento pode ser realizado
// tipo fechar alguma conexão de banco de dados por exemplo.
app.on("window-all-closed", () => {
// No MacOS quando fecha uma janela, na verdade ela é "minimizada"
// e o processo executa em segundo-plano tipo um app do celular
// Para fechar e encerrar o app tem que teclar Cmd+Q ou no dock (barra de tarefas)
// clicar com botão direito e encerrar o app
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
// Esse evento é disparado pelo MacOS quando clica no ícone do aplicativo no Dock.
// Basicamente cria a janela se não foi criada.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Abaixo você pode colocar seus códigos específicos do BackEnd que precisam executar no processo principal
// pode criar pastas e arquivos separados e importar aqui (boa prática).
O arquivo index.html
No arquivo index.html cole esse código abaixo, nele contém a estrutura HTML, a estilização em CSS e a lógica implementada em JavaScript para manipular o DOM.
Poderia deixar o CSS e o JavaScript separado o que é melhor ainda. Veja a branch master do projeto.
Fiz alguns comentários no código JavaScript para entender a lógica.
<html>
<head>
<meta charset="UTF-8">
<title>Hello Rocketseat</title>
<!-- <https://electronjs.org/docs/tutorial/security#csp-meta-tag> -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<!--Importo o a fonte Roboto do Google Fonts -->
<link href="<https://fonts.googleapis.com/css2?family=Roboto&display=swap>" rel="stylesheet">
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 250px;
background-color: #8257e5;
color: #f0f0f0;
font-family: "Roboto", sans-serif;
}
.intro {
display: flex;
flex-direction: column;
align-items: center;
}
ul {
margin: 0;
padding: 0;
}
/* Style the list items */
ul li {
cursor: pointer;
position: relative;
padding: 12px 8px 12px 40px;
list-style-type: none;
background: transparent;
font-size: 18px;
transition: 0.2s;
/* make the list items unselectable */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Set all odd list items to a different color (zebra-stripes) */
ul li:nth-child(odd) {
/* background: #f9f9f9;
*/
}
/* Darker background-color on hover */
ul li:hover {
background: #7159c1;
}
/* When clicked on, add a background color and strike out text */
ul li.checked {
background: rgba(200, 200, 200, 0.3);
color: #fff;
text-decoration: line-through;
}
/* Add a "checked" mark when clicked on */
ul li.checked::before {
content: "";
position: absolute;
border-color: #fff;
border-style: solid;
border-width: 0 2px 2px 0;
top: 10px;
left: 16px;
transform: rotate(45deg);
height: 15px;
width: 7px;
}
/* Style the close button */
.close {
position: absolute;
right: 0;
top: 0;
padding: 12px 16px 12px 16px;
}
.close:hover {
background-color: rgba(130, 702, 205, 0.3);
color: white;
}
/* Style the header */
.card__todo {
margin: 5px;
background-color: transparent;
padding: 30px 40px;
color: white;
text-align: center;
}
/* Clear floats after the header */
.card__todo:after {
content: "";
display: table;
clear: both;
}
/* Style the input */
input {
margin: 0;
border: none;
border-radius: 0;
width: 75%;
padding: 10px;
float: left;
font-size: 16px;
background-color: rgba(0, 0, 0, 0.3);
color: #fff;
}
::placeholder {
color: #ddd;
}
/* Style the "Add" button */
.btn__add {
padding: 10px;
width: 15%;
background: #7159c1;
color: #fff;
float: left;
text-align: center;
font-size: 16px;
cursor: pointer;
transition: 0.3s;
border-radius: 0;
}
.btn__add:hover {
background-color: rgba(200, 200, 200, 0.3);
}
</style>
</head>
<body>
<div class="intro">
<h1>Hi Electron!</h1>
<p>
We are using node <script>document.write(process.versions.node)</script>,
Chrome <script>document.write(process.versions.chrome)</script>,
and Electron <script>document.write(process.versions.electron)</script>.
</p>
<h3>Meu primeiro app feito em Electron, usando só HTML, CSS e JS</h3>
</div>
<div class="card__todo">
<h2>O que vou fazer hoje?</h2>
<input type="text" onkeyup="addNewTodo(event)" id="inputTodo" placeholder="Escreva sua atividade...">
<span onclick="addNewTodo()" class="btn__add">ADD</span>
</div>
<ul id="ulTodo">
<li class="checked">Fazer inscrição na NLW02</li>
<li>Ler um post no blog da Rocketseat</li>
<li class="checked">Estudar JavaScript</li>
<li>Estudar a Doc do Electron</li>
</ul>
<script>
console.log(" ---> Lendo o arquivo todos.js");
// Cria os botões para excluir um item da lista de tarefas (que já foram criados no via html).
const myNodelist = document.querySelectorAll("li");
myNodelist.forEach((_, index) => {
let span = document.createElement("span");
let iconDelete = document.createTextNode("\\u00D7");
span.className = "close";
span.appendChild(iconDelete);
myNodelist[index].appendChild(span);
});
// Clicando no botão para excluir a tarefa da lista
const closesButton = document.querySelectorAll(".close");
closesButton.forEach((_, index) => {
closesButton[index].onclick = function () {
const div = this.parentElement;
div.style.display = "none";
};
});
// Marca/desmarca a tarefa como feita quando clicar em algum item da lista
const todoList = document.querySelector("ul");
todoList.addEventListener(
"click",
function (e) {
if (e.target.tagName === "LI") {
e.target.classList.toggle("checked");
}
},
false
);
// Cria uma nova tarefa na lista de tarefas quando clicar no botão ADD ou pressionar ENTER no teclado
function addNewTodo(event) {
if (event && event.keyCode !== 13) return;
const li = document.createElement("li");
const inputTodo = document.getElementById("inputTodo").value;
let t = document.createTextNode(inputTodo);
li.appendChild(t);
if (inputTodo === "") {
alert("Digite alguma tarefa!");
} else {
document.getElementById("ulTodo").appendChild(li);
}
document.getElementById("inputTodo").value = "";
const span = document.createElement("SPAN");
const iconDelete = document.createTextNode("\\u00D7");
span.className = "close";
span.appendChild(iconDelete);
li.appendChild(span);
span.onclick = function () {
const div = this.parentElement;
div.style.display = "none";
};
}
</script>
</body>
</html>
Pronto! Finalizamos o nosso app desktop.
Executando o projeto
Acesse o terminal e na raiz do projeto execute:
npm start
ou electron main.js
O projeto será iniciado e você vai ter esse resultado:
👍 Conclusão
Gostei bastante da possibilidade de criar um aplicativo Desktop bonito e de maneira fácil, usando as tecnologias da Web, podemos utilizar Bootstrap, React, Vue.js, qualquer tecnologia que execute no navegador e facilite a experiência de construção de telas (UI).
Acredito que com HTML, CSS e JS é mais fácil e rápido implementar melhores práticas de UI/UX do que com aplicações Desktop feitas com Java, C#, Visual Basic ou Delphi. Deixe sua opinião sobre isso nos comentários.
Acho incrível a possibilidade de reaproveitar os conhecimentos da web para desenvolver apps desktop.
Tem mais assuntos avançados sobre Electron que podemos trazer em um próximo post como: acesso a banco de dados, implementação na prática da comunicação RPC com ipcRender e ipcMain.
🚀 O que o time da Rocketseat fez?
- Com Electron, Mayk Brito implementou um screenboard que ajuda a dar suas aulas, sublinhando e circulando os textos para dar ênfase no que ele está ministrando.
- Implementou também um navegador flutuante que permite abrir uma janela inteira porém manter uma tela flutuante sem que ela saia da tela quando clica em outra janela. Pode ver o passo a passo da implementação aqui.
- Diego Fernandes está construindo o RocketRedis – Uma linda interface gráfica para acessar a base de dados do Redis. Pode ver a introdução sobre Electron e da ferramenta RocketRedis aqui.
🔗 Links
- Site oficial
- Documentação oficial
- FAQ
- Twitter Oficial
- Comunidade do Electron
- Community e Awesome Electron
- Electron build
- Ajude na tradução da documentação
- Apps construídos com Electron
Espero que tenha curtido! 💜
O aprendizado é contínuo e sempre haverá um próximo nível! 🚀