📦

React Native Expo の開発環境を Dev Containers で構築

2024/01/04に公開

React Native に入門しました。Expo を使った React Native の開発環境を Dev Containers で構築してみたので、今回はそちらの構築手順を紹介してみます。

Dev Containers とは

Docker コンテナを開発環境として利用する VS Code の拡張機能です。必要なライブラリやツール類、VS Code の設定や拡張をコンテナに詰め込むことにより、コンテナ内で開発を完結させることが可能です。また、こうした環境の定義を JSON ファイルに記述するのですが、その JSON ファイルを共有するだけで、メンバ間で同じ環境を構築することができます。詳しくはこちら

個人的に大変気に入っており、最近は個人開発でもチーム開発でもこればっかり使っています。

事前準備

  • VS Code をインストールしておいてください。
  • Docker Desktop をインストールしておいてください。
  • VSCode に Dev Containers 拡張 をインストールしておいてください。
  • お手元のスマートフォンに Expo Go をインストールしておいてください。

最終的なコード

構築手順に入る前に、先に最終的なコードを貼っておきます。
https://github.com/matsu3m/react-native-expo-devcontainer-template/tree/zenn-a31b425bfb25db

コンテナを立ち上げてみる

まずは最小限の構成でコンテナを立ち上げてみましょう。

作業用のディレクトリを作成し、そのディレクトリを VS Code で開いておいてください。

作業用ディレクトリのルートに .devcontainer という名前のディレクトリを作成し、その中に devcontainer.json という名前の JSON ファイルを作成します。

devcontainer.json に以下を記述し、コンテナのベースとなるイメージを指定します。

devcontainer.json
{
  "name": "Expo Dev Container",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20"
}

記述できたら、コマンドパレットを開き、Dev Containers: Reopen in Container を選択してコンテナを立ち上げてみましょう。

↓ 立ち上がった様子

Expo を導入する

create-expo-app で新規のプロジェクトをセットアップしましょう。ターミナルから以下のコマンドを実行してください。プロジェクト名は自由に設定していただいて OK です。

npx create-expo-app my-expo-project --template

--template オプションを付けておくとプロジェクトのテンプレートを選択できます。

今回は Expo Router を使用したかったため、Navigation (TypeScript) - File-based routing with TypeScript enabled を選択しました。

プロジェクトの作成が完了すると、my-expo-project ディレクトリの配下にファイル・ディレクトリ群が生成されます。

ここでは階層は必要無いため、my-expo-project ディレクトリ の中身をルートディレクトリに移動させてしまいます。併せて、app, assets, components, constants ディレクトリは src ディレクトリにまとめておきます (Navigation (TypeScript) 以外のテンプレートを選択した場合は、テンプレートに合わせて良い感じにしてください。)。

mv ./my-expo-project/* ./my-expo-project/.* .
rm -r ./my-expo-project/
mkdir src
mv app/ assets/ components/ constants/ src/

続いて、Expo Go からコンテナ内で立ち上げた expo サーバにアクセスできるように設定していきます。

Expo Go と expo サーバが立ち上がっているマシンは、同一のネットワークに接続している必要があります。しかし、今のままでは、expo サーバはコンテナの IP アドレスで立ち上がってしまうため、Expo Go からアクセスできない状態です。

そこで、expo サーバがホストマシンのローカル IP アドレスで立ち上がるように設定するとともに、expo サーバが使用する 8081 ポートをホストの 8081 ポートにマッピングすることで、この問題を解決します。

まず、.devcontainer 配下に .env ファイルを作成し、REACT_NATIVE_PACKAGER_HOSTNAME にホストマシンのローカル IP アドレスを指定します。

.env
REACT_NATIVE_PACKAGER_HOSTNAME=192.168.xxx.xxx

続いて、devcontainer.json を編集します。

devcontainer.json
{
  "name": "Expo Dev Container",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "forwardPorts": [
    8081
  ],
  "runArgs": [
    "-p=8081:8081",
    "--env-file",
    ".devcontainer/.env"
  ]
}

devcontainer.json の編集が完了したら、コンテナを再起動して設定を反映させましょう。コマンドパレットを開き、Dev Containers: Rebuild Container をクリックします。

コンテナの再起動が完了したら、expo サーバを立ち上げてみましょう。ターミナルから以下のコマンドを実行し、表示された QR コードをスマートフォンの Expo Go でスキャンしてください。

npm run start

無事サンプルアプリケーションが表示されれば成功です。

なお、プロジェクトのディレクトリ構成を変更したためにパス解決のエラーが出るかもしれません。その場合は、ターミナルに表示されたメッセージに従ってパスを修正してください。

linter と formatter を導入する

この後もファイルの編集が続くので、linter と formatter を導入しておきましょう。

ESLint + Prettier が定番かと思いますが、今回は Biome を使ってみます。簡単設定 & 爆速でとても良い感じ。

ターミナルから以下のコマンドを実行し、Biome のインストール & 設定ファイルの作成を実施します。

npm install --save-dev @biomejs/biome
npx @biomejs/biome init

biome.json が生成されれば OK です。今回は、デフォルトの設定に、インデントの設定と横幅の設定を追加し、以下のようにしてみました。

biome.json
{
  "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 120
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "organizeImports": {
    "enabled": true
  }
}

続いて、VSCode の拡張機能を導入しましょう。

devcontainer.json の customizations > vscode > extensions にて、コンテナにインストールする拡張機能を指定することができます。

devcontainer.json を編集し、biomejs.biome を追加します。ついでに、私がよく使う拡張機能を加えています。好みに応じて修正していただければと思います。

devcontainer.json
{
  "name": "Expo Dev Container",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "forwardPorts": [8081],
  "runArgs": ["-p=8081:8081", "--env-file", ".devcontainer/.env"],
  "customizations": {
    "vscode": {
      "extensions": [
        "biomejs.biome",
        "eamodio.gitlens",
        "editorconfig.editorconfig",
        "streetsidesoftware.code-spell-checker",
        "VisualStudioExptTeam.vscodeintellicode"
      ]
    }
  }
}

加えて、Biome の設定を追記しておきます。

devcontainer.jsoncustomizations > vscode > settings にて、.vscode/settings.json に記述するような設定を追加することができます。

devcontainer.json
{
  "name": "Expo Dev Container",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "forwardPorts": [8081],
  "runArgs": ["-p=8081:8081", "--env-file", ".devcontainer/.env"],
  "customizations": {
    "vscode": {
      "extensions": [
        "biomejs.biome",
        "eamodio.gitlens",
        "editorconfig.editorconfig",
        "streetsidesoftware.code-spell-checker",
        "VisualStudioExptTeam.vscodeintellicode"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
          "quickfix.biome": true,
          "source.organizeImports.biome": true
        },
        "[javascript]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[javascriptreact]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[typescript]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[typescriptreact]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[json]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[jsonc]": {
          "editor.defaultFormatter": "biomejs.biome"
        }
      }
    }
  }
}

devcontainer.json の編集が完了したら、Rebuild Container をクリックし、コンテナを再起動しましょう。再起動が完了すると、Biome の VSCode 拡張が有効になっており、ファイルを保存すると自動で整形が働くようになっているはずです。devcontainer.jsonbiome.json で試してみてください。

この時点で一旦、既存のファイルに linter と formatter を適用しておきましょう。ターミナルから以下のコマンドを実行してください。

npx @biomejs/biome check --apply-unsafe ./*

私が試した時は dangerouslySetInnerHTML を使用している箇所のみ自動での修正ができずエラーが出てしまいましたが、ここでは一旦スルーします。

ついでに、EditorConfig 拡張用の設定ファイル .editorconfig をルートディレクトリに作成しておきます。

.editorconfig
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

パッケージのインストールが自動で走るようにする

コンテナ起動時に npm install が実行されるようにしておきましょう。

まず、devcontainer.json を編集し、postCreateCommand を追記します。ここで指定したスクリプトが、コンテナ起動後に実行されることとなります。

devcontainer.json
{
  "name": "Expo Dev Container",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "postCreateCommand": "./.devcontainer/postCreateCommand.sh",
  "forwardPorts": [8081],
  "runArgs": ["-p=8081:8081", "--env-file", ".devcontainer/.env"],
  "customizations": {
    "vscode": {
      "extensions": [
        "biomejs.biome",
        "eamodio.gitlens",
        "editorconfig.editorconfig",
        "streetsidesoftware.code-spell-checker",
        "VisualStudioExptTeam.vscodeintellicode"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
          "quickfix.biome": true,
          "source.organizeImports.biome": true
        },
        "[javascript]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[javascriptreact]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[typescript]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[typescriptreact]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[json]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[jsonc]": {
          "editor.defaultFormatter": "biomejs.biome"
        }
      }
    }
  }
}

続いて、postCreateCommand.sh を作成しましょう。

postCreateCommand.sh
#/bin/bash

npm install

作成できたら、ターミナルから以下のコマンドを実行し、postCreateCommand.sh に実行権限を付与してください。

chmod +x .devcontainer/postCreateCommand.sh

完了したら、Rebuild Container をクリックし、コンテナを再起動しておきます。

ディスクのパフォーマンスを改善する

Dev Containers では通常、ファイルをローカルに保存し、それらをコンテナに対してバインドするような形式をとっています。こうした方式で node_modules をバインドしていると、

  • パッケージのインストール
  • Linting, Formatting
  • 開発用サーバの起動

といった動作が非常に緩慢となり、大変ストレスです。そこで、名前付きボリュームを利用することでこの問題を解決します (詳細を知りたい方はこちらをご参照ください)。

devcontainer.json にマウントの設定を追記します。

devcontainer.json
{
  "name": "Expo Dev Container",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "postCreateCommand": "./.devcontainer/postCreateCommand.sh",
  "mounts": ["source=node_modules_${devcontainerId},target=${containerWorkspaceFolder}/node_modules,type=volume"],
  "forwardPorts": [8081],
  "runArgs": ["-p=8081:8081", "--env-file", ".devcontainer/.env"],
  "customizations": {
    "vscode": {
      "extensions": [
        "biomejs.biome",
        "eamodio.gitlens",
        "editorconfig.editorconfig",
        "streetsidesoftware.code-spell-checker",
        "VisualStudioExptTeam.vscodeintellicode"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
          "quickfix.biome": true,
          "source.organizeImports.biome": true
        },
        "[javascript]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[javascriptreact]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[typescript]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[typescriptreact]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[json]": {
          "editor.defaultFormatter": "biomejs.biome"
        },
        "[jsonc]": {
          "editor.defaultFormatter": "biomejs.biome"
        }
      }
    }
  }
}

併せて、postCreateCommand.sh を以下のように編集します。

postCreateCommand.sh
#/bin/bash

sudo chown node node_modules
npm install

完了したら、Rebuild Container をクリックし、コンテナを再起動しておきます。

パスエイリアスを導入する

最後に、src ディレクトリへのエイリアスとして @ を設定します。

まず、tsconfig.jsoncompilerOptions

"baseUrl": ".",
"paths": {
  "@/*": ["./src/*"]
}

を追記します。

tsconfig.json
{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": [
    "**/*.ts",
    "**/*.tsx",
    ".expo/types/**/*.ts",
    "expo-env.d.ts"
  ]
}

併せて、babel.config.jsplugins

[
  "module-resolver",
  {
    root: ["."],
    alias: { "@": "./src" },
    extensions: [".ts", ".tsx", ".js", ".ios.js", ".android.js"],
  },
],

を追記します。

babel.config.js
module.exports = (api) => {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      // Required for expo-router
      "expo-router/babel",
      [
        "module-resolver",
        {
          root: ["."],
          alias: { "@": "./src" },
          extensions: [".ts", ".tsx", ".js", ".ios.js", ".android.js"],
        },
      ],
    ],
  };
};

これで、src ディレクトリを基準にパスを指定できるようになりました。デフォルトでは全て相対パスになっているため、必要に応じて修正しておきましょう。

まとめ

本記事では、Dev Containers で React Native Expo の開発環境を構築する手順を紹介しました。筆者は React Native 初心者ですので、誤りや改善点があればご指摘いただけると大変助かります。

最後にもう一度、本記事で構築した環境のリポジトリを貼っておきます。
https://github.com/matsu3m/react-native-expo-devcontainer-template/tree/zenn-a31b425bfb25db

最後までお付き合いいただきありがとうございました。良き Dev Containers ライフを👋

Discussion