xvcl について
XVCLを使ってFastly VCLを書くためのスキルです。コンパイル時メタプログラミングを加えるトランスパイラーを利用し、.xvclの作成、VCLへの変換、Falcoによるローカルテストを支援します。
主な機能
- 01コンパイル時定数と式
- 02ループ、条件分岐、テンプレート
- 03関数とインラインマクロ
- 04循環検出付きインクルード
- 05.xvclから.vclへのコンパイル
- 06FalcoによるVCLのローカルシミュレーション
活用シーン
- 01バックエンドやルーティング設定を反復生成
- 02データ駆動型のVCLテーブルを作成
- 03Fastly VCLの変更をローカルでコンパイル・シミュレーション
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ fastly/ fastly-agent-toolkit --skill xvclインストール後、Claude Code で/xvclを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/backends.mdcaching.mdcrypto.mdheaders.mdstrings.mdsubroutines.mdtables-acls.mdtesting-vcl.mdvcl-syntax.mdxvcl-directives.md詳細
トリガーと対象範囲
次の場合にトリガーします:XVCL、.xvcl ファイル、VCL トランスパイラー、VCL メタプログラミング、VCL のコンテキストでの #const/#for/#def/#inline、VCL スクリプトの作成、VCL の作成とローカルでの実行、または Fastly VCL の作成タスク全般。
次の場合はトリガーしないでください:XVCL を使わない既存の .vcl ファイルのデバッグ、Fastly API/CLI の操作、Fastly Compute、Terraform(VCL に言及している場合も含む)。
ユーザーがプレーンな VCL を明示的にリクエストした場合は、.vcl ファイルを使用し、XVCL を導入しないでください。
デフォルトの例よりも、明示的に指定されたツールとテストモードを優先してください。
XVCL を使った VCL の作成
XVCL は、Fastly VCL にメタプログラミング機能を追加する VCL トランスパイラーです。.xvcl ファイルを作成し、.vcl にコンパイルしてから、Falco でテストするか Fastly にデプロイします。XVCL の構文はすべてコンパイル時に解決されるため、実行時のオーバーヘッドはありません。
クイックスタート
コンパイルには、uv が提供する uvx が必要です。ローカルでのチェックには Falco を使用します。
# Compile with uvx
uvx xvcl main.xvcl -o main.vcl
# Lint the output
falco lint main.vcl
# Run locally — this is how you "run" VCL on your machine
falco simulate main.vcl
# Listens on localhost:3124 by default
# Then test with: curl http://localhost:3124/
別のテストモードを明示的にリクエストされない限り、ローカル実行ではコンパイルして falco simulate を実行してください。
Lint のみでは VCL は実行されません。
最小限の動作例
バックエンドの命名:Fastly VCL では、バックエンド名に F_ プレフィックスが必要です(例:F_origin、F_api)。backend default は使用しないでください。falco に拒否されます。vcl_recv では必ず req.backend を明示的に設定してください。
#const ORIGIN_HOST = "api.example.com"
#const REGIONS = [("us", "us.example.com"), ("eu", "eu.example.com")]
#for name, host in REGIONS
backend F_{{name}} {
.host = "{{host}}";
.port = "443";
.ssl = true;
}
#endfor
sub vcl_recv {
#FASTLY recv
set req.backend = F_us;
return (lookup);
}
sub vcl_deliver {
#FASTLY deliver
set resp.http.X-Served-By = "edge";
return (deliver);
}
XVCL ディレクティブの概要
すべてのディレクティブの構文と例については、xvcl-directives.md を参照してください。
定数 — #const
#const NAME = value // type auto-inferred
#const NAME TYPE = value // explicit type
#const TTL INTEGER = 3600
#const ORIGIN = "origin.example.com"
#const ENABLED BOOL = true
#const DOUBLE_TTL = TTL * 2 // expressions supported
#const BACKENDS = ["web1", "web2"] // lists
#const PAIRS = [("api", 8080), ("web", 80)] // tuples
定数はコンパイル時のみ使用され、VCL 変数にはなりません。値を出力するには、必ず {{NAME}} を使用してください。VCL 内で定数名だけを記述すると(例:error 200 GREETING;)、リテラル文字列としてそのまま出力され、無効な VCL になります。代わりに error 200 "{{GREETING}}"; を使用してください。
テンプレートでの使用例:"{{TTL}}"、{{ORIGIN}}、backend F_{{name}} { ... }
テンプレート式 — {{ }}
{{CONST_NAME}} // constant substitution
{{PORT * 2}} // arithmetic
{{hex(255)}} // → "0xff"
{{format(42, '05d')}} // → "00042"
{{len(BACKENDS)}} // list length
{{value if condition else other}} // ternary
組み込み関数:range()、len()、str()、int()、hex()、format()、enumerate()、min()、max()、abs()
For ループ — #for / #endfor
#for i in range(5) // 0..4
#for i in range(2, 8) // 2..7
#for item in LIST_CONST // iterate list
#for name, host in TUPLES // tuple unpacking
#for idx, item in enumerate(LIST) // index + value
ループを使ったテーブル
インラインの if チェーンを生成する代わりに、#for ループを使って VCL の table 宣言に値を設定し、O(1) のルックアップを実現してください。
#const REDIRECTS = [
("/blog", "/articles"),
("/about-us", "/about"),
("/products/old-widget", "/products/widget-v2")
]
// O(1) hash-table lookup — the right pattern for data-driven VCL
table redirects STRING {
#for old_path, new_path in REDIRECTS
"{{old_path}}": "{{new_path}}",
#endfor
}
sub vcl_recv {
if (table.contains(redirects, req.url.path)) {
error 801 table.lookup(redirects, req.url.path);
}
}
インラインの if チェーンを生成するよりも、#for ループを使って VCL の table 宣言に値を設定する方法を推奨します。テーブルは O(1) のハッシュルックアップを実現し、データに基づくルーティング、リダイレクト、設定全般に適した Fastly の標準的なパターンです。
条件分岐 — #if / #elif / #else / #endif
#if PRODUCTION
set req.http.X-Env = "prod";
#elif STAGING
set req.http.X-Env = "staging";
#else
set req.http.X-Env = "dev";
#endif
対応するもの:ブール定数、比較演算子(==、!=、<、>)、論理演算子(and、or、not)。
変数の省略記法 — #let
#let cache_key STRING = req.url.path;
// expands to:
// declare local var.cache_key STRING;
// set var.cache_key = req.url.path;
関数 — #def / #enddef
// Single return value
#def normalize_path(path STRING) -> STRING
declare local var.result STRING;
set var.result = std.tolower(path);
return var.result;
#enddef
// Tuple return (multiple values)
#def parse_pair(s STRING) -> (STRING, STRING)
declare local var.key STRING;
declare local var.value STRING;
set var.key = regsub(s, ":.*", "");
set var.value = regsub(s, "^[^:]*:", "");
return var.key, var.value;
#enddef
// Call sites
set var.clean = normalize_path(req.url.path);
set var.k, var.v = parse_pair("host:example.com");
関数は VCL サブルーチンにコンパイルされ、パラメーターは req.http.X-Func-* ヘッダーを介して渡されます。
インラインマクロ — #inline / #endinline
#inline cache_key(url, host)
digest.hash_md5(url + "|" + host)
#endinline
// Zero-overhead text substitution. Auto-parenthesizes arguments
// containing operators to prevent precedence bugs.
set req.hash += cache_key(req.url, req.http.Host);
インクルード — #include
#include "includes/backends.xvcl" // relative path
#include <stdlib/security.xvcl> // include path (-I)
インクルードは一度だけ実行されます。循環インクルードは検出され、報告されます。
コンパイル
# Basic
uvx xvcl input.xvcl -o output.vcl
# With include paths
uvx xvcl main.xvcl -o main.vcl -I ./includes -I ./shared
# Debug mode (shows expansion traces)
uvx xvcl main.xvcl -o main.vcl --debug
# Source maps (adds BEGIN/END INCLUDE markers)
uvx xvcl main.xvcl -o main.vcl --source-maps
| オプション | 説明 |
|---|---|
-o, --output | 出力ファイル(デフォルト:.xvcl を .vcl に置き換え) |
-I, --include | インクルード検索パスを追加(複数指定可能) |
--debug / -v | 展開トレースを表示 |
--source-maps | ソース位置のコメントを追加 |
--error-format | エラー出力形式:text(デフォルト)または json |
よくある間違い
- VCL で定数名をそのまま使う:
error 200 GREETING;はリテラル文字列としてそのまま通ります。テンプレート構文を使い、error 200 "{{GREETING}}";と記述してください。 - テーブルではなく if チェーンを生成する:データ駆動型のルーティングやリダイレクトでは、インラインの if チェーンではなく、必ず
#forを使って VCL のtableに値を設定してください。if チェーンは O(n)、テーブルは O(1) です。 #FASTLYマクロを忘れる:すべての VCL サブルーチン(vcl_recv、vcl_fetch、vcl_deliver、vcl_error、vcl_hit、vcl_miss、vcl_pass)の先頭に、#FASTLY recv(または対応する名前)が必要です。backend defaultを使う:Fastly VCL では、バックエンド名にF_のプレフィックスが必要です。backend F_origin { ... }を使い、set req.backend = F_origin;と記述してください。
VCL の注意点
間違いやすい VCL ランタイムの落とし穴:
- 剰余演算子はありません:VCL には
%演算子がありません。トラフィックを分割するには、ハッシュダイジェストに対してsubstr()を使います。if (substr(digest.hash_sha256(client.ip), 0, 1) ~ "^[0-7]$")は約 50%になります。またはrandomint(0, 99) < 50を使ってください。 - Vary は必ず
vcl_fetchで設定する:Vary ヘッダーはキャッシュキーを制御します。vcl_deliverでのみ設定するのでは遅すぎます。Vary のディメンションを含めずに、すでにオブジェクトがキャッシュされているためです。必ずvcl_fetchで Vary を追加してください(クライアントから見えるヘッダーのために、必要に応じてvcl_deliverにも同じ内容を設定できます)。既存の Vary を上書きしないでください。確認してから追加します。 - falco テストでは
req.url.pathは読み取り専用です:テスト用サブルーチンでは、set req.url.path = "/path"ではなくset req.url = "/path"を使ってください。.pathプロパティはreq.urlから計算されるため、直接設定できません。 req.requestは非推奨です:代わりにreq.methodを使ってください。Falco はどちらも受け付けますが、req.methodが現在の形式です。- Cookie の解析:正規表現ではなく、
subfield(req.http.Cookie, "name", ";")を使ってください。Cookie ~ "name=(\w+)"のような正規表現では、似たプレフィックスの Cookie(例:name_v2=X)に誤って一致することがあります。
参考資料
VCL の基本(リクエストのライフサイクル、return アクション、変数の型)については、以下の VCL 構文およびサブルーチンの参考資料を参照してください。
特定の機能を実装する前に、該当する参考ファイルを最後まで読んでください。
| トピック | ファイル | 使用する場面 |
|---|---|---|
| XVCL ディレクティブ | xvcl-directives.md | XVCL コードを記述する場合 — すべてのディレクティブの完全な構文 |
| VCL 構文 | vcl-syntax.md | データ型、演算子、制御フローを扱う場合 |
| サブルーチン | subroutines.md | リクエストのライフサイクルやカスタムサブルーチンを理解する場合 |
| ヘッダー | headers.md | HTTP ヘッダーを操作する場合 |
| バックエンド | backends.md | オリジン、ディレクター、ヘルスチェックを設定する場合 |
| キャッシュ | caching.md | TTL、猶予期間、キャッシュキーを設定する場合 |
| 文字列 | strings.md | 文字列操作関数を使う場合 |
| 暗号 | crypto.md | ハッシュ化、HMAC、base64 エンコードを行う場合 |
| テーブル/ACL | tables-acls.md | ルックアップテーブル、アクセス制御リストを使う場合 |
| VCL のテスト | testing-vcl.md | 単体テスト、アサーション、テストヘルパーを作成する場合 |
プロジェクト構成
vcl/
├── main.xvcl
├── config.xvcl # shared constants
└── includes/
├── backends.xvcl
├── security.xvcl
├── routing.xvcl
└── caching.xvcl