xvcl - Metaprogramming for Fastly VCL
Description: xvcl - A modern VCL preprocessor that extends Fastly VCL with metaprogramming features like loops, functions, macros, and conditionals.
Keywords: VCL, Fastly, preprocessor, metaprogramming, CDN, transpiler

# 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
Get Started View on GitHub
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

📘

### Full Documentation

Complete guide to all xvcl features, directives, and usage patterns.

⚡

### Quick Reference

Concise syntax reference for all xvcl directives and template expressions.

💡

### Examples

Real-world xvcl implementations including IP encryption and complex configurations.

🐛

### Issues & Support

Report bugs, request features, or get help from the community.

### 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