今天來介紹 SvelteKit 怎麼使用環境變數(environment variables),所謂環境變數是指我們程式運行所需要的某些設定、參數或敏感資料等等東西存放在程式碼外,方便讓這些變數會隨著環境變化。
SvelteKit 的環境變數共有四種分別是 dynamic/private、dynamic/public、static/private、static/public,基本上只要記得一個大原則是「client-side 只能使用 public 的環境變數」而 server-side 是所有都能使用。
因為 SvelteKit 是使用 vite 進行打包,所以定義環境變數以及注入環境變數上其實是差不多的,就直接使用 .env 檔案即可,如果有多個 .env 檔案可以使用 --mode 來選擇注入不一樣的 .env 檔。
# in .env
API_KEY=apikey
PUBLIC_API_URL=https://api.example.com
在 .env 檔的所有變數都可以從 static 取得,接下來在根據變數名稱來決定他是 public 或是 private。預設情況下如果是 PUBLIC_ 開頭的值都會被視為 public ,反之則全部都算是 private。
// in day27/+page.server.ts
import { API_KEY } from '$env/static/private';
import { PUBLIC_API_URL } from '$env/static/public';
import type { ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = () => {
console.log(API_KEY, PUBLIC_API_URL);
};

會發現 terminal 上的輸出跟我們 .env 的一致
<!-- in day27/+page.svelte -->
<script lang="ts">
import { API_KEY } from '$env/static/private';
import { PUBLIC_API_URL } from '$env/static/public';
console.log(`API_KEY=${API_KEY}`);
console.log(`PUBLIC_API_URL=${PUBLIC_API_URL}`);
</script>
然後在 +page.svelte (client-side)使用 $env/static/private 就會看到以下錯誤

然後當我把 $env/static/private 刪掉後就能正常運行了。

那該如何新增 $env/dynamic/(public|private) 呢?基本上就跟我們新增 process.env 一樣直接在指令前新增就好
PUBLIC_API_URL_2=https://api.v2.example.com API_KEY_2=apikey2 pnpm run dev
使用上只要直接 import { env } from '$env/dynamic/private' 然後 SvelteKit 會在 build 的時候幫我們產生 type

// in day27/+page.server.ts
import { env } from '$env/dynamic/private';
import { API_KEY } from '$env/static/private';
import { PUBLIC_API_URL } from '$env/static/public';
import type { ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = () => {
console.log(`API_KEY=${API_KEY}`);
console.log(`PUBLIC_API_URL=${PUBLIC_API_URL}`);
console.log(`[$env/dynamic/private]API_KEY_2=${env.API_KE}`);
console.log(`[$env/dynamic/private]PUBLIC_API_URL_2=${env.PUBLIC_API_URL_2}`);
};

我們看 terminal 的輸出可以看到 PUBLIC_API_URL_2 是 undefined ,這是因為他是 PUBLIC_ 開頭的變數,所以就不會被放在 $env/dynamic/private 裡了。
所以只要改用 $env/dynamic/public 即可看到在 CLI 上所輸入的PUBLIC_API_URL_2 的值了
import { env } from '$env/dynamic/private';
import { env as publicEnv } from '$env/dynamic/public';
import { API_KEY } from '$env/static/private';
import { PUBLIC_API_URL } from '$env/static/public';
import type { ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = () => {
console.log(`API_KEY=${API_KEY}`);
console.log(`PUBLIC_API_URL=${PUBLIC_API_URL}`);
console.log(`[$env/dynamic/private]API_KEY_2=${env.API_KE}`);
console.log(`[$env/dynamic/private]PUBLIC_API_URL_2=${env.PUBLIC_API_URL_2}`);
console.log(`[$env/dynamic/public]PUBLIC_API_URL_2=${publicEnv.PUBLIC_API_URL_2}`);
};

那一樣如果我在 +page.svelte 使用 $env/dynamic/private 會跳出錯誤
<script lang="ts">
import { PUBLIC_API_URL } from '$env/static/public';
import { env } from '$env/dynamic/private';
import { env as publicEnv } from '$env/dynamic/public';
console.log(`PUBLIC_API_URL=${PUBLIC_API_URL}`);
console.log(`$env/dynamic/public=${JSON.stringify(env, null, 2)}`);
console.log(`$env/dynamic/private=${JSON.stringify(publicEnv, null, 2)}`);
</script>
<h1>Day 27</h1>

直到我把 import { env } from '$env/dynamic/private' 刪除

其實在剛剛的例子中我們會發現即使我們傳進 process.env 的變數依然會可以在 static 被找到,這是因為 SvelteKit 在 build time 時會自動幫我們 merge process.env 及 .env 然後產生 type declaration 。
如果變數名稱一樣會是
process.env覆蓋掉.env的值

所以其實換句話說只要我們能確保 prcoess.env 我們在每個指令/環境都有確實傳入我們該傳入的環境變數,我們依然可以把它當作 static 來 import
同理 .env 檔的環境變數也可以出現在 dynamic 中

那就產生了兩個疑問「我該選擇使用 prcoess.env 還是 .env 來新增環境變數」以及「dynamic 以及 static 該如何選擇」
以管理的方便程度來看我會推薦優先使用 .env 來定義環境變數,除非真的是在 server run 時才能確定的環境變數才會建議使用 process.env
如果可以的話一律推薦使用 static ,因為 dynamic 在 prerender 期間是不能使用的
雖然現在還沒介紹到 prerender ,但可以先想成 SSG 的頁面都不能使用 dynamic 就好。

簡而言之,我自己是覺得 SvelteKit 的環境變數 dynamic 與 static 在某些情況下是可以一致的,只是用語意在限制/提醒開發者區分出這兩者,所以我自己的習慣會是 .env 來的一律當作 static , prcoess.env 一律當作 dynamic 。
我的原因是 .env 上有的一定會出現 $env/* 的 type declaration ,但出現在 $env/* 的type declaration 不一定會出現在 .env ,所以基本上為了方便檢查這件事情我都會建議使用 .env ,畢竟查閱 .env 檔通常會比看 script 還好閱讀吧。