DEV Community

Cover image for Microfrontend: Registro dinâmico de componentes (module federation)
Alison Rodrigues
Alison Rodrigues

Posted on • Edited on

4

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.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

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

Okay