.envに書いてあるものはundefinedにならないで欲しい
.env に書いてあるものは undefined にならないで欲しい
と、一度は思ったことがあると思います。
それで、 process.env.API_KEY!
や import.meta.env.API_BASE_URL as string
的なキャストを書いて消耗していたり。
というかそもそも import.meta.env
が長いです。
あと、TYPO してても気付けないという問題もあります。
実は SvelteKit では、この問題が解決されています。参考 (感動〜 SvelteKit さんって細かいところまで気が効くのね!)
今回は、Next.js や Astro などのフレームワークでも汎用的に使える typedotenv
を紹介します。
typedotenv とは
私が作ったパッケージ群です。
Playground が用意してあるので触ればなんとなくわかると思いますが、 .env
ファイルを読んで、かんたんな TypeScript(JavaScript) コードを生成するだけのものです。
例えば
API_KEY=qwertyuiop
NEXT_PUBLIC_API_ENDPOINT=http://example.com/
という .env
ファイルがあれば
/* Auto generated by typedotenv */
if (typeof process.env.API_KEY !== 'string') throw new Error('API_KEY is not defined in .env');
export const API_KEY = process.env.API_KEY;
if (typeof process.env.NEXT_PUBLIC_API_ENDPOINT !== 'string') throw new Error('NEXT_PUBLIC_API_ENDPOINT is not defined in .env');
export const NEXT_PUBLIC_API_ENDPOINT = process.env.NEXT_PUBLIC_API_ENDPOINT;
という TypeScript のコードが吐かれます。
やってること言えば、環境変数の値が string
であることを保証しつつ export しているだけです。
つかいかた
npm i -D unplugin-typedotenv
して、各種フレームワークのプラグインとして挿入します。
Next.js
const typedotenv = require('unplugin-typedotenv/webpack') // 型が欲しければ `.default` をつける
module.exports = {
webpack: (config) => {
config.plugins.push(
typedotenv({ output: 'src/env.ts', env: process.env.NODE_ENV })
);
return config;
},
};
Vite 系
import typedotenv from 'unplugin-typedotenv/vite'
import { defineConfig } from 'vite'
export default defineConfig(({ mode }) => {
plugins: [
typedotenv({ output: 'src/env.ts', env: mode, envObject: 'import.meta.env' })
],
})
あとは、 .env.development
などから src/env.ts
などにコードが生成されます。
もちろんホットリロードにも対応しており、 .env
ファイルを編集したら再出力されます。
もちろん、フレームワーク以外でも使えるように CLI が用意してあります。
npm i -D @typedotenv/cli
して package.json
に npm-scripts を書いて使えます。
{
"scripts": {
"generate": "typedotenv generate src/env.ts"
}
}
@typedotenv/core
ではコード生成部分だけが生えていますが、紹介は省略します。
.env のバリデーション
使うことはあまりないかもしれませんが、「特定の変数名しか許さない」、「特定の変数名は含めてはいけない」、「特定の変数名を含める必要がある」、「この変数はこういう値を持つべき」といったチェックもできます。
それぞれ allowList(--allow)
/ denyList(--deny)
/ required(--required)
/ patterns
(現状 API 経由のみ) です。(括弧内は CLI オプション)
CI 文脈の開発/本番差異チェック
.env.development
には設定してあるけど .env.production
には設定してない変数があると事故る可能性がある上、気付きづらいです。
@typedotenv/cli
では先のバリデーションもですが、環境差異判定にも使える typedotenv check
コマンドが用意しています。
開発時には .env.development
を利用しつつ、 CI で typedotenv check -e production
などで .env.production
と比較させることで、変数の過不足がないかを判定できます。
その他オプション
先に紹介した例では、ランタイムの型チェックで絞り込みをしていましたが、本番環境では仮に undefined
でも throw してほしくないことがあると思います。
その為、ランタイム型チェックを無効化する disableRuntimeTypeCheck(--disable-type-check)
が生えてます。
そうすると今度は、 string | undefined
で困るというケースもあるかもしれません。
その為、型アサーションを付与する enableTypeAssertion(--enable-type-assertion)
が生えてます。
双方を有効にすると以下のようになります。
/* Auto generated by typedotenv */
export const API_KEY = process.env.API_KEY as string;
export const NEXT_PUBLIC_API_ENDPOINT = process.env.NEXT_PUBLIC_API_ENDPOINT as string;
他にも、Vite の例でしれっと使っている envObject(--env-object)
では process.env
以外のオブジェクトから環境変数を取得したり、 prefix
(現状 API 経由のみ) で冒頭コメント部分を /* eslint-ignore */
などの任意の文字列に変更できるなどがあります。
unplugin とは
最後に、今回のパッケージの unplugin-typedotenv
で使われている unplugin
について紹介します。
unplugin
は、Vue.js や Vite のコントリビュータである Anthony Fu 氏がメインで開発している、統一プラグインシステムです。
webpack や Vite にはプラグインシステムがありますが、API が統一されておらず、プラグイン開発者としては両方対応するのは面倒なものでした。(ちなみに Vite のプラグインは Rollup のプラグインシステムが基本になっており高い互換性があります)
そこで、 Rollup のプラグイン API をベースに1つのコードベースで Webpack や Vite にも対応できるようにしたのが unplugin (unified plugin) というわけです。
おわりに
typedotenv という .env
ファイルから TypeScript コードを生成するパッケージ群を紹介しました。
3 日ほどで書いたのでテストが不足しています。Issue や PR、Discussion お待ちしてます。
Discussion