Artigo original: Things I Wish I Knew Before Working with Electron.js

Neste artigo, vou compartilhar como você pode evitar alguns dos erros que eu cometi enquanto estudava Electron.js. Tomara que isso ajude você!

Observação: isso não vai ser um tutorial, vai ser uma discussão sobre os meus aprendizados pessoais.

Há alguns meses, eu decidi focar mais em construir meu produto secundário, taggr. Eu estava animado em trabalhar nele por conta da quantidade de fotos que eu tenho no computador.

Para as pessoas que guardam um backup das suas fotos, essas coleções tendem a ficar tão grandes e complexas que cuidar delas acaba exigindo um trabalho de tempo integral. Uma mistura de pastas e subpastas contendo backups de aplicações de mensagens, imagens em alta resolução da sua viagem para o Guarujá ou o casamento do seu tio.

Manter essas coleções organizadas é entediante (acredite em mim, eu tentei por anos). Também é difícil descobrir as fotos que você mais gosta, escondidas em algum lugar entre as subpastas.

Então, o taggr é uma aplicação desktop que resolve esse problema. Ele permite que as pessoas redescubram suas memórias ao mesmo tempo em que preserva sua privacidade.

Estou construindo o taggr como um programa desktop multiplataforma. Aqui, eu vou compartilhar algumas das coisas que aprendi sobre desenvolvimento multiplataforma com Electron.js que eu gostaria que eu soubesse desde o começo.

Contexto

Antes de contar o que aprendi nessa jornada com o Electron, eu gostaria de explicar um pouco mais do meu contexto e dos requisitos do taggr.

Todo desenvolvedor vem de um contexto diferente, assim como os requisitos das aplicações que eles desenvolvem.

Contextualizar as escolhas que eu fiz nesse projeto pode ajudar futuros desenvolvedores a escolherem as ferramentas certas se baseando nas suas necessidades e na sua experiência (ao invés de seguir a hype – GitHub, eu tô falando com você).

train
Desenvolvimento em JavaScript, em resumo. Fonte: giphy.

Como eu mencionei antes, eu sempre pensei no taggr como uma aplicação multiplataforma. Por uma questão de privacidade o programa deveria fazer todas as tarefas de pré-processamento e aprendizado de máquina no computador local, sem enviar dados para nenhum serviço externo.

Como estava trabalhando sozinho, eu gostaria de poder escrever minha aplicação uma vez só, e conseguir usar a aplicação em diferentes sistemas sem perder a minha sanidade.

Eu sou um desenvolvedor de front-end apaixonado pela web e por JavaScript. Eu trabalhei anteriormente com Java e C#, mas eu adoro a flexibilidade que a web proporciona e o ecossistema vibrante que ela tem.

Já tendo visto por mim mesmo a dor que é usar a ferramenta Eclipse RCP para criar aplicações para o lado do client, eu sabia que não queria trabalhar com essa tecnologia de novo.

Resumindo, meus requisitos de tecnologias para o taggr eram mais ou menos as seguintes:

  • ele deveria ter suporte multiplataforma – de preferência esse suporte deveria vir pelo framework usado.
  • ele deveria me permitir escrever o código uma vez só e, então, fazer ajustes pra cada plataforma se necessário.
  • ele deveria me dar acesso a ferramentas de aprendizado de máquina, independente do ambiente do host, sem depender de ter algum runtime específico instalado. O programa deveria ser fácil de instalar.
  • se isso fosse viável, ele deveria usar tecnologias para a web. Seria maravilhoso poder aproveitar meu conhecimento existente.

Como você pode ver, os requisitos não são: eu deveria usar React com Redux, observáveis e WebSockets. Esses são detalhes de implementação de baixo nível, e esses detalhes deveriam ser decididos quando e SE a necessidade aparecer.

Escolha a ferramenta certa para o trabalho ao invés de escolher uma stack desde o começo, sem considerar o problema em questão.

Então, depois de algumas pesquisas no Google, eu decidi experimentar o Electron. Eu nunca tinha usado esse framework antes, mas eu sabia que muitas empresas estavam usando ele com sucesso em produtos como Atom, VS Code, Discord, Signal, Slack e outros.

O Electron.js era uma opção atrativa para o projeto por ser de código aberto e ter uma compatibilidade com os ecossistemas de JS e Node (Electron usa Chromium e Node internamente).

Eu não vou entrar em muitos detalhes com relação ao resto da stack já que eu mudei várias vezes algumas partes críticas do projeto conforme precisei (como persistência e camadas de apresentação). Isso sairia do escopo desse texto.

Porém, eu gostaria de mencionar o Tensorflow.js, que permite executar, treinar e implantar modelos de aprendizado de máquina diretamente do navegador (com WebGL) e Node (com chamadas externas para código C), sem instalar nenhum runtime específico pra aprendizado de máquina no host.

Voltando ao Electron – agora, a diversão começou.

Chega de falar sobre o contexto. Vamos mergulhar nos aprendizados.

1. Comece pequeno (e devagar)

Esse não é um conceito novo, mas vale a pena relembrar de tempos em tempos. Só por que existem milhares de templates de projetos ótimos disponíveis para o Electron, isso não significa que você deveria pegar um deles logo de começo.

Espera. Como assim?

Devagar é mais tranquilo, e tranquilo é mais rápido — ditado da marinha dos Estados Unidos

Com a conveniência vem a complexidade

Enquanto os templates para projetos incluem muitas integrações úteis (Webpack, Babel, Vue, React, Angular, Express, Jest, Redux), eles também têm os seus problemas.

Como um iniciante em Electron, eu decidi começar com um template enxuto que incluía o básico para "criar, publicar e instalar uma aplicação Electron" sem a quinquilharia extra. No começo, até mesmo sem Webpack.

Eu recomendo começar com algo similar ao electron-forge para ver algo rodando rápido. Você pode criar o seu grafo de dependências e estrutura em cima disso para aprender a lidar com Electron.

Quando os problemas aparecerem (e eles vão), você estará com um template que você mesmo criou em vez de usar um que você escolheu lá no começo, com seus mais de 30 scripts do npm e mais de 180 dependências.

Dito isso, quando você estiver confortável com as bases do Electron, sinta-se livre para incrementar o projeto com Webpack/React/Redux/OProximoFrameworkBoladão. Eu fiz isso de maneira incremental e só quando necessário. Não adicione um banco da dados de tempo real ao seu programa de listas de tarefas só porque você leu um artigo legal sobre esse assunto em algum lugar.

2. Estruture seu programa de maneira consciente

Essa aqui levou um pouco mais de tempo para acertar do que eu gostaria de admitir.

No começo, pode parece tentador misturar o código pra UI e para o back-end (acesso de arquivos, otimizações de operações de CPU), mas as coisas se complicam rápido. Conforme minha aplicação foi crescendo em termos de recursos, tamanho e complexidade, manter um "espaguete de código" que mistura UI e back-end foi se tornando mais complicado e propenso a erros. Além disso, esse acoplamento tornou mais difícil testar cada parte separadamente.

Para criar uma aplicação para a web que tenha mais que uma página (banco de dados, acesso a arquivos, operações intensivas de CPU...), eu recomendo separar o programa em módulos e reduzir o acoplamento. Testes unitários ficam muito mais fáceis e existe um caminho claro na direção de testes de integração entre os módulos. Para o taggr, eu me baseei levemente na estrutura proposta aqui (texto indisponível).

Além disso, existem questões de desempenho. Nesse assunto, os requisitos e expectativas de usuários variam bastante dependendo da aplicação que você está construindo. Nunca é uma boa ideia bloquear a thread principal ou a thread de renderização com funções pesadas.

3. Desenhe o programa com a estrutura de threads em mente

Eu não vou entrar em muitos detalhes aqui – eu vou simplesmente reforçar o que já está maravilhosamente explicado na documentação oficial (em inglês).

No caso específico do taggr, existem operações de CPU, GPU e de I/O que demoram um longo tempo. Executar essas operações na thread principal ou na thread de renderização faz o FPS cair pra abaixo de 60, deixando a UI lenta.

O Electron oferece várias alternativas para tirar essas operações das threads principal e de renderização, por exemplo WebWorkers, Node Worker Threads ou BrowserWindow. Cada uma tem as suas vantagens e seus poréns e é o seu caso de uso que vai determinar qual delas se encaixa melhor na sua situação.

Independente de qual alternativa você escolha pra delegar o trabalho das threads principal e de renderização (quando necessário), considere como será a interface de comunicação. Eu levei algum tempo para chegar em uma solução com a qual eu estivesse satisfeito, porque isso impacta muito em como a aplicação é estruturada e funciona. Eu acho que experimentar com diversos alternativas facilitou muito o processo de escolha.

Por exemplo, se você acha que a interface de mensageria do WebWorkers talvez não seja a mais fácil de debugar, tente usar o comlink.

sponge
O Bob Esponja sabe do que tá falando. Fonte: giphy

4. Teste ❌, teste ❌ e teste ✔️

Você já escutou essa certo? Eu queria adicionar isso por último por causa de alguns "problemas" anedóticos que eu tive recentemente. Fortemente ligados ao primeiro e segundo ponto, criar o seu próprio template de projeto e cometer erros mais cedo vai poupar seu tempo precioso mais pra frente no desenvolvimento.

Se você seguiu minhas recomendações para separar o programa em UI e back-end em módulos com uma interface clara entre as duas coisas, construir um conjunto de testes unitários e de integração deve ser fácil. Conforme a aplicação amadurece, você pode querer adicionar suporte para testes de ponta a ponta também.

Extração de localização por GPS

Há dois dias, enquanto implementava a extração de localização por GPS para o taggr, assim que os testes unitários ficaram verdes e que o recurso funcionou em desenvolvimento (com o Webpack), eu resolvi tentar rodar no ambiente de produção.

Enquanto o recurso funcionava muito bem em desenvolvimento, ele falhou miseravelmente em produção. Os metadados EXIF das imagens foram lidos como binário e processados por uma biblioteca externa. A informação em binário era lida corretamente nos dois ambientes (eu chequei com diff – texto em inglês), mas a biblioteca externa falhou em analisar os dados no build de produção. Como assim?

Solução: eu descobri que a codificação de caracteres usada pelo ambiente de produção e pelo Webpack não eram as mesmas. Isso fez com que os metadados fossem analisados como UTF-8 em desenvolvimento, mas não em produção. O problema foi corrigido definindo a codificação correta no cabeçalho dos arquivos HTML que o Electron carrega.

Imagens esquisitas

Manipulando e trabalhando com imagens, você pode pensar que, se um JPEG "simplesmente funciona" no seu computador, ele deve ser um JPEG válido. Errado.

Enquanto eu trabalhava com a biblioteca de processamento de imagens do Node chamada sharp, redimensionar algumas imagens JPEG simplesmente quebrava o programa. Depois de debugar o problema, a causa eram imagens JPEG mal formadas geradas por firmware da Samsung.

Solução: melhorar a detecção de erros do programa (por exemplo, com blocos try-catch), melhorar o módulo de análise de JPEG e suspeitar de tudo.

Resumo

Os ecossistemas de Node e Javascript estão em alta, com muitas ferramentas poderosas sendo criadas e compartilhadas todo dia.

A quantidade de opções faz com que seja difícil escolher um caminho claro para começar a escrever a sua aplicação com o Electron. Independentemente da sua escolha de framework, eu recomendaria concentrar-se no seguinte:

  1. Comece com algo simples e adicione complexidade aos poucos.
  2. Estruture seu programa com cuidado, mantendo back-end e interface de usuário (a UI) separados.
  3. Planeje com o modelo de threads em mente, mesmo se estiver criando um programa pequeno.
  4. Teste, teste e teste de novo, para descobrir cedo a maioria dos erros e diminuir as dores de cabeça.

Obrigado por continuar comigo até o final!

O taggr é uma aplicação para desktop multiplataforma que permite aos usuários redescobrir suas memórias digitais e ao mesmo tempo manter sua privacidade. A versão alpha aberta será lançada para Linux, Windows e Mac OS. Então, fique ligado no Twitter e no Instagram, onde eu posto updates sobre o desenvolvimento, próximos recursos e novidades.