Parcel で HTML から環境変数にアクセスしたい

# TL;DR

- [Parcel](https://parceljs.org/) を使ってフロントエンド開発をしている人を対象とした内容です
- [posthtml-expressions](https://github.com/posthtml/posthtml-expressions) を使って locals に `process.env` を追加します
- `<span>{{ process.env.NODE_ENV }}</span>` のように環境変数にアクセスします
- JavaScript からは簡単にアクセスできますが、HTML からアクセスする方法があまりウェブ上になかったので残しておきます

# やりたいこと

- 一言で言うと、Parcel でテンプレートエンジンをサクッと使いたいのです
- 環境ごとに処理を切り替えたいケースは多々あります。JavaScript の文脈では、次のように環境変数にアクセスするのが一般的です
    - `if (process.env.NODE_ENV === 'PRODUCTION') // 本番ビルド時だけ行いたい処理`
    - `new SomeLibrary({ apiKey: process.env.API_KEY }) // API キー等を DEV/PROD で分ける`
- これと同じことを**ビルド時に HTML に直接書きたい**時があります
    - サーバーサイドで処理する場合は、何らかのテンプレートエンジンを使うのが一般的です
    - 一方フロントエンド開発（主に SPA として静的に HTML をサーブしたい場合）では、ビルド時に HTML を生成するのが一般的です
    - これは SSR する場合にも言えます。ビルド時の変数を利用するには、やはりビルド時に HTML を生成する必要があります
- Parcel はデフォルトで Babel などを用意してくれていますが、HTML テンプレートエンジンは自分で追加する必要があります
- 公式ドキュメントの HTML の項目をみると、[PostHTML](https://github.com/posthtml/posthtml) なるものが標準サポートされているそうなので、今回は PostHTML を利用してビルド時にテンプレートエンジンを動作させ、環境変数にアクセスできるようにします

# PostHTML

- PostCSS を知っている方なら、その HTML 版だと理解するのが早いです
- 要するに「HTML 変換器」と「HTML 変換器を作るためのプラグイン管理システム」が PostHTML です
    - Post の意味は pre と post の post であり、HTTP の POST ではありません
- Parcel には元々「アセット変換器」という役割があり、「アセット変換プラグイン管理システム」も有しています
    - これは PostHTML の存在意義とよく似ていますが、２つのエコシステムをどっちも使える分、ライブラリがたくさんあって便利という訳です

# posthtml-expressions

- https://github.com/posthtml/posthtml-expressions
- PostHTML プラグインの１つで、変数にアクセスできるだけでなく、条件分岐や繰り返しなどの制御構文、スコープ、モジュール化などを備えたユニークなテンプレートエンジンです
- `locals` オプションにキーバリューペアを与えることで、HTML からアクセス可能な変数を定義できます
- HTML 中に書かれた `{{ }}` の中に JavaScript 式を書くことが出来るため、それだけでも十分な表現力を備えていますが、うっかり書き過ぎると非常に読みづらい HTML になってしまうので注意が必要です
- もう１つ注意点として、このプラグインでは式の評価のために内部的に Node.js の [vm](https://nodejs.org/api/vm.html) の [runInContext](https://nodejs.org/api/vm.html#vm_vm_runincontext_code_contextifiedobject_options) を使っているため、Parcel ビルドとはコンテキストとは異なります
    - 要するに、**Node.js の変数は共有されていません**（そのために `locals` があります）
    - Parcel には `.env` に書かれた環境変数をデフォルトで `process.env` (JavaScript) に書き出してくれる機能がありますが、上記のような理由で `{{ process.env.NODE_ENV }}` と書いてもアクセスできません
        - うっかり書くと `process is not defined.` と言われるはずです
        - 解決方法を次に述べます

# やってみる

- まずは、 `npm i -D posthtml-expressions` を実行してプラグインをインストールします
- 次に、`posthtml.config.js` というファイルを `package.json` と同じフォルダに作り、次のように書きます  

```
module.exports = {
  plugins: {
    'posthtml-expressions': {
      locals: {
        process: {
          env: process.env
        }
      }
    }
  }
};
```

- 上記は Parcel の設定ファイルで、PostHTML プラグインに関する設定をまとめたものです。また、PostHTML を有効化する意味合いも含まれています
    - `'posthtml-expressions': { ... }` と言うのが posthtml-expressions の設定です
    - `process.env` の中身を全てコピーして、 HTML からアクセスできるようにしています
    - 説明は割愛しますので、詳しく知りたい方は README をご覧ください
    - https://github.com/posthtml/posthtml-expressions#options
- あとは、 `index.html` などに `<div>{{ process.env.NODE_ENV }}</div>` などと書いて、ビルドするだけです
- 生成後の HTML を確認すると、 `<div>development</div>` のように、文字列に置き換わっているのが分かると思います


# リスク

- 重要なことなので２回書きますが、このライブラリは式の評価に `vm.runInContext` を使っています
- VM がサンドボックスの役割を果たしているとは言え、例えば第三者が書いたコードをここで実行するなどと言ったことは大変危険なのでやめておくべきでしょう
    - やむおえずそうなってしまう場合は、もっとセキュアなテンプレートエンジンを選ぶべきでしょう。ただし、一般には表現力の高さとトレードオフの関係にあります（例えば、関数が使えないなど）
    - 複雑になってしまう場合は、諦めて JavaScript を使って動的に DOM に書き込むコードを書く方が賢明かも知れません