Ir para o conteúdo

Essa funcionalidade tem sido utilizada por muitos desenvolvedores em projetos de todos os níveis. Entretanto, nem todos conhecem as vantagens e desvantagens de utilizar esse recurso. Em uma JSConf, o criador do Node.js, Ryan Dahl, se arrepende de ter criado essa funcionalidade. Veremos quais são os impactos de utilizar essa funcionalidade em um projeto.

O que são barrel files?

Os arquivos de barril, em tradução direta, são arquivos para acumular exportações de módulos em um único módulo, simplificando a importação dos mesmos. Para exemplificar, vamos construir um projeto com Node.js, TypeScript e Webpack.

Para iniciar, vamos criar algumas funções em arquivos separados, denominados módulos:

// src/utils/foo.ts
export function foo() {
	console.log("foo!");
}

// src/utils/bar.ts
export function bar() {
	console.log("bar!");
}

// src/utils/baz.ts
export function baz() {
	console.log("baz!");
}

Se quisermos importar essas funções, precisaremos fazer dessa forma:

// src/without-barrel.ts
import { foo } from "./utils/foo";
import { bar } from "./utils/bar";
import { baz } from "./utils/baz";

foo();
bar();
baz();

Agora, criando um barrel file podemos simplificar as importações:

// src/utils/index.ts
export * from "./foo";
export * from "./bar";
export * from "./baz"; // src/with-barrel.js
import { foo, bar, baz } from "utils";

foo();
bar();
baz();

 

Vantagens

A vantagem é centralizar as importações de um mesmo módulo, com o objetivo de diminuir linhas de importação. Imagine um cenário onde há 10 módulos importados de uma mesma pasta. Para cada módulo terá que ser escrito um import separado.

 

Desvantagens

Apesar de simplificar e deixar o código mais harmônico, essa prática pode trazer consequências negativas para a aplicação.

Vamos por em prática uma das desvantagens!

Para começar, vamos criar um projeto e adicionar as seguintes dependências:

npm install -D ts-loader typescript webpack webpack-cli

Precisamos configurar o TypeScript e Webpack:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES5",
    "module": "ES6",
    "moduleResolution": "Node",
    "allowJs": true,
    "outDir": "./dist/",
    "noImplicitAny": true
  }
}
// webpack.config.js
const path = require("path");

module.exports = {
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: ["ts-loader"],
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  mode: "development",
};

Também vamos criar dois scripts para nos auxiliar:

// package.json
...
"scripts": {
  "build:with-barrel": "webpack --entry ./src/with-barrel.ts",
  "build:without-barrel": "webpack --entry ./src/without-barrel.ts",
  "start": "node dist/bundle.js"
},
...

Após configurar o projeto, vamos incluir um log fora das funções nos utils/foo.ts, utils/bar.ts e utils/baz.ts:

// utils/foo.ts
export function foo() {
	console.log("foo!");
}
console.log("Compilou foo");

// utils/bar.ts
export function bar() {
	console.log("bar!");
}
console.log("Compilou bar");

// utils/baz.ts
export function baz() {
	console.log("baz!");
}
console.log("Compilou baz");

Depois alterar os arquivos without-barrel.ts e with-barrel.ts:

// without-barrel.ts
import { foo } from "./utils/foo";

foo();

// with-barrel.ts
import { foo } from "utils";

foo();

Compilando cada arquivo, temos os seguintes outputs:

Screenshot da execução do comando "npm run build:with-barrel && npm run start". O output tamanho do bundle é de 6.33 KiB. Exibe que todos arquivos "foo.ts", "bar.ts" e "baz.ts" foram compilados.
Com barrel files
Screenshot da execução do comando "npm run build:without-barrel && npm run start". O output tamanho do bundle é de 4.2 KiB. Exibe que somente o arquivo "foo.ts" foi compilado.

Sem barrel files

Comparando os resultados de utilizar e não utilizar barrel files, é possível notar algumas diferenças. Ao importar a função foo do barrel file e compilar a aplicação, as funções bar e baz foram incluídas no bundle. Chamamos isso de código morto.

Ao importar a função foo direto do seu módulo, não é incluído código morto. Também há redução no tamanho do bundle.

 

Webpack Tree shaking

Desde a versão 4 do Webpack, existe um recurso chamado Tree shaking. Consiste em remover o código morto do bundle. Podemos testar mantendo os barrel files e habilitando o tree shaking:

No package.json, devemos incluir:

// package.json
...
"sideEffects": false,
...

Compilando novamente:

Screenshot da execução do comando "npm run build:without-barrel && npm run start". O output tamanho do bundle é de 4.17 KiB. Exibe que somente o arquivo "foo.ts" foi compilado.

Podemos observar que não compilou o código morto, obtendo assim o mesmo resultado mantendo o barrel file. Ou seja, conseguimos o melhor dos dois mundos!


Em um cenário real

Um projeto com Next.js v12 e Jest foi utilizado como cobaia. Faremos um comparativo do tempo para executar os testes unitários, tempo de build e tamanho do bundle.

Infelizmente os testes com Tree shaking do Webpack são inconclusivos com essa versão do Next.js, inclusive existe uma issue aberta para isso. Nos testes houve aumento no tempo de execução teste e build, e no tamanho do bundle. Logo foi desconsiderado.

Tempo de testes

Foi reduzido em 45% o tempo da execução dos testes com Jest:

Test Suites: 105 passed, 105 total. Tests: 991 passed, 991 total. Time: 785.569 segundos.

Com barrel files
Test Suites: 105 passed, 105 total. Tests: 991 passed, 991 total. Time: 352.496 segundos.

Sem barrel files

Tempo de build

Houve uma pequena redução no tempo de build. Com barrel files passou de 6min 13seg para 5min 40seg sem barrel files.

Tamanho do bundle

Foi reduzido em 65% o tamanho do bundle comum em todas as páginas. Consequentemente também houve redução do tamanho do bundle de cada página. Na rota “/” foi reduzido 31%. Na “/pagina1” foi reduzido 28%. E por fim, na rota “/pagina2” foi reduzido 37%.

Com barrel files:

Sem barrel files:

Nova solução para Next.js: optimizePackageImports

A partir da versão 13.5 do Next.js, foi liberado uma opção chamada optimizePackageImports para solucionar o problema dos arquivos de barril. É possível configurar isso para os próprios arquivos de barril do projeto, entretanto, promete também resolver de forma automática para uma lista de bibliotecas comuns.

Conclusão

Os arquivos de barril são amados por facilitar e simplificar o código e a experiência de desenvolvimento. Como consequência em utilizar essa prática de forma desenfreada, pode trazer problemas sérios de performance, tempos de teste e build. Mas se bem configurado, é possível continuar utilizando sem problemas. Cada caso precisa ser avaliado para determinar seu uso.


Referências

Outras publicações