Speed Run de MicroFrontends com Single-SPA (any%)
Tags: webdev, frontend, javascript, angular
Published: 2023-11-24 20:42:07

Recentemente completei 1 ano trabalhando diretamente com MicroFrontends e consegui sentir os "prazeres" e as "dores" de um MicroFrontend, com o objetivo de me aprofundar mais tanto no [SingleSPA](https://single-spa.js.org/) quanto em MicroFrontends resolvi sintetizar o conhecimento nessa speedrun, ao final teremos construído dois MicroFrontends que tem como objetivo irmos migrando funcionalidades de uma aplicação legada para uma tecnologia mais nova sem que seja necessário reescrevermos toda a aplicação.

Caso você não queira acompanhar o passo-a-passo, ao final eu disponibilizo a aplicação pelo Stackblitz e também a URL do repositório.

> **DISCLAIMER:** Não sou nenhum especialista no Single-SPA e muito menos em MicroFrontends, no entanto, acredito que consigo passar a ideia geral, sugestões e correções/críticas são sempre bem-vindas.

### Table of Contents

1. [O que são MicroFrontends](#oque-sao-microfrontends)
2. [Como funciona](#magica-bruxaria-apenas-javascript)
3. [Tipos de MicroFrontends](#tipos-de-microfrontends)
4. [Setup inicial](#setup-inicial)
5. [Adicionando primeira aplicação](#adicionando-nossa-primeira-aplicação)
6. [Adicionando mais um MFE](#adicionando-mais-um-mfe)
7. [Conclusão](#observacoes-e-conclusoes-finais)

### O que são MicroFrontends?

MicroFrontends, ou MFE para os íntimos, á grosso modo podem ser resumidos a pequenas partes da aplicação principal sendo desenvolvidos em frameworks/libs diferentes e se comunicando como se fossem um só, isso é, com MicroFrontends podemos ter a nossa aplicação dividida em partes menores e essas partes menores podem ser Desenvolvidas em React e uma outra parte em Angular e uma terceira parte em Vue, e o Single-SPA será responsável por "montá-las" assim como "desmontá-las" quando elas já não forem mais necessárias.

Tudo isso de forma desacoplada onde cada aplicação pode ter seus próprios arquivos de CSS, Libs e se comunicarem normalmente, dessa forma dependendo do tamanho da aplicação você pode ter devs trabalhando em diferentes partes do projeto com a tecnologia que tem mais conhecimento.

Não pretendo entrar muito á fundo sobre como funciona por debaixo dos panos por não me sentir confortável o suficiente para isso ~~e é uma speedrun, o speedrunner não para a run para explicar porquê funciona, mas como faz.~~

### Mágica? Bruxaria? Apenas JavaScript...

Ao criarmos uma aplicação com o nosso framework favorito nós sabemos, ou pelo menos espero que sim, que o arquivo gerado ao final do build é muito diferente do que estamos escrevendo, cheio de minificações e métodos/variáveis ofuscados mas além disso, os métodos que utilizamos se tornam bem diferentes e etc. No entanto, ainda que seja diferente aquela sopa de letrinhas ainda é a nossa aplicação porém agora sem a camada de abstração que o framework nos proporciona e é a partir desse arquivo que o Single-SPA atua, pelo bem da didática e da simplicidade vou utilizar do cenário mais simples possível para que seja possível entender a ideia geral sobre o Single-SPA, após ter entendido isso você será capaz de criar seus próprios MFEs.

Ao final do processo de build normalmente temos um arquivo chamado de `main.js`, e esse arquivo main.js é servido ao usuário ao entrar na nossa aplicação, e ele é o responsável por renderizar nossos componentes, realizar o roteamento e tudo mais.

E é através desse arquivo que o Single-SPA monta a nossa aplicação, no entanto, nós precisamos "dizer" a ele em que momentos isso deve acontecer e quando os requisitos não forem atingidos ele irá "desmontar" a aplicação novamente, para deixamos isso mais claro abaixo a função que registra um MFE e suas condições de renderização, veremos com mais calma isso mais pra frente.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ajdpl74tljfdn6t1ikqp.png)

A função `registerApplication` é responsável por registrar a aplicação e ela recebe como parâmetro o nome da nossa aplicação (precisa ser único, mais pra frente veremos o motivo), em seguida uma função que será responsável por localizar o nosso arquivo gerado ao final do build e após isso podemos passar três tipos de dados diferentes:

**1.** uma simples `string` onde caso a rota seja a que inserimos a aplicação será montada.
**2.** Uma array de `strings` onde podemos realizar o processo de montagem da aplicação em rotas diferentes.
**3.** uma função e por ser uma função você pode realizar qualquer verificação que você quiser, o importante é que ao final retorne booleano.
**4.** Podemos também passar propriedades para dentro dessas aplicações, a forma como serão capturados dentro da aplicação muda para cada framework, em [Angular](https://single-spa.js.org/docs/ecosystem-angular#custom-props), por exemplo:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc344atsj7e8qn31xi94.png)

De forma bem resumida e talvez até um pouco "grosseira" essa é a ideia principal sobre como funciona o processo de montagem do Single-SPA, no entanto, eu quero gastar uns 2 centavos explicando de forma rápida como esses arquivos são servidos, especialmente quando em Desenvolvimento já que estamos constantemente alterando arquivos e esses por sua vez geram um novo "main.js", afinal ainda são aplicações inteiras e elas ainda precisam passar pelo seu respectivo processo de build para esses arquivos serem gerados e normalmente ao subirmos uma aplicação elas estão em portas diferentes, a questão aqui é que independente da porta elas ainda estão no mesmo "localhost", então ao contrário do que vimos na imagem acima onde está sendo apontado para uma pasta `src`, podemos apontar diretamente pro `localhost:XXXX/main.js`, assim como para outros domínios também, e isso irá funcionar normalmente, no entanto, quando em produção o ideal é que seja criado uma pasta `dist` onde fique cada um desses arquivos talvez até fora da pasta de cada aplicação, já que não haverão mais alterações nesses arquivos após o build.


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7m03o3bskh8woexpqum9.png)


### Tipos de MicroFrontends

Apesar de eu estar sempre me referindo a MicroFrontends como "aplicações" talvez nem todos precisem ser uma aplicação inteira, isso porquê podemos ter MicroFrontends que se tratem apenas de um único componente, ou talvez apenas serem utilitários para comunicação entre MFEs e etc, não vou entrar no mérito sobre quando se deve usar cada caso, no entanto, a depender do tipo de MFE escolhido o processo de build/configuração muda, abaixo os exemplos de MicroFrontends e suas comparações diretamente da documentação.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0il784snxuhnturf8r1.png)

Em resumo, nós temos os seguintes tipos: 

**1.** Applications ou Aplicações são as que após o processo de montagem passam normalmente pelo seu processo de roteamento e e o framework escolhido irá renderizar os respectivos componentes e etc.
**2.** Parcels ou parcelas/pedaços se trata de apenas um componente que pode ser inicializado em alguma parte da tela que talvez esteja sendo servida por outro framework distinto.
**3.** Utility seguem a mesma linha do Parcels, porém, eles não necessariamente precisam renderizar uma UI, podem servir apenas para comunicação, uma service e etc.

A partir daqui vou focar em Applications porque é o que tenho tido mais contato.

### Setup Inicial

Dito tudo isso agora podemos ir para á pratica, criaremos uma aplicação que contém dois MicroFrontends, sendo um deles em Angular e o outro em AngularJS, imaginando um cenário em que precisamos sair de uma aplicação legada para uma tecnologia mais recente, no entanto, após ter entendido como funciona o processo você poderá criar qualquer outro tipo, Vue, React, Preact e etc.

E por último mas não menos importante, cada framework tem sua própria "helper" lib dentro do Single-SPA, elas são responsáveis por adaptar o processo de montagem e desmontagem das aplicações conforme a necessidade do SingleSPA, dito isso, podemos ir para os requisitos:

> Você pode até mesmo criar sua própria implementação caso tenha alguma necessidade específica, e se tiver interessado para saber mais a fundo como isso funciona, você pode consultar [essa parte da documentação](https://single-spa.js.org/docs/building-applications/#lifecycle-helpers).

1. **Node.JS**
2. **create-single-spa**
3. **single-spa-angular**
4. **Angular CLI**

```
npm install create-single-spa
npm install single-spa-angular
npm install @angular/cli
```

Podemos ter duas formas de ter o nosso setup inicial, o primeiro sendo o recomendado pela documentação onde nós inserimos na raiz do projeto um arquivo HTML que irá carregar as dependências necessárias e um arquivo em JavaScript que será responsável por registrar as nossas aplicações juntamente com o Webpack, e o segundo exemplo onde você pode colocar Single-SPA na raiz do seu framework que será o "host" de todas as outras aplicações, apesar de a primeira alternativa ser a recomendada **EU** não consegui identificar nenhum ganho nessa parte, além de deixar de forma bastante explicita o que é relacionado ao Single-SPA e o que é relacionado ao framework escolhido dessa forma evitando misturar arquivos, pesquisando sobre também encontrei pessoas com a mesma indagação.

Portanto, seguiremos a recomendação da documentação para termos uma instalação mais limpa, após você ter entendido como funciona pode fazer o processo no seu framework favorito.

Com o "create-single-spa" instalado e já na pasta do projeto nós iremos utilizar do comando que irá nos ajudar a pular toda essa parte de setup através do seguinte comando:
```
create-single-spa
```

Caso não tenha instalado globalmente você pode utilizar do npx que deverá funcionar normalmente, ficando no caso `npx create-single-spa`

Após o comando ter sido executado será solicitado algumas informações sobre o nosso projeto, poderiamos até mesmo já escolher direto o framework de "host", mas como no nosso caso, nós queremos apenas a configuração inicial e ir criando cada aplicação aos poucos iremos escolher apenas o "root config", abaixo as respostas:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovatzfeydsvhislyj6qh.png)

Após o comando podemos notar que alguns arquivos foram criados na nossa pasta, no entanto, os mais importantes e os que nós, de fato, iremos alterar serão os que estão dentro de _"src"_ e ao entrarmos na nossa pasta nós temos um arquivo chamado _"index.ejs"_ esse será o arquivo que irá carregar o nosso Single-SPA e após isso nós iremos carregar o nosso arquivo ".js" com a função que irá registar nossas aplicações.

Outro fator importante na utilização do Single-SPA é o SystemJS, onde ele será responsável por registrar nossos módulos JavaScript.

> **DISCLAIMER:** Mais uma vez irei me abster sobre o arquivo _"index.ejs"_, assim como também sobre o SystemJS, pelos motivos que citei mais acima, á grosso modo mais uma vez o ".ejs" é um arquivo com estrutura semelhante para não dizer igual ao HTML, no entanto, ele dá suporte para renderização de HTMLs dinâmicos, como você pode identificar dentro do próprio arquivo onde temos um "if/else" para apontar para arquivos diferentes dependendo do ambiente e o SystemJS tem como objetivo carregar módulos JavaScripts e assim nós podemos registrá-los na função do Single-SPA, isso mais para dar suporte a browsers que estão atrás da ES6 onde ainda não existiam módulos de forma nativa no JS, caso queira saber mais sobre: [SystemJS](https://www.geeksforgeeks.org/difference-between-systemjs-and-webpack/) e [".ejs"](https://www.geeksforgeeks.org/difference-between-index-ejs-and-index-html/).

Já com os arquivos em mãos nós podemos agora apenas utilizar do seguinte comando para subirmos a nossa aplicação e nos será exibido a página inicial da aplicação.

```
npm run start
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9uqts2f7iqfrzxqekh9l.png)

Bom, agora nós já temos nossa configuração inicial feita basicamente e já podemos adicionar o nosso primeiro MFE, no entanto, aconselho fortemente que tente explorar a aplicação no seu formato mais enxuto antes, até que fique claro como as coisas se comportam, por ex: 

1. Ao abrirmos o "DevTools" e inspecionarmos nossos elementos HTMLs podemos ver que toda essa aplicação que está sendo importada através de um endereço remoto (por enquanto) foi inserida dentro de uma única `div` e o seu respectivo `id` contém a string 'single-spa-application:@' concatenado com o nome dado ao MFE no método RegisterApplication, por isso não podemos inserir nomes repetidos talvez possa gerar problemas de vazamento de escopo de estilo e outras complicações.

2. Tente renderizar a aplicação apenas em determinada rota, para isso basta alterar o `activeWhen`, ex: Tente renderizar essa aplicação inicial apenas na rota `/single-spa`.

3. Recomendo fortemente que leia os comentários no arquivo ".ejs", algumas coisas ali podem ser removidas, mas mais importante que isso é bom entender porque elas estão lá.

### Adicionando nossa primeira aplicação

Agora que já exploramos um pouco sobre como as coisas se comportam na configuração inicial podemos seguir adiante e criarmos o nosso primeiro MFE.

Para isso nós utilizaremos da CLI do Angular para criarmos o nosso projeto, para isso basta executarmos os comandos abaixo em sequência:
```
ng new NOME_PROJETO --routing
cd NOME_PROJETO

// Incluiremos a "schematics" do single-spa dentro do Angular.
ng add single-spa-angular
```

Após a utilização do single-spa-angular, que será responsável por formatar nosso projeto para se adequar as configurações do single-spa, nos será feito algumas perguntas, abaixo as respostas:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rm8xrlw49bu9vuz65zd.png)

Caso você já esteja familiarizado com o Angular deverá ter notado algumas mudanças em relação a um projeto convencional, não pretendo entrar em todas, mas as principais:

1. Foi criado um componente "empty-route" que tem como objetivo auxiliar o Single-SPA durante as transições de rotas para que nenhuma página de erro seja exibido, será necessário incluí-lo nos imports do nosso `App.module.ts`.
2. Utilizaremos de um build personalizado com o Webpack, para que ele realize o import das configurações do Single-SPA, assim o processo de build sairá do controle da CLI do Angular e será feito apenas pelo Single-SPA, mais pra frente veremos um exemplo disso.
3. Criação de dois novos scripts dentro do _"package.json"_ onde será os scripts que utilizaremos a partir de agora, saem o `ng serve` e `ng build` e entram `serve:single-spa:angular-mfe` e `build:single-spa:angular-mfe`, respectivamente.

Com o projeto gerado nós precisamos realizar algumas correções para termos tudo funcionando, abaixo as tarefas:

**1.** Importar o EmptyRouteComponent no nosso `App.module.ts`
**2.** Instalar todas as novas dependências inseridas pelo `ng add single-spa-angular` através do `npm i`.
**3.** Configurar as nossas rotas iniciais, dentro do `app-routing.module.ts` iremos inserir um provider para alterar a rota inicial da aplicação, ficando da seguinte forma:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s04cma5x5miu2dgvv4ly.png)
> Existe uma boa discussão sobre os motivos que levaram a essa modificação na rota base da aplicação, caso queira conferir: [Aplicação não funciona sem APP_BASE_HREF](https://github.com/single-spa/single-spa-angular/issues/64), [Como renderizar router links entre diferentes aplicações em sub-rotas](https://github.com/single-spa/single-spa-angular/issues/62) e a própria [documentação](https://single-spa.js.org/docs/ecosystem-angular#configure-routes) tenta explicar de forma resumida.

**4.** Executarmos o `ng g environments` para gerarmos os arquivos das variáveis de ambientes e após isso inserirmos a variável production com seu respectivo valor dentro de cada arquivo.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kjq024h0naevxbajsw53.png)

Bom após tudo isso, agora é só subirmos a aplicação e entrarmos nela normalmente, certo?

Então... caso você tente subir a aplicação será isso que você verá:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l26xg87aaa7rrravbna3.png)

Perceba que está tudo "normal", nós estamos vendo o "app-root" no DevTools, porem nada está sendo renderizado e o HTML está preenchido normalmente com a tela inicial do Angular, como havia explicado mais acima, a partir do momento que utilizamos do comando `ng add single-spa-angular` foram feitas alterações no processo de build e serve da nossa aplicação de forma que apenas o Single-SPA tem o "poder" de montar o componente.

Para isso precisamos voltar ao nosso arquivo _".js"_ lá na pasta raiz do projeto onde está o _"index.ejs"_ e precisamos registrar a nossa aplicação, assim como as condições de renderização, para isso não iremos alterar a configuração inicial (ainda), continuaremos renderizando a página de antes, registaremos apenas uma segunda aplicação da seguinte forma:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzvykfd16i619s21yba2.png)

E ao sairmos do diretório do Angular no teminal e executarmos o comando `npm run start` conseguiremos ver as duas aplicações coexistindo no mesmo espaço:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atqmal84pwb2e7er9v1l.png)

> Caso você não tenha conseguido e tenha surgido erros, existem dois erros comuns nesse momento, sendo o primeiro você não subiu a sua aplicação Angular e ela não está acessível, lembre-se, você precisa "servir" o arquivo `main.js` para o SingleSPA já que nós estamos apontando para `localhost` e o segundo é a ausência do 'Zone.js' que é dependência fundamental do Angular, você pode "descomentar" essa linha no `index.ejs`:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hudzaq2ztbuqpoo2hidc.png)

Bom agora nós temos as nossas duas aplicações coexistindo no mesmo espaço, ainda que de forma "bugada" mas isso é mais por conta das configurações de estilo inicial do setup do Angular, podemos apagar o conteúdo do `App.component.html` e deverá voltar ao normal, agora que temos o nosso primeiro componente, aconselho mais uma vez a explorar como as coisas funcionam:

**1**. Tente alterar a rota de montagem do Angular e inicialize ele em uma outra rota sem a página inicial do Single-SPA.
**2**. Tente renderizar componentes diferentes dentro do Angular de acordo com a rota.

#### Adicionando mais um MFE

Bom agora que você já explorou um pouco mais o seu primeiro MFE, espero que sim, podemos seguir adiante e adicionar um novo MFE que será o nosso AngularJS, ao final do AngularJS criaremos uma navegação simples entre MFEs através de `<a>` onde o Angular será o "host" principal e o AngularJS será servido apenas em páginas específicas.

Daqui pra frente é basicamente mais do mesmo que já fizemos anteriormente, precisamos instalar o nosso AngularJS e realizar o bootstrap dele conforme fariamos com uma aplicação normal em AngularJS, caso você não seja familiar estarei inserindo o código e você pode copiar e colá-lo dentro do nosso arquivo "js" de configurações.

Caso você queira pode criar outra aplicação em Angular também e repetir os passos que fizemos acima.

Antes de inserirmos o AngularJS realize as seguintes alterações no  Angular:

1. Altere a inicialização do Angular para ser feito apenas na rota `/ng`
2. Insira um parágrafo no template do App.component.ts que servirá de identificação do Angular, ex: "Hello World Angular"
3. Insira uma tag de `<a>` com o href para `/js`, essa tag nos levará para a rota que iremos inserir o AngularJS.

Lembre-se de sair da pasta do Angular:
```
npm install angular@1.8.2
// helper lib do AngularJS
npm install single-spa-angularjs
```

E após isso caso você não conheça ou não queira criar a aplicação em AngularJS de exemplo pode colar o código abaixo no mesmo arquivo .js onde registramos as aplicações, de preferência antes do registro.

```
import singleSpaAngularJS from 'single-spa-angularjs'
import angular from 'angular'

angular.module('app', [])

angular
    .module('app')
    .component('root', {
      template: `<p> Hello World AngularJS</p><a href="/ng">Retornar ao Angular 13</a>`,
      controller: function () {
        this.$onInit = function () {
          console.log("AngularJS Bootstrapped")
        }

        this.$onDestroy = function () {
          console.log("AngularJS Unmounted!")
        }
      }
    })


window.myAngularApp = singleSpaAngularJS({
  angular: window.angular,
  mainAngularModule: 'app',
  uiRouter:true,
  preserveGlobal: false,
  template: '<root/>'
})
```

e após isso podemos registrar a nossa aplicação normalmente da seguinte forma:

```
registerApplication({
  name: '@angularjs',
  app: myAngularApp,
  activeWhen: '/js'
})
```

Feito tudo isso, agora ao entrar na aplicação na rota base `/` não veremos mais nada, no entanto, ao entrarmos na rota do `/js` poderemos ver a mensagem que inserimos no template e uma tag para retornar ao Angular 13, e se tiver sido feito o que pedi mais acima em relação ao Angular 13 será renderizado outra tag para retornar ao AngularJS e é basicamente tudo isso, agora temos duas aplicações de tecnologias diferentes coexistindo em rotas diferentes.

Para finalizarmos nós podemos fazer com que o Angular seja a rota base alterando o `/ng` para `/` e ao entrarmos em `/js` veremos os dois MFEs coexistindo na mesma página.

Abaixo a URL para a aplicação no Stackblitz e o [repositório](https://github.com/mbarbosasan/single-spa-devto).

[![Abrir no Stackblitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/~/fork/github/mbarbosasan/single-spa-devto)

> **IMPORTANTE:** Ao entrar no stackblitz ele já irá executar o `npm i` e o `npm start` do Single-SPA, no entanto, você ainda precisará navegar até a pasta `angular-mfe` e instalar as suas respectivas dependencias e subir a aplicação. Além disso eu também precisei fazer algumas modificações para se adequar a URL fornecida pelo Stackblitz, mas deixei um comentário explicando o que fazer caso tente utilizá-la localmente.


### Observações e Conclusões finais

Pessoalmente, acredito que um dos principais ganhos de utilizarmos de MicroFrontends é podermos migrar de um Framework para o outro sem a necessidade de reescrita total de todos os componentes e termos as duas aplicações coexistindo no mesmo espaço, especialmente quando queremos atualizar a aplicação para tecnologias mais novas.

No entanto, á depender do tamanho da aplicação e seu respectivo contexto todo a complexidade e "overhead" necessário para utilização do Single-SPA não se "paga", já que você pode acabar caindo em alguns casos bem específicos de problemas e por não serem casos comuns você terá que desbravar o Stackoverflow atrás de respostas ou entrar a fundo sobre como funciona por debaixo dos panos, uma alternativa ao SingleSPA é o Nx que vem ganhando cada vez mais popularidade além de **EU** achar a documentação melhor, caso queira conferir pode começar pela explicação sobre ["Module Federations"](https://nx.dev/concepts/module-federation/module-federation-and-nx#what-is-module-federation).

E é basicamente ~~tudo~~ isso, se você chegou até aqui: 

![Kissing cat](https://media.giphy.com/media/3oz8xsaLjLVqVXr3tS/giphy.gif)