JavaScript モジュール バンドラは多くのことを行うことができますが、最も便利な機能の 1 つとして、コードベースに外部ライブラリを追加して使用する機能が挙げられます。モジュール バンドラは、コード内のインポートパスを読み取り、アプリケーション固有のコードとインポートしたライブラリのコードを組み合わせます(これをバンドルと言います)。
Firebase JavaScript モジュール方式 API のバージョン 9 以降は、モジュール バンドラの最適化機能と連動するように最適化されており、最終的なビルドに含まれる Firebase コードの量を減らすことができます。
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.
 */
ライブラリの未使用のコードを削除するこのプロセスは、ツリー シェイキングと呼ばれます。手作業によるコードの削除は、非常に手間がかかり作業ミスが発生しやすいものですが、モジュール バンドラはこの削除を自動的に行います。
JavaScript エコシステムには高品質なモジュール バンドラが多数あります。このガイドでは、webpack、Rollup、esbuild とともに Firebase を使用する方法について説明します。
始める
このガイドでは、開発環境に npm がインストールされていると想定しています。npm は依存関係(ライブラリ)のインストールと管理に使用されます。npm をインストールするには、Node.js をインストールします。Node.js をインストールすると、npm も自動的にインストールされます。
ほとんどの開発環境は Node.js をインストールすると適切にセットアップされます。ただし、環境をセットアップするときに多くの開発者が経験する一般的な問題があります。エラーが発生した場合は、環境に npm CLI が存在することと、適切な権限が設定されていることを確認してください。これにより、sudo コマンドで管理者としてパッケージをインストールする必要がなくなります。
package.json と Firebase のインストール
npm をインストールしたら、ローカル プロジェクトのルートに package.json ファイルを作成する必要があります。次の npm コマンドを使用してこのファイルを生成します。
npm init
ウィザードに沿って必要な情報を入力します。作成されたファイルは次のようになります。
{
  "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": {
  }
}
このファイルには多くの役割があります。このファイルは、モジュールのバンドルと JavaScript コードのビルド全般について把握しようとする場合に、理解する必要がある重要なファイルです。このガイドで重要なのは、"dependencies" オブジェクトです。このオブジェクトには、インストールされているライブラリと使用するバージョンの Key-Value ペアが含まれます。
依存関係を追加するには、npm install または npm i コマンドを使用します。
npm i firebase
npm i firebase を実行すると、package.json が更新され、Firebase が依存関係としてリストされます。
  "dependencies": {
    "firebase": "^9.0.0"
  },
キーはライブラリの名前で、値は使用するバージョンです。バージョンの値には柔軟性があり、値の範囲を指定できます。これは、セマンティック バージョニングまたは semver と呼ばれます。semver の詳細については、セマンティック バージョニングに関する npm のガイドをご覧ください。
ソースフォルダとビルドフォルダ
記述したコードはモジュール バンドラによって読み取られて処理され、新しいファイルまたはファイルセットとして出力されます。この 2 種類のファイルを区別することが重要です。モジュール バンドラが読み取って処理するコードは「ソース」コードと呼ばれます。出力されるファイルは、ビルドされたコードまたは「dist」(distribution: 配布)コードと呼ばれます。
コードベースの一般的な設定では、src というフォルダにソースコードを保存し、dist というフォルダにビルドされたコードを保存します。
- src
 |_ index.js
 |_ animations.js
 |_ datalist.js
- dist
 |_ bundle.js
上記のファイル構造の例では、index.js が animations.js と datalist.js をインポートしています。モジュール バンドラがソースコードを処理すると、dist フォルダに bundle.js ファイルが生成されます。bundle.js は、src フォルダにあるファイルと、インポートされるすべてのライブラリを組み合わせたものです。
Git などのソース管理システムを使用していて、メイン リポジトリにこのコードを保存する場合、通常 dist フォルダは無視します。
エントリ ポイント
すべてのモジュール バンドラには、エントリ ポイントという概念があります。アプリケーションはファイルのツリーと考えることができます。あるファイルが別のファイルのコードをインポートし、それが続いていきます。つまり、1 つのファイルがツリーのルートになります。このファイルはエントリ ポイントと呼ばれます。
前述のファイル構造の例に戻りましょう。
- 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);
});
src/index.js ファイルは、アプリケーションに必要なすべてのコードのインポートを開始するため、エントリ ポイントと見なされます。このエントリ ポイント ファイルは、モジュール バンドラがバンドル プロセスを開始するために使用されます。
Firebase と webpack を組み合わせて使用する
Firebase アプリと webpack の組み合わせに特別な構成は必要ありません。このセクションでは、一般的な webpack の構成について説明します。
最初の手順は、開発の依存関係として npm から webpack をインストールすることです。
npm i webpack webpack-cli -D
webpack.config.js という名前のファイルをローカル プロジェクトのルートに作成し、次のコードを追加します。
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',
};
次に、Firebase が依存関係としてインストールされるようにします。
npm i firebase
次に、コードベース内で Firebase を初期化します。次のコードは、エントリ ポイント ファイルで Firebase をインポートして初期化し、Firestore Lite を使用して「city」ドキュメントを読み込みます。
// 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(),
  };
}
次の手順では、webpack のビルドを実行する npm スクリプトを追加します。package.json ファイルを開き、次の Key-Value ペアを "scripts" オブジェクトに追加します。
  "scripts": {
    "build": "webpack --mode=development"
  },
webpack を実行してビルドフォルダを生成するには、次のコマンドを実行します。
npm run build
最後に、dist ビルドフォルダを確認します。ここには bundle.js という名前のファイルがあり、バンドルされたアプリケーションと依存関係コードがこのファイルに含まれます。
webpack のビルドを本番環境用に最適化する方法については、「mode」の構成設定に関する公式ドキュメントをご覧ください。
Firebase と Rollup を組み合わせて使用する
Firebase アプリと Rollup の組み合わせに特別な構成は必要ありません。このセクションでは、一般的な Rollup の構成について説明します。
最初の手順は Rollup とプラグインをインストールすることです。このプラグインは、npm でインストールされる依存関係にインポートをマッピングするのに使用されます。
npm i rollup @rollup/plugin-node-resolve -D
rollup.config.js という名前のファイルをローカル プロジェクトのルートに作成し、次のコードを追加します。
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()]
};
次に、コードベース内で Firebase を初期化します。次のコードは、エントリ ポイント ファイルで Firebase をインポートして初期化し、Firestore Lite を使用して「city」ドキュメントを読み込みます。
// 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(),
  };
}
次の手順では、rollup のビルドを実行する npm スクリプトを追加します。package.json ファイルを開き、次の Key-Value ペアを "scripts" オブジェクトに追加します。
  "scripts": {
    "build": "rollup -c rollup.config.js"
  },
rollup を実行してビルドフォルダを生成するには、次のコマンドを実行します。
npm run build
最後に、dist ビルドフォルダを確認します。ここには bundle.js という名前のファイルがあり、バンドルされたアプリケーションと依存関係コードがこのファイルに含まれます。
Rollup のビルドを本番環境用に最適化する方法については、本番環境のビルドのプラグインに関する公式ドキュメントをご覧ください。
Firebase と esbuild を組み合わせて使用する
Firebase アプリと esbuild の組み合わせに特別な構成は必要ありません。このセクションでは、一般的な esbuild の構成について説明します。
最初の手順は、開発の依存関係として esbuild をインストールすることです。
npm i esbuild -D
esbuild.config.js という名前のファイルをローカル プロジェクトのルートに作成し、次のコードを追加します。
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))
次に、コードベース内で Firebase を初期化します。次のコードは、エントリ ポイント ファイルで Firebase をインポートして初期化し、Firestore Lite を使用して「city」ドキュメントを読み込みます。
// 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(),
  };
}
次の手順では、esbuild を実行する npm スクリプトを追加します。package.json ファイルを開き、次の Key-Value ペアを "scripts" オブジェクトに追加します。
  "scripts": {
    "build": "node ./esbuild.config.js"
  },
最後に、dist ビルドフォルダを確認します。ここには bundle.js という名前のファイルがあり、バンドルされたアプリケーションと依存関係コードがこのファイルに含まれます。
esbuild を本番環境用に最適化する方法については、軽量化とその他の最適化に関する公式ドキュメントをご覧ください。