Como usar os bundlers de módulo com o Firebase

Os bundlers de módulo JavaScript podem fazer muitas coisas, mas um dos recursos mais úteis é a capacidade de adicionar e usar bibliotecas externas na base de código. Os bundlers de módulo leem caminhos de importação no código e combinam (empacotam) o código específico do aplicativo com o código da biblioteca importada.

A partir da versão 9, a API modular do Firebase para JavaScript é otimizado para funcionar com os recursos de otimização de bundlers de módulo a fim de reduzir a quantidade de código do Firebase na versão final.

import { initializeApp } from 'firebase/app';
import { getAuth, onAuthStateChanged, getRedirectResult } from 'firebase/auth';

const firebaseApp = initializeApp({ /* config */ });
const auth = getAuth(firebaseApp);
onAuthStateChanged(auth, user => { /* check status */ });

/**
 * getRedirectResult is unused and should not be included in the code base.
 * In addition, there are many other functions within firebase/auth that are
 * not imported and therefore should not be included as well.
 */

Esse processo de eliminação de código não utilizado de uma biblioteca é conhecido como tree shaking. Seria muito demorado e propenso a erros remover esse código manualmente, mas os bundlers de módulo podem automatizar essa remoção.

Há muitos bundlers de módulo de alta qualidade no ecossistema JavaScript. O foco deste guia é o uso do Firebase com o webpack, Rollup e esbuild.

Primeiros passos

Este guia requer que o npm esteja instalado no ambiente de desenvolvimento. O npm é usado para instalar e gerenciar dependências (bibliotecas). Para instalar o npm, instale o Node.js (em inglês), que inclui o npm automaticamente.

A maioria dos desenvolvedores é configurada corretamente depois de instalar o Node.js. No entanto, muitos desenvolvedores enfrentam problemas comuns ao configurar o ambiente. Se ocorrer algum erro, verifique se o ambiente tem a CLI npm e se você tem as permissões adequadas configuradas para não ter que instalar pacotes como administrador com o comando sudo.

package.json e instalação do Firebase

Depois de instalar o npm, você precisará criar um arquivo package.json na raiz do projeto local. Gere esse arquivo com o comando npm a seguir:

npm init

Isso vai guiar você por um assistente que fornece as informações necessárias. Depois que o arquivo for criado, ele será semelhante a isto:

{
  "name": "your-package-name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {

  }
}

Esse arquivo é responsável por muitas coisas diferentes. Ele é um arquivo importante que você precisa conhecer se quiser saber mais sobre empacotamento de módulos e criação de código JavaScript em geral. O importante neste guia é o objeto "dependencies". Esse objeto terá um par de chave-valor da biblioteca instalada e a versão que ela está usando.

A adição de dependências é feita pelo comando npm install ou npm i.

npm i firebase

Quando npm i firebase for executado, o processo de instalação atualizará package.json para listar o Firebase como uma dependência:

  "dependencies": {
    "firebase": "^9.0.0"
  },

A chave é o nome da biblioteca, e o valor é a versão a ser usada. O valor da versão é flexível e aceita um intervalo de valores. Isso é conhecido como controle de versões semântico ou semver. Para saber mais sobre o semver, consulte o guia do npm sobre o controle de versões semântico.

Pastas de origem vs. de criação

O código que você escreve é lido e processado por um bundler de módulo e enviado como um novo arquivo ou conjunto de arquivos. É importante separar esses dois tipos de arquivos. O código que os bundlers de módulo leem e processam é conhecido como código "fonte". Os arquivos de saída são conhecidos como código criado ou "dist" (distribuição).

Uma configuração comum em bases de código é armazenar o código-fonte em uma pasta chamada src e o código criado em uma pasta chamada dist.

- src
 |_ index.js
 |_ animations.js
 |_ datalist.js


- dist
 |_ bundle.js

No exemplo de estrutura de arquivo acima, index.js importa animations.js e datalist.js. Quando um bundler de módulo processa o código-fonte, ele produz o arquivo bundle.js na pasta dist. O bundle.js é uma combinação dos arquivos na pasta src e de qualquer biblioteca na importação.

Se você estiver usando sistemas de controle de origem, como o Git, é comum ignorar a pasta dist ao armazenar esse código no repositório principal.

Pontos de entrada

Os bundlers de módulo têm um conceito de um ponto de entrada. Pense no aplicativo como uma árvore de arquivos. Um arquivo importa código de outro, e assim por diante. Isso significa que um arquivo será a raiz da árvore. Esse arquivo é conhecido como ponto de entrada.

Vamos rever o exemplo anterior da estrutura de arquivos.

- src
 |_ index.js
 |_ animations.js
 |_ datalist.js


- dist
 |_ bundle.js
// src/index.js
import { animate } from './animations';
import { createList } from './datalist';

// This is not real code, but for example purposes only
const theList = createList('users/123/tasks');
theList.addEventListener('loaded', event => {
  animate(theList);
});

O arquivo src/index.js é considerado o ponto de entrada porque inicia as importações de todos os códigos necessários para o aplicativo. Esse arquivo de ponto de entrada é usado pelos bundlers de módulo para iniciar o processo de agrupamento.

Como usar o Firebase com o webpack

Não há configuração específica necessária para apps do Firebase e webpack. Esta seção abrange uma configuração geral do webpack (em inglês).

A primeira etapa é instalar o webpack pelo npm como uma dependência de desenvolvimento.

npm i webpack webpack-cli -D

Crie um arquivo na raiz do projeto local chamado webpack.config.js e adicione o seguinte código.

const path = require('path');

module.exports = {
  // The entry point file described above
  entry: './src/index.js',
  // The location of the build folder described above
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  // Optional and for development only. This provides the ability to
  // map the built code back to the original source format when debugging.
  devtool: 'eval-source-map',
};

Em seguida, verifique se você instalou o Firebase como dependência.

npm i firebase

Depois, inicialize o Firebase na base de código. O código a seguir importa e inicializa o Firebase em um arquivo de ponto de entrada e usa o Firestore Lite para carregar um documento de "cidade".

// src/index.js
import { initializeApp } from 'firebase/app';
import { getFirestore, doc, getDoc } from 'firebase/firestore/lite';

const firebaseApp = initializeApp({ /* config */ });
const db = getFirestore(firebaseApp);

async function loadCity(name) {
  const cityDoc = doc(db, `cities/${name}`);
  const snapshot = await getDoc(cityDoc);
  return {
    id: snapshot.id,
    ...snapshot.data(),
  };
}

A próxima etapa é adicionar um script de npm para executar o build do webpack. Abra o arquivo package.json e adicione o seguinte par de chave-valor ao objeto "scripts".

  "scripts": {
    "build": "webpack --mode=development"
  },

Para executar o webpack e gerar a pasta de versão, execute o seguinte comando.

npm run build

Por fim, verifique a pasta da versão dist. Ela vai ter um arquivo chamado bundle.js que contém o aplicativo em pacote e o código de dependência.

Para mais informações sobre como otimizar a versão do webpack para produção, consulte a documentação oficial sobre a configuração do "modo" (em inglês).

Como usar o Firebase com o Rollup

Não há configuração específica necessária para apps do Firebase e Rollup. Esta seção abrange uma configuração geral do Rollup.

A primeira etapa é instalar o Rollup e um plug-in usado para mapear importações em dependências instaladas com o npm.

npm i rollup @rollup/plugin-node-resolve -D

Crie um arquivo na raiz do projeto local chamado rollup.config.js e adicione o seguinte código.

import { nodeResolve } from '@rollup/plugin-node-resolve';

export default {
  // the entry point file described above
  input: 'src/index.js',
  // the output for the build folder described above
  output: {
    file: 'dist/bundle.js',
    // Optional and for development only. This provides the ability to
    // map the built code back to the original source format when debugging.
    sourcemap: 'inline',
    // Configure Rollup to convert your module code to a scoped function
    // that "immediate invokes". See the Rollup documentation for more
    // information: https://rollupjs.org/guide/en/#outputformat
    format: 'iife'
  },
  // Add the plugin to map import paths to dependencies
  // installed with npm
  plugins: [nodeResolve()]
};

Depois, inicialize o Firebase na base de código. O código a seguir importa e inicializa o Firebase em um arquivo de ponto de entrada e usa o Firestore Lite para carregar um documento de "cidade".

// src/index.js
import { initializeApp } from 'firebase/app';
import { getFirestore, doc, getDoc } from 'firebase/firestore/lite';

const firebaseApp = initializeApp({ /* config */ });
const db = getFirestore(firebaseApp);

async function loadCity(name) {
  const cityDoc = doc(db, `cities/${name}`);
  const snapshot = await getDoc(cityDoc);
  return {
    id: snapshot.id,
    ...snapshot.data(),
  };
}

A próxima etapa é adicionar um script de npm para executar a versão do rollup completa. Abra o arquivo package.json e adicione o seguinte par de chave-valor ao objeto "scripts".

  "scripts": {
    "build": "rollup -c rollup.config.js"
  },

Para executar o rollup e gerar a pasta de build, execute o seguinte comando.

npm run build

Por fim, verifique a pasta do build dist. Ela vai ter um arquivo chamado bundle.js que contém o aplicativo em pacote e o código de dependência.

Para mais informações sobre como otimizar a versão do Rollup para produção, consulte a documentação oficial sobre plug-ins para versões de produção (em inglês).

Como usar o Firebase com o esbuild

Não há configuração específica necessária para apps do Firebase e esbuild. Esta seção abrange uma configuração geral de esbuild.

A primeira etapa é instalar o esbuild como uma dependência de desenvolvimento.

npm i esbuild -D

Crie um arquivo na raiz do projeto local chamado esbuild.config.js e adicione o seguinte código.

require('esbuild').build({
  // the entry point file described above
  entryPoints: ['src/index.js'],
  // the build folder location described above
  outfile: 'dist/bundle.js',
  bundle: true,
  // Replace with the browser versions you need to target
  target: ['chrome60', 'firefox60', 'safari11', 'edge20'],
  // Optional and for development only. This provides the ability to
  // map the built code back to the original source format when debugging.
  sourcemap: 'inline',
}).catch(() => process.exit(1))

Depois, inicialize o Firebase na base de código. O código a seguir importa e inicializa o Firebase em um arquivo de ponto de entrada e usa o Firestore Lite para carregar um documento de "cidade".

// src/index.js
import { initializeApp } from 'firebase/app';
import { getFirestore, doc, getDoc } from 'firebase/firestore/lite';

const firebaseApp = initializeApp({ /* config */ });
const db = getFirestore(firebaseApp);

async function loadCity(name) {
  const cityDoc = doc(db, `cities/${name}`);
  const snapshot = await getDoc(cityDoc);
  return {
    id: snapshot.id,
    ...snapshot.data(),
  };
}

A próxima etapa é adicionar um script de npm para executar o esbuild. Abra o arquivo package.json e adicione o seguinte par de chave-valor ao objeto "scripts".

  "scripts": {
    "build": "node ./esbuild.config.js"
  },

Por fim, verifique a pasta da versão dist. Ela vai ter um arquivo chamado bundle.js que contém o aplicativo em pacote e o código de dependência.

Para mais informações sobre como otimizar o esbuild para produção, consulte a documentação oficial sobre a minificação e outras otimizações (em inglês).