きっかけ:Jest でエラーが出た
Vite プロジェクトに Jest でテストを追加しようとしたとき、こんなエラーが出ました。
SyntaxError: Cannot use 'import.meta' outside a module
原因はすぐわかりました。Jest は Babel を使ってコードを CommonJS に変換してから実行するため、ES Modules の構文である import.meta を解釈できないのです。解決策は babel-preset-vite を入れるだけです。
npm install -D babel-preset-vite
// babel.config.js
module.exports = {
presets: ['babel-preset-vite'],
};
これで import.meta.env.VITE_XXX が process.env.VITE_XXX に変換されて動くようになります。
ただ、この「process.env に変換される」という部分が気になりました。そもそも import.meta.env って何なのか? .env ファイルって何なのか?——調べてみると、「言語仕様」と「ツールの慣習」が混在していて面白かったので整理します。
import.meta.env の正体
import.meta は ES Modules 標準
import.meta は ES2020 で標準化されたオブジェクトで、実行中のモジュールに関するメタ情報を保持します。標準仕様で定義されているプロパティは import.meta.url(現在のモジュールの URL)だけです。
console.log(import.meta.url);
// → "file:///path/to/your/module.js"
import.meta.env はツールが追加した慣習
import.meta.env は ES Modules 標準にはありません。これはビルドツールが「ここに環境変数を注入しよう」と決めた慣習です。
採用しているツールは Vite だけではありません:
| ツール | 採用状況 |
|---|---|
| Vite | import.meta.env.VITE_XXX |
| WMR(Preact 製) | 同様の API を持つ(Vite より先という説も) |
| Snowpack | 採用していた(現在はメンテナンス終了) |
| Astro / SvelteKit / Nuxt 3 | Vite ベースなので同様に使える |
Vite が有名にしたのは確かですが、「Vite 独自」というより「フロントエンドのビルドツールが広めた慣習」と理解するのが正確です。
ブラウザに「環境変数」はない
重要な点として、ブラウザには環境変数という概念がありません。import.meta.env.VITE_API_URL は、ブラウザ上では実際にはビルド時に Vite が文字列として埋め込んだものです。
// 開発者が書くコード
const url = import.meta.env.VITE_API_URL;
// Vite がビルド時に変換した結果(イメージ)
const url = "https://api.example.com";
import.meta.env は「ブラウザで動く環境変数アクセス」のように見えますが、実態は「ビルドツールによる文字列の差し込み」です。
「環境変数」と .env の正体
環境変数は OS の仕組み
「環境変数」は JavaScript でも Node.js でもなく、OS(Unix/Windows)が提供する仕組みです。プロセスを起動するときに OS がキーと値のペアを渡せます。
MY_API_KEY=secret node index.js
Node.js ではこれを process.env.MY_API_KEY で読めます。Go なら os.Getenv("MY_API_KEY")、Swift なら ProcessInfo.processInfo.environment["MY_API_KEY"]——どの言語でも OS の環境変数を読む API が用意されています。
.env ファイルは「dotenv」が生んだ慣習
.env ファイルは言語の仕様でも OS の仕様でもなく、2013 年頃に登場した dotenv(Node.js ライブラリ)が作った慣習です。
「毎回シェルで環境変数を設定するのは面倒なので、.env ファイルに書いておいて自動で読み込もう」というアイデアが広まり、Vite・webpack・Next.js・Laravel など多くのフレームワークが後追いで内蔵しました。
dotenv は他言語にも「輸入」された
この慣習が便利だったため、他の言語にも同名・類似のライブラリが生まれました。
| 言語 | 標準の環境変数アクセス | .env 対応ライブラリ |
|---|---|---|
| JavaScript (Node.js) | process.env.KEY |
dotenv(本家) |
| Python | os.environ["KEY"] |
python-dotenv |
| Swift (iOS) |
Bundle.main.object(forInfoDictionaryKey: "KEY")※xcconfigで設定する場合 |
SwiftDotenv など |
ただし、言語によってスタンスは異なります。
Swift の場合、iOS アプリにはそもそも環境変数という概念がほぼありません。
環境変数が使えるのは macOS のコマンドラインツールやサーバーサイド Swift(Vapor など)に限られます。.env ライブラリも存在はしますが、採用率は低めです。
※そもそもの話ですが、iOSアプリはバイナリが配布されるわけですから、APIキーなど秘匿されるべき情報をクライアントサイドに置くような設計をしてはいけません。
Python は python-dotenv がかなり普及しており、Django・FastAPI などのフレームワークとセットで使われることが多いです。
Node.js 自体も .env に追いついてきた
Node.js v20.6 からは dotenv ライブラリなしに .env を読めるようになりました。
node --env-file=.env index.js
「慣習がデファクトスタンダードになり、ランタイム本体が取り込んだ」という典型的な例です。
まとめ
OS → 環境変数という仕組みを提供
Node.js → OS の環境変数を process.env で読めるようにした
dotenv → .env ファイルを process.env に追加する慣習を作った(JS 発)
各 FW → .env 対応を内蔵、デファクトスタンダード化
他言語 → 慣習を「輸入」して各自 dotenv 系ライブラリを作った
-
import.meta.envは ES 標準にはない。Vite をはじめ複数のビルドツールが採用した慣習 - ブラウザには環境変数がないため、
import.meta.envはビルド時の文字列埋め込みに過ぎない -
.envファイルは Node.js のdotenvが生んだ慣習で、言語仕様でも OS 仕様でもない - Go のように「
.envはローカル開発だけ、本番は環境変数を直接渡す」という文化もある - Jest のエラーは、この「実行環境の違い」と「慣習の変換」を可視化してくれる良い題材でした