DEV Community

Alison Rodrigues
Alison Rodrigues

Posted on

7 1 1 1 1

Microfrontend: Registro dinâmico de componentes (module federation)

Hoje em dia existem várias formas de se construir aplicações front end distribuídas. Também existem inúmeros frameworks para facilitar a vida de quem está construindo um ambiente de componentes distribuídos e diferentes formas de gerenciar dependências e orquestrar os componentes visíveis no browser.

Neste artigo vou utilizar o "module federation", um modelo arquitetural proposto por Zack Jackson e outros Core Developers do Webpack. Como o module federation é uma especificação, existem implementações em diferentes Module bundlers como o Rollup. Porém a que está com a api mais avançada é mesmo o Webpack. Então os exemplos desse artigo são escritos utilizando do plugin ModuleFederation do Webpack.

Para que a arquitetura de module federation funcione, existem dois papéis, a application shell e o remote component. Esses papeis podem ser atribuídos a duas apps no mesmo projeto ou em projetos distintos (o mais comum é construí-los em projetos distintos).

O remote component deve informar quais trechos de códigos quer expor para serem utilizados em outras aplicações. Por exemplo uma classe, um componente, uma função e etc. E a application shell precisa ter registrado onde estão os componentes remotos que serão importados.

webpack.config.js (remote component)


module.exports = {
    //... 
    plugins: [
        new ModuleFederationPlugin({
            name: 'RemoteComponent1', // nome do módulo remoto
            filename: "remoteEntry.js",
            exposes: {
                "./AppContainer": "./src/App.js" // arquivos exportados, disponíveis para serem importados por outra aplicação
            },
            // ...
        }),
    ],
};

Enter fullscreen mode Exit fullscreen mode

Exemplo de como exportar um componente utilizando o plugin ModuleFederationPlugin

webpack.config.js (application shell)


module.exports = {
    // ...
    plugins: [
        new ModuleFederationPlugin({
            name: "container",
            filename: "containerEntry.js",
            remotes: {
                "AppContainer": "RemoteComponent1@https://host-of-component/remoteEntry.js"
            },
            shared: {
                // ...
            }
        })
    ]
};

Enter fullscreen mode Exit fullscreen mode

Exemplo de como configurar o plugin na aplicação shell para que seja possível encontrar o componente remotamente

Eis o problema

Como podemos ver, o registro dos componentes remotos acontece de forma estática através da propriedade remotes na configuração do plugin. Mas e se quisermos adicionar um novo componente? E se tivermos que atualizar o endereço http de algum dos componentes remotos? E se haver um controle de versão no projeto do componente remoto e o arquivo remoteEntry.js for gerado dinamicamente com diferentes nomes para diferentes versões?

Para todos esses casos e muitos outros, terá que ser alterado o arquivo webpack.config.js da aplicação shell e compilar o projeto todo novamente. Mesmo que não tenha sido alterado uma linha se quer de javascript na aplicação shell, o projeto tem que ser compilado novamente, ir para produção novamente, por conta de uma alteração em outro projeto. Ou seja pode acontecer de ser necessário recompilar a aplicação shell inúmeras vezes por causa dos projetos de componentes.

Eis a solução

O webpack + module federation fornece para nós uma API para que os componentes possam ser registrados dinamicamente. 🤩🤩🤩
Afinal esse registro acontece em tempo de execução, então nós podemos fazer manualmente a mesma coisa que o webpack faz!!!

O pulo do gato

Com o poder de registar os módulos remotos em suas mãos, você pode ler a lista de módulos remotos de qualquer lugar e não somente de um registro estático. Pode ser de um json hospedado em outro lugar ou até mesmo de algum cadastro servido por uma API (situação recomendada).

Mão na massa

A sintaxe para importação de um modulo remoto é muito simples, precisamos apenas de nome do componente, nome do módulo e a url, Exatamente como pudemos ver no exemplo acima da configuração dos remotes na aplication shell.

O script abaixo cria uma tag script no head do html para importar o script remoto e após o arquivo js ser importado instancia o modulo


async function loadRemoteComponent({scopeName, moduleName, url} = params) {
    const moduleExist = await window[scopeName]?.get(moduleName)
    return await moduleExist ? loadRemoteModule(scopeName, moduleName) : importRemoteScope(scopeName, moduleName, url)
}

async function importRemoteScope(scope, moduleName, url) {
    const element = document.createElement("script")
    element.type = "text/javascript"
    element.async = true
    element.setAttribute('id', `scope_${scope}`) // id caso você queria remover o import depois por algum motivo
    element.src = url
    let promise =  new Promise((resolve, reject) => {
        element.onload = async () => {
            const mod = await loadRemoteModule(scope, moduleName)
            resolve(mod)

        }
        element.onerror = async () => { reject('Não foi possivel carregar o modulo desejado') }
    })
    document.head.appendChild(element)
    return promise
}

async function loadRemoteModule(scope, moduleName) {
    await __webpack_init_sharing__("default")
    const container = window[scope]
    await container.init(__webpack_share_scopes__.default)
    const factory = await window[scope].get(moduleName)
    const module = factory()
    return module.default
    // se você não trabalha com export default faça: return module
}

Enter fullscreen mode Exit fullscreen mode

Esse trecho de código deve estar presente na sua aplicação shell.

E para instanciar seu componente basta chamar:

loadRemoteComponent({
    moduleName: './AppContainer',
    scopeName: 'RemoteComponent1',
    url: 'http://https://host-of-component/remoteEntry.js'
  });
Enter fullscreen mode Exit fullscreen mode

Você pode ter cadastrado esses componentes em outro lugar e não de forma estática no arquivo webpack.confg.js. Assim você faz uma requisição para essa lista de cadastro de componentes e os registra. Ou pode fazer o registro sob demanda, somente quando alguém pedir esse componente a sua aplicação shell.

Comparativo

Registro estático no webpack.config.js


// ...
new ModuleFederationPlugin({
    remotes: {
        "AppContainer": "RemoteComponent1@https://host-of-component/remoteEntry.js"
    }
})
// ...

Enter fullscreen mode Exit fullscreen mode

Registro dinâmico com a função loadRemoteComponent

loadRemoteComponent({
    moduleName: './AppContainer',
    scopeName: 'RemoteComponent1',
    url: 'http://https://host-of-component/remoteEntry.js'
  });
Enter fullscreen mode Exit fullscreen mode

Gostou? Ficou com alguma dúvida? Deixa um comentário que eu respondo.

Image of Datadog

Master Mobile Monitoring for iOS Apps

Monitor your app’s health with real-time insights into crash-free rates, start times, and more. Optimize performance and prevent user churn by addressing critical issues like app hangs, and ANRs. Learn how to keep your iOS app running smoothly across all devices by downloading this eBook.

Get The eBook

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay