O melhor lugar para você aprender Javascript.

Descubra como é simples dominar o desenvolvimento Full Stack.

Como criar uma aplicação desktop, com React Js & Electron.

Como criar uma aplicação desktop, com React Js & Electron.

O Electron se destacou muito nos últimos tempos por oferecer a possibilidade de construirmos aplicações desktop utilizando tecnologias web.

As aplicações construídas com Electron podem funcionar Offline, acessar os Recursos Nativos do Sistema Operacional e até mesmo serem comercializadas nas lojas de aplicativos.

Hoje, vamos aprender a desenvolver uma aplicação desktop completa, com Electron e React Js.

Passo #1: Criação do projeto

Para começamos, criaremos um projeto através do Create React App.

Você pode criar seu projeto através do comando abaixo:

create-react-app app

Com o processo finalizado, você pode entrar na pasta app que você criou e rodar o comando:

yarn start

Após alguns segundos, uma nova janela no seu navegador se abrirá, semelhante a essa:

Tela padrão de projetos criados através do CRA.

Passo #2: Instalação das bibliotecas

Agora, instalaremos as bibliotecas que usaremos no nosso projeto.

Começaremos instalando o electron, o wait-on e a biblioteca concurrently como dependências de desenvolvimento.

Para isso, dentro da pasta do seu projeto, rode o comando abaixo:

yarn add electron wait-on concurrently -D

O concurrently é biblioteca que nos permitirá rodar vários comandos em sequência.

O wait-on nos permitirá esperar até que a página web da nossa aplicação esteja carregada, para assim, iniciarmos nosso app.

Também precisamos instalar a biblioteca electron-is-dev como uma dependência de produção.

Você pode fazer isso com o comando abaixo:

yarn add electron-is-dev

Instalaremos algumas dependências extras no Passo #6: Build do Projeto.


Passo #3: Configuração do Electron

Agora, precisamos fazer algumas configurações para que o Electron funcione corretamente.

Para isso, crie um arquivo electron.js na pasta public do seu projeto.

Esse arquivo precisa conter o seguinte conteúdo:

const electron = require('electron');

const { app } = electron;
const { BrowserWindow } = electron;

const path = require('path');
const isDev = require('electron-is-dev');

let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
    },
  });

  mainWindow.loadURL(
    isDev ? 'http://localhost:3000' : `file://${path.resolve(__dirname, '..', 'build', 'index.html')}`,
  );

  if (isDev) {
    mainWindow.webContents.openDevTools();
  }

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow();
  }
});

A função createWindow é responsável por criar uma janela no desktop, chamada BrowserWindow.

O BrowserWindow pode receber alguns parâmetros, como a largura (width) e a altura (height) em que a janela iniciará.

A opção nodeIntegration: true nos permite acesso à API nativa do electron, é importante que essa opção esteja ativada para a aplicação que vamos construir nesse post.

Agora, precisamos apenas criar o nosso script de desenvolvimento, para isso, adicione o script abaixo no campo scripts do seu package.json:

"dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron public/electron.js\""

Nesse momento, o electron está configurado e pronto para uso.

Sua estrutura de pastas deve estar semelhante a essa:

Para ver aplicação em modo de desenvolvimento, basta rodar o comando abaixo:

yarn dev

Ao rodar o comando acima, uma nova janela será aberta na sua máquina, semelhante a essa:


Passo #4: Configurações gerais da aplicação

Para criar o projeto desse post, você também precisará instalar o syled-components, para isso, basta rodar o comando abaixo:

yarn add styled-components

Em todos os projetos que eu crio utilizando Create React App, eu sempre gosto de fazer algumas configurações para deixar o projeto mais organizado.

Começaremos apagando alguns arquivos na pasta src, são eles:

  • App.css
  • App.test.js
  • index.css
  • logo.svg
  • serviceWorker.js

Apague também os seguintes arquivos da pasta public:

  • logo192.png
  • logo512.png
  • manifest.json
  • robots.txt

Precisamos também remover a importação desses arquivos.

O seu index.js da pasta src deve ficar da seguinte forma:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

E o seu arquivo App.js da pasta src deve ficar da seguinte forma:

import React from 'react';

function App() {
  return <div />;
}

export default App;

Nesse momento, sua estrutura deve estar mais ou menos assim:


Passo #5: Construção da aplicação

Agora, iremos dar início a construção da nossa aplicação.

Criaremos uma Todo List, semelhante ao Google Keep.

Para isso, crie uma pasta pages dentro da pasta src do seu projeto.

Dentro da pasta pages, crie uma nova pasta Main, e nela, crie dois novos arquivos, o index.js e o styles.js

No arquivo index.js, adicione o seguinte conteúdo:

import React, { useState, useEffect } from 'react';
import crypto from 'crypto';

import {
  Container,
  NewTodo,
  NewTodoInput,
  TodosList,
  Todo,
  TodoName,
  Button,
  AddTodoMessage,
} from './styles';

const { ipcRenderer } = window.require('electron');

export default function Main() {
  const [todos, setTodos] = useState([]);

  function handleNewTodo(e) {
    e.preventDefault();
    const newTodo = e.target.todo.value;

    if (!newTodo) return;

    const todo = {
      id: crypto.randomBytes(256),
      name: newTodo,
    };

    setTodos([...todos, todo]);

    const message = {
      title: 'Um novo todo foi criado',
      body: `O todo ${todo.name} foi criado`,
    };

    ipcRenderer.send('@notification/REQUEST', message);

    e.target.reset();
  }

  function handleRemoveTodo(todoId) {
    setTodos(todos.filter(todo => todo.id !== todoId));
  }

  function renderTodos() {
    return todos.map(todo => (
      <Todo key={todo.id}>
        <TodoName>{todo.name}</TodoName>

        <Button onClick={() => handleRemoveTodo(todo.id)}>Concluir</Button>
      </Todo>
    ));
  }

  function handleNotificationFailure(message) {
    console.log(message);
  }

  useEffect(() => {
    ipcRenderer.on('@notification/FAILURE', handleNotificationFailure);

    return () => {
      ipcRenderer.removeListener('@notification/FAILURE', handleNotificationFailure);
    };
  }, []);

  return (
    <Container>
      <NewTodo onSubmit={handleNewTodo}>
        <NewTodoInput name="todo" placeholder="Novo todo" />
        <Button>Adicionar</Button>
      </NewTodo>
      {todos.length > 0 ? (
        <TodosList>{renderTodos()}</TodosList>
      ) : (
        <AddTodoMessage>Adicione um novo todo :D</AddTodoMessage>
      )}
    </Container>
  );
}

Nesse arquivo, criamos uma página de criação de todos, que salva os todos em uma variável, através do state.

A função handleNewTodo é responsável por criar um novo todo na nossa lista atual, e em seguida, envia uma mensagem à API Nativa do Electron.

Essa mensagem contém um tipo (@notification/REQUEST) e os detalhes dessa mensagem, contidos na variável message.

Utilizamos o useEffect para definirmos um listener caso a criação da mensagem falhe, e nesse caso, chamamos a função handleNotificationFailure onde damos um console na mensagem de erro.

Temos também a função handleRemoveTodo que remove o todo quando o usuário clica em Concluir.

Agora, criaremos os estilos dessa página, utilizando o styled-components.

Para isso, no arquivo styles.js, adicione o seguinte conteúdo:

import styled from 'styled-components';

export const Container = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
`;

export const NewTodo = styled.form`
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin: 0 auto;
  padding: 50px 0;
  text-align: center;
`;

export const NewTodoInput = styled.input`
  height: 50px;
  padding: 0 15px;
  margin: 10px;
  border: 0.5px solid #333;
  border-radius: 4px;
  font-size: 14px;
`;

export const TodosList = styled.div`
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  list-style: none;
  margin-top: 50px;
`;

export const Todo = styled.div`
  width: 250px;
  max-width: 250px;
  border: 1px solid #eee;
  padding: 15px 20px;
  text-align: left;
  margin: 10px;
`;

export const TodoName = styled.h3`
  font-size: 16px;
  margin-bottom: 5px;
  color: #333;
`;

export const Button = styled.button`
  width: 100%;
  max-width: 250px;
  height: 50px;
  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.05);
  border: 0;
  border-radius: 4px;
  background: #009cde;
  color: #fff;
  font-weight: bold;
  cursor: pointer;
`;

export const AddTodoMessage = styled.h1`
  color: #333;
`;

Com os estilos da nossa página criados, criaremos agora o código de exibição de uma mensagem nativa quando um todo for criado.

Nesse código, faremos com que seja exibido uma notificação sempre que um novo todo for criado.

Notificação no MacOS

Para isso, começaremos a utilizar a API Nativa que o Electron nos oferece.

Começaremos criando uma pasta chamada app na raiz do projeto (fora da pasta src).

Dentro da pasta app, crie um arquivo index.js

No arquivo index.js, adicione o seguinte conteúdo:

const { ipcMain, Notification } = require('electron');

ipcMain.on('@notification/REQUEST', async (event, message) => {
  try {
    const { title, body } = message;

    const notification = new Notification({
      title,
      body,
    });

    notification.show();
  } catch (err) {
    event.sender.send('@notification/FAILURE', 'Houve um erro na criação da notificação');
  }
});

Essa parte do projeto segue bastante a ideia do websocket, onde enviamos uma mensagem sempre que quisermos nos comunicar com a API Nativa do Electron.

Nesse arquivo, nós estamos ouvindo a mensagem do tipo @notification/REQUEST, e esperamos receber o parâmetro message.

O event é enviado por padrão para todos os listeners, contendo as informações do evento e de quem o disparou.

Ao receber a mensagem do tipo que definimos (@notification/REQUEST), nós instanciamos um objeto do tipo Notification e utilizamos o método show para mostrar a mensagem em tela.

No catch dessa função, nós enviamos uma mensagem para sender informando que houve um erro.

Agora, precisamos importar esse arquivo dentro do arquivo public/electron.js que criamos no Passo #3: Configuração do Electron.

Para isso, adicione a seguinte linha logo após o require do electron:

require('../app');

Precisamos importar também a página Main que criamos no Passo #5: Construção da aplicação.

Para isso, deixe o conteúdo do arquivo App.js da pasta src da seguinte maneira:

import React from 'react';

import Main from './pages/Main';

function App() {
  return (
    <>
      <Main />
    </>
  );
}

export default App;

Nesse momento, sua estrutura de pastas deve estar semelhante a essa:

OBS.: É importante que você pare seu servidor o rode o comando yarn dev sempre que houver alguma alteração nos arquivos relacionados ao Electron.

Demonstração


Passo #6: Build do Projeto

Para fazer o build do projeto precisamos seguir alguns passos importantes.

Primeiramente, precisamos instalar as dependências necessárias para que o build do projeto seja feito corretamente.

Para instalar as bibliotecas, utilize o comando abaixo:

yarn add @rescripts/cli @rescripts/rescript-env electron-builder typescript -D

Precisaremos também fazer algumas alterações nas configurações do webpack do nosso projeto.

Para isso, utilizaremos a biblioteca rescripts para fazermos essas alterações sem precisar ejetar as configurações do Create React App.

O electron-builder é a biblioteca sugerida pelo próprio electron para o build de projetos.

Para começarmos a fazer essas configurações, crie um arquivo na raiz do seu projeto chamado .webpack.config.js e adicione o seguinte conteúdo:

module.exports = config => {
  config.target = "electron-renderer";
  return config;
};

Em seguida, precisamos fazer o rescripts entender essas configurações.

Para isso, também na raiz do seu projeto, crie um arquivo .rescriptsrc.js e adicione à ele o seguinte conteúdo:

module.exports = [require.resolve("./.webpack.config.js")];

Agora, precisamos definir algumas informações referentes ao criador do software.

Essas informações são muito importantes, principalmente se você quiser disponibilizar seu software nas lojas de Aplicativos, como a Windows Store ou a App Store.

Para isso, adicione o código abaixo no seu package.json:

  "author": {
    "name": "Seu nome",
    "email": "seu.email@email.com",
    "url": "https://seu-site.com"
  },
  "homepage": "./",
  "main": "public/electron.js",
  "build": {
    "appId": "com.seu-site.seu-app",
    "productName": "MyApp",
    "copyright": "Copyright © 2019 Seu Nome",
    "mac": {
      "category": "public.app-category.utilities"
    },
    "files": [
      "build/**/*",
      "node_modules/**/*"
    ],
    "directories": {
      "buildResources": "assets"
    }
  },

Agora, precisamos alterar os scripts da nossa aplicação para que eles utilizem o rescripts em vez do react-scripts.

Além disso, criaremos também os scripts de build da nossa aplicação, para isso, os scripts do seu package.json devem ficar da seguinte maneira:

  "scripts": {
    "start": "rescripts start",
    "build": "rescripts build",
    "test": "rescripts test",
    "eject": "react-scripts eject",
    "dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\"",
    "postinstall": "electron-builder install-app-deps",
    "preelectron-build": "yarn build",
    "electron-build": "electron-builder -mwl"
  },

Nós criamos 3 scripts novos, são eles:

  • postinstall: Esse script irá instalar as dependências do electron builder sempre que rodarmos o comando install. Esse script é uma recomendação do electron-builder para que as dependências dele estejam sempre atualizadas.
  • electron-build: Esse script fará o build do nosso projeto. Nele, podemos passar algumas flags para informamos para quais plataformas o projeto deve ser buildado. Nesse caso, passamos a flag -mwl para informamos que o build deve ser para o MacOS (m), Windows (w) e Linux (l).
  • preelectron-build: Esse script irá buildar o nosso projeto CRA e gerar os arquivos estáticos. Como criamos esse script com o pre antes do electron-build, ele sempre irá executar antes do electron-build.

Com tudo isso configurado, rode o comando abaixo:

yarn electron-build

Quando rodarmos esse comando, o electron-builder irá gerar os arquivos executáveis do nosso projeto.

O processo de build pode demorar alguns minutos, principalmente se você está gerando o build para várias plataformas.

Os arquivos gerados no processo de build podem ser encontrados na pasta dist.

Ao finalizar e buildar sua aplicação, sua estrutura de pastas deve estar semelhante a essa:

Concluindo

Nesse post pudemos ver um pouco do poder do Electron. Aprendemos também como realizar o build de projetos criados com Electron e React Js.

Espero que esse post tenha te ajudado de alguma forma 🙂

Ver código no Github ↗

Forte Abraço,

Carlos Levir

Tags: | |

Entre para nossa lista e receba conteúdos exclusivos e com prioridade

Sobre o Autor

Carlos Levir
Carlos Levir

Desenvolvedor fascinado por aprendizado e pelo ecossistema Javascript. Sempre em busca do próximo nível. Atualmente atua como Engenheiro de Software na Rocketseat.

4 Comentários

  1. Levir, pensando em desenvolver uma aplicação com reactjs e node, rodando localmente pra um cliente, teria como fazer alguma aplicação com o electron, onde a gente colocasse o caminho do frontend e backend com um botão de “run” ao lado pra startar as aplicações? Até porque o cliente não vai abrir o projeto e rodar “yarn start”

    • Faaala Marcelo, isso não é necessário, quando o projeto é buildado, já é gerada uma versão de produção utilizável, ou seja, a aplicação já vai com “yarn start” ativo digamos assim.

      Grande Abraço 😀

  2. levir, uso React diariamente no meu trabalho e estava pensando em aprender a usalo com electron, no caso eu já fiz uma aplicação com electron porem utilizando angular e nesta aplicação eu estava utilizando o sqlite como banco imbutido. Caso saiba como poderia escrever um artigo sobre como implementar o uso do sqlite com o React e electron. ?


Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *