Micro Frontends com o Single SPA, React e VueJs

O objetivo do Micro frontends é capacitar a construção, teste e implantação de peças do front-end de forma independente. Saiba mais!

Os aplicativos web estão se tornando cada vez mais complexos. Torna-se um desafio lançar softwares de maneira ágil sem sacrificar a qualidade.  Como solução para essas situações, surgiu o Micro frontend. O objetivo dele é capacitar a construção, teste e implantação de peças do front-end de forma independente. 

Este padrão arquitetural de micro-frontends diminui a complexidade de grandes projetos front-end, onde é possível separar um projeto extenso em partes menores com tecnologias diferentes, funcionando de forma parcialmente independente.  

Essa solução é muito útil em cenários onde temos um projeto frontend com muitos times responsáveis trabalhando em apenas uma base de código. Diversos problemas podem surgir quando temos um monolito, como: 

  • Emergir conflitos muito frequentes, afetando todo o projeto; 
  • Basta uma pequena mudança que irá impactar todo o projeto; 
  • Dificilmente é possível trabalhar com diferentes tecnologias. 
  • Muitos times trabalhando em um mesmo código.
  • Grande acoplamento.

Nesses cenários, faz sentido utilizar a arquitetura para ter vantagens como:  

  • Cada time será responsável por apenas uma parte do projeto de forma independente; 
  • Fazer diversas tecnologias conviverem no dia a dia. Por exemplo, uma parte do time pode trabalhar com React enquanto a outra trabalha com VueJs sem nenhum conflito; 
  • Partes separadas do projeto rodando em locais diferentes e em ciclos de deploy diferentes, onde uma alteração não irá impactar em outra parte do projeto. 
  • Menor acoplamento.
  • Possibilidade de trabalhar com princípios de responsabilidade única.

Criando um projeto utilizando Micro-frontends 

Para exemplificar como funciona esta dinâmica de micro-frontends, iremos criar 4 aplicações rodando de forma independente e trabalhando em conjunto. 

Este projeto é baseado na live “Micro Frontends com single-spa e react”, com Rodrigo Branas  (arquiteto de software, professor, autor, palestrante) e Carlos Sempé  (Tech Leader na ZENVIA).

Veja o vídeo completo sobre o tutorial:

Instalando o framework Single SPA 

Para construir nossos micro-frontends vamos utilizar o framework Single SPA. Este framework javascript permite o desenvolvimento de micro-frontends onde é possível escolher qualquer uma grande quantidade de frameworks Javascript, como React, Vue e Angular, com toda a compatibilidade de integração. 

1. Será necessário ter o create-single-spa configurado. 

  • Para fazer a instalação rode o comando: 
npm install --global create-single-spa
 
# or
yarn global add create-single-spa

Caso você não opte por utilizar o create-single-spa globalmente, utilize um dos comandos abaixo: 

npm init single-spa# 
ou 
npx create-single-spa# 
ou 
yarn create single-spa 

Iniciando o Projeto 

2. Com o comando yarn create single-spa utilize as seguintes configurações para ser criado nosso projeto root. 

  • O root será responsável por rotear todos os nossos Micro-Frontends, é nele que iremos configurar todas as rotas de cada aplicação e ele será responsável por gerenciar os lifecycles de cada micro frontend.
  • Entre na pasta do projeto cd root , rode o comando yarn start e a aplicação estará disponível em http://localhost:9000/


3. Agora vamos adicionar mais uma aplicação frontend separada, que será o nosso navbar, com o comando yarn create single-spa . Adicione as seguintes configurações:
 

  • Entre na pasta do projeto que foi gerado em React com o comando cd navbar . 
  • Rode o comando yarn start e a aplicação estará disponível em http://localhost:8080/
  • Clicando neste link abaixo é possível ver o javascript que é responsável pela criação da página. Utilizaremos esta url para ativar todos os micro-frontends no root. 


Ao clicar no link é possível ver este código, responsável por gerar a página web do projeto (entry point).
 

4. Agora vamos configurar o navbar no projeto root. 

  • Para compartilhar libs entre aplicações React, o navbar espera como injeção de dependência as libs do react e react-dom dentro do System.register. 
  • Para configurar as dependências do react e react-dom, vá em http://localhost:9000/ e copie estes links abaixo:
     

  • No arquivo index.ejs que está no projeto root, cole as dependências para que o  react e o react-dom sejam compartilhados entre outros projetos. 
Nessa parte é onde colocamos as dependências que serão comuns para os micro frontends

5. Em index.ejs no projeto root, em imports , adicione a url de entry point da navbar com o nome do projeto “@mfe/navbar”: “//localhost:8080/mfe-navbar.js: 

Aqui é onde colocarmos todos os micro frontends que farão parte da nossa aplicação.

6. Configure o arquivo mfe-root-config.ts dentro de src no projeto root, para ser integrado ao projeto navbar e apague o registerApliccation que existe. Dentro do arquivo mfe-root-config.ts definimos quando e qual projeto será utilizado em determinada rota. As tags que utilizamos são as seguintes:

  • “name”: onde informamos qual será o nome desse micro frontend em nossa aplicação (esse será o nome que ficará na div onde esse mfe será montado e desmontado no nosso dom).
  • “app”: é o mesmo nome que utilizamos no passo anterior para definir o caminho de nosso entrypoint.
  • “activeWhen”: é onde determinamos quando um mfe deve ser montato ou desmontado, nesse caso abaixo, estamos montando o mfe navbar toda vez que acessarmos a rota “/app1”. Obs. Podemos também trabalhar com subRotas dentro de cada mfe.
import { registerApplication, start } from "single-spa";

registerApplication({
  name: "@mfe/navbar",
  app: () => System.import("@mfe/navbar"),
  activeWhen: ["/app1"]
});

start({
  urlRerouteOnly: true,
});

7. Agora vamos criar mais uma aplicação em React, rode o comando yarn create single-spa e adicione as seguintes configurações:
 

  • Entre na pasta do projeto cd app1 e rode o comando yarn start e a aplicação estará rodando em http://localhost:8081/ 

8. Em index.ejs dentro de src no projeto root em imports adicione a url de configuração do app1 “@mfe/app1”: “//localhost:8081/mfe-app1.js”:
 

9. Configure o arquivo mfe-root-config.ts dentro de src no projeto root, para ser integrado ao projeto app1. 

  • Refatore o arquivo mfe-root-config.ts da seguinte maneira: 
import { registerApplication, start } from "single-spa";

registerApplication({
  name: "@mfe/navbar",
  app: () => System.import("@mfe/navbar"),
  activeWhen: ["/app1"]
});
registerApplication({
  name: "@mfe/app1",
  app: () => System.import("@mfe/app1"),
  activeWhen: ["/app2"],
});


start({ urlRerouteOnly: true, });
  • Agora de fato temos três aplicações frontend, root, app1 e o navbar. Rodando de fato, sendo roteados através da aplicação root.  Colocamos uma aplicação (navbar) para rodar na rota ”/app1″ e outra (“app1”) para rodar na rota “/app2”.

10. Para exemplificar como é feita a renderização dos componentes em tela e seu ciclo de vida (lifecycles), no arquivo mfe-app1.tsx crie uma função bootstrap, mount e unmount com o seguinte código:

import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    return null;
  },
});

const { bootstrap: _bootstrap, mount: _mount, unmount: _unmount } = lifecycles;

export function bootstrap(props) {
  return Promise.resolve().then(() => {
    console.log(props.name, " bootstraped");
    _bootstrap(props);
  });
}
export function mount(props) {
  return Promise.resolve().then(() => {
    console.log(props.name, "mounted");
    _mount(props);
  });
}
export function unmount(props) {
  return Promise.resolve().then(() => {
    console.log(props.name, "unmonted");
    _unmount(props);
  });
}

11. Faça o mesmo na aplicação navbar, alterando o arquivo mfe-navbar.tsx:

import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    return null;
  },
});

const { bootstrap: _bootstrap, mount: _mount, unmount: _unmount } = lifecycles;

export function bootstrap(props) {
  return Promise.resolve().then(() => {
    console.log(props.name, " bootstraped");
    _bootstrap(props);
  });
}
export function mount(props) {
  return Promise.resolve().then(() => {
    console.log(props.name, "mounted");
    _mount(props);
  });
}
export function unmount(props) {
  return Promise.resolve().then(() => {
    console.log(props.name, "unmonted");
    _unmount(props);
  });
}
  • Em http://localhost:9000/ abra o console no navegador. Para evitar o reload do html, utilize uma função do próprio singleSpa para navegar entre os mfes e verificar os lifecycles das aplicações. Limpe o console e faça como na imagem abaixo:
A função utilizada é singleSpaNavigate(‘app1’);
  • Faça uma navegação para a sua outra rota usando o comando singleSpaNavigate(‘app2’);
  • Você verá que a aplicação app1 será desmontada e a aplicação app2 terá rodado o bootstrap em sua primeira execução e posteriormente será montado no Dom.
  • Se você retornar novamente para o app1, verificará que somente a função mount será chamada. Isso acontece pois a função bootstrap é responsável por baixar a aplicação em sua primeira vez, após isso apenas é executado funções de mount e unmount.
Neste exemplo podemos ver os ciclos citados acima.

12. Agora vamos criar mais um micro-frontend em vuejs chamado app2. Rode o comando yarn create single-spa e adicione as seguintes configurações: 

  • Entre na pasta do projeto cd app2.  As aplicações single-spa oferecem uma forma de executarmos sem dependermos do nosso root, porém isso é indicado apenas para ambientes de desenvolvimento.
  • Para fazer isso, execurte o comando yarn serve:standalone
  • Isso fará com que sua aplicação esteja disponível em http://localhost:8082.  Sem estar dependendo do seu root.

13. Em index.ejs , no projeto root em imports, adicione a url de configuração do app2 “@mfe/app2”: “//localhost:8082/js/app.js”

14. Configure o arquivo mfe-root-config.ts dentro de src no projeto root para ser integrado ao projeto app2 em Vuejs: 

Obs. Veja que no código abaixo também alteramos para a navbar estar sempre presenta na aplicação colocando () => true

import { registerApplication, start } from "single-spa";

registerApplication({
  name: "@mfe/navbar",
  app: () => System.import("@mfe/navbar"),
  activeWhen: () => true
});
registerApplication({
  name: "@mfe/app1",
  app: () => System.import("@mfe/app1"),
  activeWhen: ["/app1"],
});
registerApplication({
  name: "@mfe/app2",
  app: () => System.import("@mfe/app2"),
  activeWhen: ["/app2"],
});

start({ urlRerouteOnly: true, });
  • Agora temos quatro micro-frontends rodando em conjunto, root, navbar, app1 e app2 

15. Entre no projeto navbar e instale a biblioteca material-ui com o comando seguinte: 

yarn add @material-ui/core 

16. No projeto navbar da pasta src crie um arquivo chamado App.tsx, adicione a navbar do material-ui como o seguinte código: 

  • No código abaixo foi utilizada a lib do single-spa. Ela esta sendo importada pelo próprio root para o outros mfes. Para fazer as rotas dentro do projeto, utilizamos uma função do single-spa. Função: singleSpa.navigateToUrl(‘coloqueARotaAqui’);
  • O código abaixo foi retirado da própria documentação do Material-UI do React. O link do código copiado para o componente de navbar é https://material-ui.com/pt/components/tabs/.
import React from 'react';
//@ts-ignore
import * as singleSpa from 'single-spa';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';

function TabPanel(props) {
    const { children, value, index, ...other } = props;

    return (
        <div
            role="tabpanel"
            hidden={value !== index}
            id={`simple-tabpanel-${index}`}
            aria-labelledby={`simple-tab-${index}`}
            {...other}
        >
            {value === index && (
                <Box p={3}>
                    <Typography>{children}</Typography>
                </Box>
            )}
        </div>
    );
}

TabPanel.propTypes = {
    children: PropTypes.node,
    index: PropTypes.any.isRequired,
    value: PropTypes.any.isRequired,
};

function a11yProps(index) {
    return {
        id: `simple-tab-${index}`,
        'aria-controls': `simple-tabpanel-${index}`,
    };
}

const useStyles = makeStyles((theme) => ({
    root: {
        flexGrow: 1,
        backgroundColor: theme.palette.background.paper,
    },
}));

export default function SimpleTabs() {
    const classes = useStyles();
    const [value, setValue] = React.useState(0);

    const handleChange = (event, newValue) => {
        setValue(newValue);

        switch (newValue) {
            case 0:
                singleSpa.navigateToUrl('app1');
                break;
            case 1:
                singleSpa.navigateToUrl('app2');
                break;
            default:
                break;
        }
    };

    return (
        <div className={classes.root}>
            <AppBar position="static">
                <Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
                    <Tab label="App React" {...a11yProps(0)} />
                    <Tab label="App Vue" {...a11yProps(1)} />
                </Tabs>
            </AppBar>
        </div>
    );
}

17. Em navbar na pasta src no arquivo root.component.tsx adicione a seguinte configuração: 

import SimpleTabs from "./App";
export default function Root(props) {
  return SimpleTabs();
}

18. Abra seu navegador e em http://localhost:9000/, agora é possível ter acesso ao navbar com a estilização do material-ui e navegar entre os micro-frontends em React e Vuejs. 

Neste tutorial aprendemos como utilizar o single-spa para a criação de micro-frontends e conseguimos ter quatro projetos front-ends rodando separadamente, com frameworks diferentes e trabalhando em conjunto de forma independente. O repositório deste projeto está disponível aqui. Continue acompanhando nosso Blog Developers e descubra novos conteúdos.

Acompanhe também o canal Dev Sempé!

*Carlos Sempé é  Tech Leader na ZENVIA | Canal Dev Sempé, LinkedIn e Instagram.

Categorias:
Escrito por

Carlos Sempé

Leia também

Fique por dentro e confira as nossas dicas sobre o mercado mobile e interação digital.