Angularのindex.htmlを環境ごとに差し替える

# はじめに

この記事では、Angular で配信する index.html を環境に応じて変更する方法について紹介します。

なお、ここで紹介するのはあくまでも index.html そのものを変更する方法なので、Angular 自体の処理によって index.html 内の値を更新する方法については対象外としています。また、記事中で述べるプロジェクトの構成は Angular CLI により生成されたことを前提としており、 Angular のバージョンは 7.x を対象としています。

# 方法1. fileReplacement を利用する

最も簡単でシンプルな方法は、`angular.json` の中で指定することができる `fileReplacement` を利用することです。これはビルド実行時に所定のファイルを差し替える機能であり、Angular CLI でプロジェクトを作成した場合は、`environment.ts`を差し替えるために利用されています。それでは `fileReplacement`を利用した構成方法を見てみましょう。

## 環境ごとの index.html を追加

次のように、それぞれの環境に合わせた index.html を `src` ディレクトリに配置します。

```html:src/index.prod.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>App (Production)</title>
    ...
  </head>
  <body>
    これはProductionです
  </body>
</html>
```

```html:src/index.staging.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>App (Staging)</title>
    ...
  </head>
  <body>
    これはStagingです

    <strong>これはテスト環境です！</strong>
  </body>
</html>
```

## `fileReplacements` への値の追加

`angular.json` の `configurations` で定義されている各環境の設定に、index.html を差し替えるための`fileReplacement` の定義を追加します。

```json:angular.json
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                },
                {
                  "replace": "src/index.html",
                  "with": "src/index.prod.html"
                }
              ],
              ...
            "staging": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                },
                {
                  "replace": "src/index.html",
                  "with": "src/index.staging.html"
                }
              ],
              ..
```

以上の設定により、ビルド実行時に index.html が所定のファイルへと差し替えられ、環境に応じた index.html が使用されるようになります。

この方法は最もシンプル、かつ簡単な方法ですが、それぞれの index.html に同一の内容を多く記述しなければならず、DRY な構成とするのが困難です。例えば `head` の中に `meta` 要素を追加しようとすると、その変更をそれぞれの index.html に対して行わなければいけません。

そこで、次のセクションでは、1つのファイルをベースとして各環境ごとの index.html を生成する方法について説明します。

# 方法2. EJS でindex.htmlを生成する

[EJS](https://ejs.co/) は、JavaScriptの構文により値を埋め込むことができるテンプレートの仕組みです。これを利用することで、index.htmlに埋め込んだプレースホルダーに環境の値を埋め込んだり、あるいは環境に応じて要素を表示するかどうかを決定することができます。

それでは、EJSを用いたindex.htmlの生成方法を確認してみましょう。

## ejsのインストール

次のコマンドを実行して `ejs-cli` をインストールします。

```sh
npm i -D ejs-cli
```

## index.ejsの作成

`src/ejs` ディレクトリを作成し、その中に index.html を生成するためのEJSテンプレート `index.ejs` を 格納します。

```html:src/ejs/index.ejs
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>App (<%= environment %>)</title>
    ...
  </head>
  <body>
    <%= body %>

    <% if (environment === 'staging') { %>
    <strong>これはテスト環境です！</strong>
    <% } %>
  </body>
</html>
```

## EJSの設定値を定義するjsonの作成

`src/ejs` に各環境の EJS で使用するための値を定義するjsonファイルを作成します。

```json:src/ejs/options.prod.json
{
  "environment": "production",
  "body": "これはProductionです"
}
```

```json:src/ejs/options.staging.json
{
  "environment": "staging",
  "body": "これはStagingです"
}
```

## EJSビルド用コマンドの追加

`package.json` に EJS のテンプレートをビルドするためのコマンドを追加します。

```json:package.json
  "scripts": {
    "build": "npm run ejs:prod & ng build",
    "build:staging": "npm run ejs:staging & ng build",
    "ejs:staging": "ejs-cli --base-dir src/ejs index.ejs --out src --options src/ejs/options.staging.json",
    "ejs:prod": "ejs-cli --base-dir src/ejs index.ejs --out src --options src/ejs/options.prod.json"
  }
```

上記の `ejs-cli` が ejs ファイルをビルドするためのコマンドであり、 `--options` によって、どのファイルからオプションとなる値を取得するかを指定しています。これにより、 `npm run build` 、もしくは `npm run build:staging` を実行することで、指定した環境に合わせた index.html が生成されます。

# 最後に
この記事では、 fileReplacement の使用、EJS の使用という2つの方法によって index.html を差し替える方法について紹介しました。他にも [cheerioを使用する方法](https://github.com/angular/angular-cli/issues/7824#issuecomment-332498000) などがあるようですが、EJSの方が保守性の面で優れるのではないかと考えて、ここでは紹介を省きました。興味があれば、こちらの方法を調べるのも良いでしょう。

# 参考

- [Is there a way to conditionally include scripts in index.html as in analytics?](https://github.com/angular/angular-cli/issues/4451)
- [how do you dynamically update index.html via the angular CLI build?](https://github.com/angular/angular-cli/issues/7824)