O Module Federation é um plugin do webpack que permite trabalhar com microfrontends removendo uma carga de complexidade e com algum ganho de performance que iremos descrever nesse breve artigo.
Disclaimer:
- Esse artigo não é um tutorial! é apenas um artigo de referência para descrever as opções da propriedade Share do plugin
- Teremos o termo shell mais usado pela comunidade como a definição de um container que abriga ali vários microfrontends, e microfrontend chamaremos de MF ou MFs(plural) até o final do artigo.
Existem hoje algumas possibilidades de se trabalhar com MFs, algumas delas usando outros frameworks como single-spa, estratégias com monorepo como NX ou Angular Worskpace e até mesmo alguma proposta de compiler que tenta resolver o problema de duplicação de código que as abordagens citadas anteriormente não se propõem a fazer “meados de maio de 2022".
Mas o que seria a duplicação de código?
Tenha o seguinte cenário em mente, temos uma aplicação chamada de shell com a versão 17 do react e um MF utilizando a mesma versão, quando geramos o bundle final que vai para a produção temos a duplicação do core da biblioteca, pois mesmo que essas aplicações estejam utilizando a mesma versão da biblioteca ela não é compartilhada entre as mesmas, e esse cenário pode ser o mesmo utilizando frameworks como angular, então imagina uma aplicação com vários MFs, considere o tamanho do bundle que será consumido pelo client/browser, e existem algumas abordagens para resolver esse problema no processo de build, mas requer alguma complexidade.
E o que muda com o Module Federation
É exatamente nesse ponto que o module federation entra, ele não vem apenas para ser mais uma solução de MFs e sim para resolver o problema de duplicação de código.
Voltando para o mesmo cenário citado, as duas aplicações com a mesma dependência do React na versão 17, que agora utilizando module federation conseguirmos através da propriedade share criar estratégias que forneçam a possibilidade de ter o core da biblioteca compartilhado entre o Shell e os MFs gerando um ganho de performance considerável já que o cliente/browser vai consumir apenas o bundle necessário para a execução da aplicação.
Agora vamos entender as Opções do plugin
name: string é o nome que definimos para o nosso MF, e é uma boa prática que ela seja a mesma que o nome da aplicação.
filename: string é o nome dado ao entrypoint que o webpack vai gerar para que possa ser consumido pela aplicação shell ex: “meuModuleRemoteEntry.js".
library: object é um objeto com opções que ajudam descrever como um código/chunck gerado que foi exposto será armazenado e recuperado, essa propriedade tem seu próprio leque de configurações, como a proposta desse artigo é falar um pouco mais sobre a propriedade share vou deixar o link para documentação.
Mas no geral você pode configurar como consumir esse MF que vai expor, como uma variável ou módulo etc...
share: object -> É onde declaramos todas as bibliotecas que podem ser compartilhadas entre os MFs, quando o shell compartilha uma determinada lib com os MFs e os MFs tem a mesma lib compatível com o shell os MFs consomem a lib compartilhada pelo shell do contrário acabam utilizando a sua própria versão lib na aplicação, sendo assim teremos duas libs iguais mais que podem estar versões diferentes.
Entendo isso temos 3 formas de declarar as bibliotecas compartilhadas.
- Array:
shared: ['react']
- objeto
shared: {
// Será utilizado a maior versão do React
// que pode se >= 17.0 and < 18
react: '^17.0.2',
},
- Objeto da biblioteca com algumas opções de compartilhamento:
shared: {
// adiciona o react como módulo compartilhado
// e configura mais algumas opções
react: {
requiredVersion: '^17.0.2',
singleton: true,
},
},
E essas opções de compartilhamento podem ser:
eager: boolean -> essa opção vai permitir fornecer um um módulo inicial no chunck da aplicação sendo ele o módulo principal ou um fallback, o módulo vai ser compilado junto com seu MF em vez de ser solicitado de forma assíncrona.
import: false | string -> fornece o módulo que deve ser adicionado ao Shared Scope, se o módulo compartilhado no Shared Scope não for encontrado ele vai usar o módulo fornecido no import como fallback
shareKey: string -> O módulo compartilhado é pesquisado através dessa chave do escopo compartilhado.
shareScope: string -> O nome do escopo compartilhado.
Exemplo da utilização do import, shareKey e shareScope
shared: {
'my-extension-lib': {
import: 'lib', // O pacote "lib" será usado como pacote fornecido e um substituto
shareKey: 'shared-lib', // Nome dado ao módulo que será colocado no Share Scope
shareScope: 'default', // Nome dado ao Share Scope
version: '1.2.3', // A versão da lib
requiredVersion: '^1.0.0', // A versão minima solicitada pela lib
},
},
packageName: string -> Necessário APENAS para quando precisamos determinar uma versão de biblioteca que não pode ser determinada automaticamente.
requiredVersion: string -> Usado para declarar a versão especifica de uma biblioteca.
singleton: boolean ->Essa opção permite ter uma única versão do módulo que foi compartilhado no escopo de compartilhamento o que seria o shell.
Usar mais do que uma versão de biblioteca não é uma escolha muito interessante, pois quando falamos de bibliotecas que mantém o estado, você utilizar mais de uma versão fornece automaticamente a duplicidade de um Estado Principal para a aplicação.
Por esse motivo o Module Federation permite definir bibliotecas como singletons, se temos versões da biblioteca compatíveis considerando a Minor version o Module Federation vai decidir pela versão mais recente pela lib.
É possível usar versões diferentes de uma lib usando um range no requiredVersion, mesmo optando pelo singleton: true
e strictVersion true
.
strictVersion: boolean -> Essa opção deve rejeitar o módulo compartilhado e emitir um erro se a versão não é válida, por default ela é true quando um módulo de fallback está disponível e não estamos utilizando a opção singleton, a outra opção é false mas ele não tem efeito se houver declarado a versão obrigatória da biblioteca.
version: false | string -> A versão do módulo fornecido, permite que o webpack substitua uma versão correspondente Minor version, mas não a Major version.
Em resumo falando sobre Share e o compartilhamento de libs, o Shell sempre vai assumir a responsabilidade de manter a versão mais atual da biblioteca compartilhada com os MFs, mas quando o carregamento dos MFs não for feito de forma estática em suma quando for feito de forma dinâmica, e o Shell tenha uma versão menor do que o MF cada um vai ficar responsável por subir sua própria versão da biblioteca.
Ainda podemos através do compartilhamento dos MFs de forma dinâmica carregar dinamicamente apenas o ponto de entrada remoto no início do programa e carregar o micro frontend posteriormente sob demanda. Ao dividir isso em dois processos de carregamento, o comportamento é exatamente o mesmo do compartilhamento de forma estática ("clássica"). A razão é que neste caso os metadados da entrada remota estão disponíveis com antecedência suficiente para serem considerados durante a negociação das versões.
Caso eles sejam estáticos essas libs vão ser conhecidas no tempo de build tanto do shell quanto do microfrontend e com isso será carregado a versão mais atual da lib em questão.
Bom é isso, Module Federation trás um cenário bem interessante e com muitas possibilidades para se trabalhar com Microfrontends.
Referências:
Documentação Webpack
Angular Architects
Top comments (0)