Um tempo atrás, comecei a trabalhar em um projeto paralelo chamado taggr , um aplicativo de exploração de fotos interativo totalmente off-line. O desenvolvimento do taggr exigiu que eu navegasse do nível mais baixo de complexidade do aplicativo, experimentando várias abordagens arquitetônicas e explorando as limitações de cada uma.

Neste artigo, discutiremos as compensações de diferentes abordagens arquitetônicas para construir aplicativos de desktop com Electron . Analisaremos as deficiências de cada um e apresentaremos uma arquitetura que visa resolvê-los.

O projeto apresentado neste artigo é o resultado de um esforço contínuo para encontrar uma abordagem que me permita, um desenvolvedor solo, para gerenciar a complexidade do aplicativo e atender aos requisitos de desempenho, aproveitando o conjunto de ferramentas da Web padrão. Vamos mergulhar!

Observação: você pode acompanhar este repositório GitHub .

Introdução ao Electron.js

Nos últimos anos, o uso de JavaScript aumentou drasticamente no domínio do navegador, em grande parte com a ajuda de bibliotecas e estruturas como React , Vue e Angular. Da mesma forma, vimos o JavaScript crescer além do navegador com Node.js, Deno e React Native.

Electron.js é uma dessas estruturas. Desde seu lançamento em 2013, Electron cresceu e se tornou uma das estruturas mais usadas para a construção de aplicativos de desktop multiplataforma. VS Code, Slack, Twitch e muitos outros aplicativos de desktop populares são desenvolvidos usando o Electron.

Como o Electron funciona

O Electron incorpora o Chromium e o Node.js em seu binário, permitindo que os desenvolvedores da web escrever aplicativos de desktop sem escrever código nativo. Electron implementa um modelo de multiprocessos composto pelos processos principal e renderizador, que é semelhante ao navegador Chromium.

A janela de cada aplicativo é um processo de renderização, que isola a execução do código no nível da janela. O processo principal é responsável pelo gerenciamento do ciclo de vida do aplicativo, gerenciamento de janela ou processo de renderização e APIs nativas, como menus do sistema, notificações e ícones de bandeja.

Cada aplicativo é composto por um processo principal e um número variável de processos de renderização. Processos de renderização podem ser usados ​​para execução de código JavaScript e podem ser ocultados sem uma IU.

Nota: Electron não é a única opção para a construção de plataforma cruzada aplicativos de desktop. Outras alternativas oferecem menos consumo de recursos e executáveis ​​mais leves, mas nenhuma compartilha da comunidade, dos recursos de aprendizagem ou da adoção generalizada do Electron.

Introdução ao Electron

Se você ainda não está familiarizado com o Electron, é muito fácil começar, especialmente porque o conhecimento de Node.js e JavaScript é transferível.

O Electron fornece abstrações e uma linguagem familiar, reduzindo o tempo de lançamento no mercado e os custos de desenvolvimento. Essencialmente, o que o Electron faz para o desenvolvimento de aplicativos de desktop é semelhante ao que o React Native faz para o desenvolvimento móvel.

O Electron também gerencia a construção e implantação de atualizações de aplicativos, tornando mais fácil manter aplicativos de plataforma cruzada em uma versão sincronizada. Você pode conseguir isso com atualizações automáticas e carregando ativos remotos em tempo de execução.

No entanto, os benefícios do Electron têm suas compensações. O Electron é fornecido com os ambientes Chromium e Node.js, fazendo com que um aplicativo Electron consuma mais recursos do que seus equivalentes nativamente implementados. Como resultado, há opiniões contraditórias sobre a viabilidade do Electron.

Além disso, aplicativos complexos do Electron apresentam desafios de desempenho e experiência do desenvolvedor relacionados à arquitetura subjacente. Vamos considerar essas compensações em profundidade, analisando três exemplos de aplicativos diferentes.

Compensações específicas do aplicativo

Vamos examinar as arquiteturas de alto nível de três aplicativos fictícios com complexidade variável. Lembre-se de que nossa análise de aplicativos não pretende ser exaustiva, mas sim testar os aplicativos em potencial que você pode construir com o Electron.

Aplicativo de baixa complexidade

Vamos começar com um aplicativo de baixa complexidade. Para nosso exemplo, vamos considerar o empacotamento de uma página da web como um aplicativo de desktop. Os exemplos podem incluir aplicativos de mensagens instantâneas, painéis de análise de dados e aplicativos de streaming online.

Muitas empresas fornecem versões de desktop de seus aplicativos baseados na web de sucesso, tornando o nosso um caso de uso comum. Usaremos Electron para executar o aplicativo no Chromium, eliminando polyfills desnecessários e fornecendo um UI unificada em vez de um cenário de navegador heterogêneo.

Recursos principais do aplicativo de baixa complexidade:

O código será compartilhado entre o aplicativo da web e o aplicativo de desktop O ciclo de atualização será compartilhado entre o aplicativo da web e aplicativo de desktop O aplicativo de desktop carregará os mesmos ativos que o aplicativo da web e os renderizará dentro do Chromium. O back-end (se aplicável) permanecerá inalterado O back-end será acessado da mesma forma tanto no desktop quanto no aplicativo da web. WebWorkers e WebGL , funcionará em várias plataformas sem alterações. Usaremos ferramentas de desenvolvimento da web padrão

Arquitetura de alto nível para baixa conformidade aplicativo exity

Como arquitetura de exemplo, usaremos um aplicativo de desktop para o aplicativo da web Telegram chat . O Electron atuará como um wrapper para o aplicativo da web existente sem exigir nenhuma alteração no back-end.

Configurar o Electron é fácil para este tipo de aplicativo! Não há mudanças necessárias no nível da base de código do aplicativo da web.

Aplicativo de complexidade média

Um aplicativo de streaming de música como o Spotify, que oferece suporte a streaming offline usando um cache local, é um típico exemplo de um aplicativo com um nível médio de complexidade. O aplicativo de desktop pode usar o Electron para construir uma camada de cache local.

Semelhante aos aplicativos de baixa complexidade, um aplicativo de média complexidade também pode complementar um aplicativo da web. A principal diferença é a capacidade de fornecer suporte offline. Portanto, esses aplicativos são conceitualmente relacionados a aplicativos da web progressivos ( PWAs ) com suporte offline.

Principais recursos:

A maior parte do código pode ser compartilhada entre aplicativos da web e de desktop (ou seja, em uma camada de IU). O aplicativo de desktop terá uma implementação de cache local que interceptará as solicitações de back-end, preencherá o cache e fornecerá resultados em cache quando offline. Precisamos usar APIs Electron de alto nível para verificar se o aplicativo de desktop está online ou offline. O ciclo de atualização não é necessariamente compartilhado entre a web e o desktop. O desktop carregará a IU a partir de arquivos estáticos usando sua IU offline e criará uma camada de solicitação personalizada com o cache. Você pode aproveitar as ferramentas de desenvolvimento da web padrão com exceção do módulo de solicitação personalizado, que deve ser desenvolvido e ajustado para Electron

High arquitetura de nível

Vamos imaginar que nosso aplicativo de streaming reproduz a música do dia. Se não houver conexão com a Internet, ele servirá a música em cache disponível.

Conforme descrito no esquema acima, a IU será veiculada a partir de ativos locais em vez de um CDN, e a camada de solicitação deve ser personalizada para suportar o armazenamento em cache. Embora o exemplo seja relativamente simples, os requisitos de compartilhamento de código e armazenamento em cache acabarão aumentando em complexidade, exigindo código Electron personalizado.

Aplicativo de alta complexidade

Para o nível mais alto de complexidade, vamos dar uma olhada em um aplicativo de processamento de imagens em lote como sharp . O aplicativo deve ser capaz de processar milhares de imagens e funcionar totalmente off-line.

Os aplicativos off-line são significativamente diferentes dos dois exemplos anteriores. Especificamente, as cargas de trabalho de back-end típicas, como processamento de imagem, serão executadas dentro do Electron criando um aplicativo offline.

Principais recursos:

A maior parte do nosso código será personalizado para o aplicativo de desktop O aplicativo terá seu próprio ciclo de lançamento O backend será executado de dentro do Electron (ou seja, de um processo de renderização). Ferramentas de desenvolvimento web padrão podem ser usadas, mas depende da arquitetura definida. Podemos precisar usar módulos nativos como acesso a banco de dados, processamento de imagem ou máquina para aprender o acesso à API Electron de nível inferior pode ser necessário em vários processos, especialmente para comunicações entre processos (IPC)

Arquitetura de alto nível

Para a proposta de arquitetura, vamos considerar o aplicativo de processamento de imagem offline descrito acima.

O esquema estrutura o app seguindo a documentação do Electron, o que traz algumas limitações. Por um lado, há degradação perceptível de desempenho ao executar as operações de longa duração e uso intensivo da CPU em um processo de renderização oculto.

Observe que você nunca deve executar as operações no processo principal . Fazer isso pode bloquear o processo principal, fazendo com que seu aplicativo congele ou trave.

Além disso, o acoplamento das camadas de lógica de negócios e transporte às APIs Electron limita as opções de reutilização de ferramentas de desenvolvimento web padrão. As comunicações entre os processos principais e os processos de renderização usam IPC, que requer uma viagem de ida e volta do processo principal ao se comunicar entre dois processos de renderização.

Se seu aplicativo se enquadra nas categorias de complexidade baixa ou média, parabéns! Muitas das dores de cabeça que surgem em aplicativos off-line não se aplicam a você. No entanto, se os requisitos do seu aplicativo se enquadram na faixa de alta complexidade, ainda há esperança!

Proposta de arquitetura avançada

Quando consideramos problemas em aplicativos offline, como degradação de desempenho, comunicação de ida e volta entre renderização processos e a experiência geral do desenvolvedor, precisamos de uma arquitetura especializada:

A arquitetura proposta é construída sobre os seguintes pilares:

O código compartilhado entre o frontend e o backend é extraído em um único módulo O código da IU é independente do Electron, então as melhores práticas de desenvolvimento da web podem ser aplicadas. A IU e o roteamento da página são construídos usando componentes controlados e um estado de aplicativo centralizado. O back-end é executado a partir de um processo Node.js separado. Os módulos front-end e back-end comunicar-se por meio da passagem de mensagens

Vamos examinar cada um dos módulos detalhadamente!

Nota: partes da pilha são escolhidas puramente devido à preferência pessoal e são intercambiáveis. Por exemplo, você pode trocar os pacotes TypeScript para JavaScript, React para Vue, Redux para MobX ou npm para compartilhamento de código em vez de espaços de trabalho Yarn. Contanto que os pilares mencionados acima sejam respeitados, você tem liberdade de escolha na pilha.

Módulo compartilhado

O módulo compartilhado é responsável pelo código e tipos compartilhados pelos módulos front-end e back-end. Ele permite que você desenvolva ambos os módulos como entidades separadas enquanto ainda compartilha o código e os tipos relevantes para o domínio.

O compartilhamento de código é obtido usando Yarn workspaces , uma alternativa simples para publicar o módulo como um pacote npm , liberando e controlando-o.

Principais recursos:

Base de código de typescript Tipagens para comunicação de passagem de mensagens: contém cargas úteis e manipuladores de mensagens necessários nos modelos e entidades de domínio de front-end e back-end Utilitários compartilhados como registro e relatório de eventos

Módulo de front-end

O módulo de front-end é responsável por todas as coisas da interface do usuário. Ele contém os componentes e animações de nosso aplicativo, mas não a lógica de negócios. Na produção, o Electron o serve a partir de arquivos estáticos gerados.

Principais recursos:

Base de código de typescript com acesso ao módulo compartilhado Usa React para construir a interface do usuário com Create React App como um template Usa Redux como o gerenciador de estado, que define deterministicamente o estado de renderização da IU Comunicação com o back-end por meio da passagem de mensagens: o front-end expõe um manipulador de mensagens que escuta as mensagens do back-end e modifica o armazenamento Redux de acordo com o Desenvolvimento de componentes em isolamento usando Storybook

Backend com Electron módulo

O módulo de back-end contém a base de código de back-end e o código de configuração do Electron. A lógica de negócios e as operações de longa duração, como o processamento de imagem, serão executadas em um processo Node.js separado para que a IU não sofra com a degradação do desempenho.

Principais recursos:

Base de código de typescript , com acesso ao módulo compartilhado. O back-end é executado como um processo Node.js bifurcado, o que melhora o desempenho para tarefas demoradas e computacionalmente caras. Acesso às dependências nativas. Executa uma etapa de pré-construção que combina as dependências nativas com a versão Electron. Scripts de configuração e empacotamento de elétrons

Camada de comunicação

O front-end e o back-end se comunicam usando a passagem de mensagens entre processos com node-ipc A passagem de mensagens permite a comunicação assíncrona e baseada em eventos.

A comunicação assíncrona é mais adequada para operações de curta duração. O front-end pode esperar até que o back-end processe a mensagem para obter o resultado imediatamente.

A comunicação baseada em eventos é mais adequada para operações de longa duração, como processamento em lote. Conforme a tarefa é processada no back-end, ela envia eventos que modificarão o estado do aplicativo do front-end no Redux. O back-end pode concluir tarefas de longa execução de forma assíncrona e atualizar periodicamente o progresso exibido pela IU.

Recursos principais:

node-ipc como a biblioteca de comunicação Cargas e manipuladores de mensagens totalmente digitadas no módulo compartilhado Suporte à comunicação assíncrona e baseada em mensagens

Conclusão

O Electron é uma ótima opção para construir aplicativos de desktop multiplataforma usando diferentes tecnologias da web. Embora o Electron seja fácil de usar em aplicativos de baixa complexidade, as limitações de desempenho e experiência do desenvolvedor surgirão à medida que a complexidade aumenta.

A arquitetura proposta visa fornecer uma base conceitual sólida para aplicativos de alta complexidade. Claro, pode ser necessário estendê-lo dependendo do caso de uso, mas descobri que serve como uma boa base para muitos tipos de aplicativos.