Metaprogramming for Fastly VCL

A modern preprocessor that extends VCL with loops, functions, macros, and conditionals. Write less, deploy more.

⚔ Zero runtime overhead - compiles to standard VCL
šŸ”„ Reduces boilerplate with loops and macros
šŸ›”ļø Type-safe functions with automatic parameter passing
Input (xvcl)
#const MAX_BACKENDS INTEGER = 5

#for i in range(MAX_BACKENDS)
backend web{{i}} {
  .host = "web{{i}}.example.com";
  .port = "80";
}
#endfor
→
Output (VCL)
backend web0 {
  .host = "web0.example.com";
  .port = "80";
}
backend web1 {
  .host = "web1.example.com";
  .port = "80";
}
// ... web2, web3, web4

Why xvcl?

VCL is Repetitive

Fastly VCL lacks abstraction mechanisms. Creating similar backends, directors, or ACL entries requires copy-paste-modify patterns that are error-prone and hard to maintain.

Configuration Management is Hard

Managing environment-specific configurations (dev, staging, production) or multi-region deployments requires maintaining multiple VCL files or brittle string templating.

No Code Reuse

VCL subroutines can't accept parameters or return values, making it difficult to create reusable logic without global state management hacks.

The Solution

xvcl extends VCL with metaprogramming features at compile time. It processes your enhanced VCL source files and generates clean, standard VCL that works with Fastly. Think of it as a build step that makes VCL development feel like modern programming.

Powerful Features

Everything you need to write maintainable, DRY VCL configuration

šŸ“

Constants

Define compile-time constants with type checking. Use them throughout your configuration for centralized management.

#const API_TIMEOUT INTEGER = 5000
#const ORIGIN STRING = "api.example.com"
šŸ”

For Loops

Generate repetitive code blocks dynamically. Iterate over ranges or lists to create backends, directors, or ACL entries.

#for region in ["us", "eu", "asia"]
backend origin_{{region}} {
  .host = "{{region}}.example.com";
}
#endfor
šŸ”€

Conditionals

Conditional compilation based on environment variables or constants. Different code for dev vs production.

#if PRODUCTION
  set req.http.X-Debug = "false";
#else
  set req.http.X-Debug = "true";
#endif
āš™ļø

Functions

Define reusable functions with parameters and return values. Compiles to VCL subroutines with automatic header-based parameter passing.

#def normalize(url STRING) -> STRING
  return std.tolower(url);
#enddef

set req.url = normalize(req.url);
⚔

Inline Macros

Zero-overhead text substitution for simple expressions. Expands inline without function call overhead.

#inline is_mobile(ua)
regsuball(ua, "(?i)mobile|android", "")
#endinline
šŸ“¦

Includes

Modular code organization with include-once semantics and circular dependency detection.

#include "backends.xvcl"
#include "functions.xvcl"
#include "security.xvcl"
šŸŽÆ

Template Expressions

Evaluate Python expressions at compile time. Use constants and loop variables for dynamic code generation.

#for i in range(10)
  if (req.http.X-Port == "{{8000 + i}}") {
    set req.backend = app{{i}};
  }
#endfor
šŸ”

Source Maps

Debug generated VCL with source map comments that track where each line originated in your xvcl files.

xvcl main.xvcl -o main.vcl --source-maps

See It In Action

Real-world examples showing xvcl's power

Example 1: Multi-Region Backends

xvcl Source
#const REGIONS = ["us_east", "us_west", "eu_central", "ap_southeast"]

#for region in REGIONS
backend origin_{{region}} {
  .host = "{{region}}.api.example.com";
  .port = "443";
  .ssl = true;
  .connect_timeout = 5s;
}
#endfor
Generated VCL
backend origin_us_east {
  .host = "us_east.api.example.com";
  .port = "443";
  .ssl = true;
  .connect_timeout = 5s;
}
backend origin_us_west {
  .host = "us_west.api.example.com";
  .port = "443";
  .ssl = true;
  .connect_timeout = 5s;
}
// ... eu_central, ap_southeast

Example 2: Reusable Functions

xvcl Source
#def cache_key(url STRING, user STRING) -> STRING
  declare local var.key STRING;
  set var.key = url + ":" + user;
  return std.tolower(var.key);
#enddef

sub vcl_hash {
  declare local var.key STRING;
  set var.key = cache_key(req.url, req.http.User-ID);
  set req.hash += var.key;
}
Generated VCL
sub vcl_hash {
  declare local var.key STRING;
  set req.http.X-Func-cache_key-url = req.url;
  set req.http.X-Func-cache_key-user = req.http.User-ID;
  call cache_key;
  set var.key = req.http.X-Func-cache_key-Return;
  set req.hash += var.key;
}

// Function subroutine generated at end of file
sub cache_key {
  // Parameter unpacking, function body...
}

Example 3: Environment Configuration

xvcl Source
#const ENV STRING = "production"
#const DEBUG BOOL = false

sub vcl_recv {
  #if DEBUG
    set req.http.X-Debug = "enabled";
    set req.http.X-Timestamp = now;
  #endif

  set req.http.X-Environment = "{{ENV}}";

  #if ENV == "production"
    unset req.http.X-Internal-Header;
  #endif
}
Generated VCL
sub vcl_recv {
  set req.http.X-Environment = "production";

  unset req.http.X-Internal-Header;
}

Quick Start

Get up and running in minutes

Installation

Choose your preferred installation method:

Using pip

pip install xvcl

Using uv (recommended)

uv pip install xvcl

Using uvx (no install required)

uvx xvcl input.xvcl -o output.vcl

From source

git clone https://github.com/dip-proto/xvcl.git
cd xvcl
pip install -e .

Your First xvcl File

Create a simple hello.xvcl file:

#const SERVICE_NAME STRING = "My Awesome Service"

sub vcl_recv {
  set req.http.X-Service = "{{SERVICE_NAME}}";

  #for i in range(3)
  if (req.http.X-Backend-ID == "{{i}}") {
    set req.backend = backend{{i}};
  }
  #endfor
}

#for i in range(3)
backend backend{{i}} {
  .host = "backend{{i}}.example.com";
  .port = "80";
}
#endfor

Compile it to standard VCL:

xvcl hello.xvcl -o hello.vcl

Validate with Falco (if installed):

falco lint hello.vcl

Development Workflow

1

Write xvcl

Create your VCL configuration using xvcl's enhanced syntax with loops, functions, and macros.

2

Compile

Run xvcl to transpile your source to standard VCL. Use --debug for verbose output.

3

Validate

Use falco lint to check the generated VCL for errors and best practices.

4

Test

Run falco test on your VCL to verify behavior with unit tests.

5

Deploy

Upload the generated VCL to Fastly and activate your service.

Documentation

Everything you need to master xvcl

Integration with Falco

xvcl works seamlessly with Falco, a comprehensive VCL linting, testing, and simulation tool.

Complete Pipeline Example

# 1. Compile xvcl to VCL
xvcl main.xvcl -o main.vcl -I ./includes

# 2. Lint the generated VCL
falco lint -vv main.vcl

# 3. Run tests
falco test main.vcl

# 4. Simulate locally
falco simulate main.vcl

# 5. Deploy to Fastly (using Fastly CLI)
fastly vcl custom update --version=latest --name=main --content=main.vcl