Introdução
Barrels são formas de acumular exportações de vários módulos em um arquivo base. Em resumo, são arquivos que re-exportam partes selecionadas de seu módulo. É uma técnica usada por muitas aplicações, inclusive Angular, React e Vue.
Provavelmente você já se deparou com importações neste formato:
// Vue
import { defineComponent, reactive, computed } from 'vue';
// Angular
import { Router, Route, ActivatedRoute } from '@angular/router';
Como Angular e Vue utilizam monorepo, ou seja, temos uma separação da aplicação em packages com responsabilidades definidas, é conveniente que as partes importantes desses packages sejam agrupados e exportados em um arquivo base que arroga a tarefa de ser o ponto central de acesso as partes daquele módulo. Também vale ressaltar que esta abordagem não se restringe a monorepo.
Importação de Módulos
Com a massiva atualização que o JavaScript recebeu em 2015 (aka ES6), dentre as novas funcionalidades recebemos o ESModules: um sistema oficial de módulos para o JavaScript. Havia alternativas como CommonJS e RequireJS, mas agora temos o suporte a módulos nativamente. Se deseja entender um pouco mais a fundo sobre módulos, indico o capítulo do livro do Dr. Axel Rauschmayer: Impatient Programmers - Modules. Ressalva: também é possível utilizar Barrel Pattern com CommonJS, mas me restringirei a explicá-lo com módulos nativos.
O contexto
Agora imaginemos um contexto: se estamos trabalhando com um Design System que se utiliza da filosofia Atomic Design, logo mais chegaremos a uma situação semelhante em diversas partes da nossa aplicação:
// Navbar.vue
import Menu from './components/menu/Menu';
import MenuList from './components/menu/MenuList';
import MenuItem from './components/menu/MenuItem';
import MenuLink from './components/menu/MenuLink';
import Select from './components/select/Select';
import SelectList from './components/select/SelectList';
import SelectOption from './components/select/SelectOption';
Percebemos que estamos importando 4 componentes da pasta menu e 3 da pasta select, repetindo o caminho de diretórios e alterando apenas o nome do componente. Certamente, se pudéssemos melhorar o código acima por um mais elegante seria interessante. Vejamos um exemplo com Barrel:
// Navbar.vue
import {
Menu,
MenuList,
MenuItem,
MenuLink
} from './components/menu';
import {
Select,
SelectList,
SelectOption
} from './components/select';
Agora temos uma sintaxe mais enxuta, não precisando repetir o nome do componente devido a desestruturação do objeto que o arquivo barrel de cada módulo exporta. Porém, ainda podemos melhorar.
Caso não tenha familiaridade com o conceito de desestruturação de objetos e arrays, indico como referência: JavaScript.info - Destructuring Assignment.
A abordagem
Como já vimos, é possível desestruturar um objeto exportado e extrair somente o que nos é necessário. Então, vamos entender como funciona um arquivo barrel (o arquivo que exporta a API pública do módulo).
Vejamos como o Angular exporta partes do package @angular/router:
Analise na íntegra e veja que existe um arquivo index.ts exportando partes de router. A escolha do nome do arquivo não é por acaso. No sistema de módulos, quando referenciamos uma pasta, é buscado inicialmente por um arquivo index.
Levando em conta, assim ficaria nossa pasta components em uma aplicação Vue:
components
├── menu
│ │── Menu.vue
│ │── MenuList.vue
│ │── MenuItem.vue
│ │── MenuLink.vue
│ └── index.js <-- arquivo barrel
├── select
│ │── Select.vue
│ │── SelectList.vue
│ │── SelectOption.vue
│ └── index.js <-- arquivo barrel
Agora exportamos os componentes que desejamos:
// components/menu/index.js
export { default as Menu } from './Menu.vue';
export { default as MenuList } from './MenuList.vue';
export { default as MenuItem } from './MenuItem.vue';
export { default as MenuLink } from './MenuLink.vue';
Pronto. Ao acessarmos components/menu, o arquivo barrel (index.js) será chamado e retornará um merge dos objetos com nossos componentes, e então poderemos usar a desestruturação para extrair o que realmente precisamos:
// Navbar.vue
import { Menu, MenuList } from './components/menu';
Melhorando a abordagem
A abordagem acima é bem interessante, porém é bom ficar atento para não exagerar no uso de barrels. Nós não queremos ter muitos arquivos, pois isso é contraproducente e geralmente leva a problemas de dependência circular que às vezes podem ser bem complicados de resolver.
O recomendado é que cada módulo tenha um arquivo barrel, delegando ao mesmo a responsabilidade de exportar as suas partes convenientes. Deve concentrar a API pública e ser responsável por encapsular o acesso de suas partes. Caso a localização de uma pasta interna seja alterada, aqueles que utilizam seu módulo não devem ser afetados.
Sabendo disso, podemos colocar um arquivo barrel na raiz da pasta components e exportar suas partes internas de forma concentrada:
components
├── index.js
├── menu
│ └── ...
├── select
│ └── ...
Agora podemos exportar de duas formas:
Individual, sendo assim, não é necessário um arquivo barrel em cada pasta interna referente a um componente (recomendado):
// components/index.js
export { default as Menu } from './menu/Menu.vue';
export { default as MenuList } from './menu/MenuList.vue';
export { default as MenuItem } from './menu/MenuItem.vue';
export { default as MenuLink } from './menu/MenuLink.vue';
export { default as Select } from './select/Select.vue';
export { default as SelectList } from './select/SelectList.vue';
export { default as SelectOption } from './select/SelectOption.vue';
Ou reaproveitando os arquivos barrels e re-exportando no arquivo barrel base do módulo:
// components/index.js
export * from './menu';
export * from './select';
No final das contas teremos o seguinte resultado:
// Navbar.vue
import { Menu, MenuList, Select, SelectList } from './components';
Conseguimos acessar todos os nossos componentes apenas referenciando a pasta components. Não precisamos saber quais componentes estão lá, nem lembrar das subpastas que exportam algo. Tudo o que está acessível para importar encontra-se no arquivo barrel base (API pública do módulo).
Dica: se você usa o VSCode, apertando Ctrl+Space
dentro das chaves lhe é sugerido uma lista do que é possível importar:
A indecisão
É provável que você tenha se perguntado a diferença em utilizar export * from
vs export {} from
. A reposta que eu daria: depende do contexto. No exemplo do @angular/router as duas formas de exportação são usadas. Preferencialmente, opte por exportações descritivas, ou melhor dizendo, export explícito, assim podemos controlar melhor o que de fato queremos expor.
export * from
adiciona um toque de "mágica". Controlar o que estamos exportando é um pouco mais difícil, já que precisaremos ir no arquivo que é re-exportado para ter uma melhor noção do que realmente está sendo exposto. Por outro lado, se você tem uma API muito bem definida, partes importantes do seu módulo que não são constantemente alteradas, é plausível utilizar esta forma "mágica".
Avalie o seu contexto e aplique como melhor planejar. Não é uma regra. Não terceirize suas decisões.
Conclusão
Entendemos uma abordagem para reduzir as repetições em importações, melhorar o compartilhamento de código entre módulos especializados, encapsular caminhos de arquivos e reduzir quebra de dependências, e também uma forma mais elegante de manter nosso código.
Portanto, use este pattern com cautela. Há contextos em que ele é muito útil. O problema não está no Barrel Pattern, e sim no mau uso do mesmo.
Referências
- https://basarat.gitbook.io/typescript/main-1/barrel
- https://dev.to/luispa/how-to-create-barrels-in-typescript-or-javascript-59ma
- https://medium.com/@adrianfaciu/barrel-files-to-use-or-not-to-use-75521cd18e65
- https://hackernoon.com/react-project-architecture-using-barrels-d086146eb0f6
- http://cangaceirojavascript.com.br/barrels-simplificando-importacoes-de-modulos/
- https://srinivasankk.com/javascript-barrel/
Soli Deo Gloria
Top comments (0)