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).
Representação do envio de mensagens entre processos
📁 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
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:
Resultado Final - App Hi Electron
👍 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
Espero que tenha curtido! 💜
O aprendizado é contínuo e sempre haverá um próximo nível! 🚀