Skip to main content
Wik/Lex compiles every .lex template to a plain PHP file and stores it in the .lexer/ directory. On subsequent requests the engine re-uses the compiled file, skipping the full parse-and-compile pipeline. When a source file changes, the engine detects the change via a dependency graph and recompiles automatically — you never need to clear the cache by hand during development.

Cache directory layout

The cache directory is always placed at {projectRoot}/.lexer/ when you use Lexer::fromConfig(). When you use the fluent API without a config file it defaults to {cwd}/.lexer/. Override it with ->cache(string $dir).
PathContents
.lexer/compiled/Compiled PHP files named {md5(cacheKey)}.php
.lexer/ast/Serialised AST snapshots named {md5(cacheKey)}.ast
.lexer/compiled/index.phpPrecompiled view index used in production mode
.lexer/view_dependencies.jsonDependency graph for automatic cache invalidation
Add .lexer/ to your .gitignore. Compiled output is derived from your source templates and should not be committed.

How compilation and caching work

1

First render

When you call $lexer->render('home', $data) for the first time, the engine runs the full compilation pipeline:
  1. FileLoader resolves 'home' to an absolute path and generates a cache key (md5(path:mtime)).
  2. The Lexer tokenises the source character-by-character (no regex structural parsing).
  3. The Parser converts tokens into a nested AST.
  4. DependencyGraph walks the AST and records every static dependency (#extends, #include, component tags) with its current filemtime.
  5. AstValidator enforces sandbox rules if sandbox mode is active.
  6. OptimizePass merges adjacent text nodes and removes empty nodes.
  7. Code generation produces a PHP source string.
  8. FileCache writes the compiled PHP to .lexer/compiled/{md5}.php atomically (write to temp file, then rename).
  9. The compiled file is include()-d in an isolated scope with $__env injected.
2

Subsequent renders (dev mode)

On every render in dev mode the engine:
  1. Generates the cache key from the current filemtime of the source file.
  2. Checks DependencyGraph::isStale() — if any recorded dependency has a different filemtime than when it was compiled, the compiled cache is cleared.
  3. If the key matches an existing .php file and the template is not stale, the file is served directly — no recompilation.
  4. Otherwise the pipeline from step 1 above runs again.
3

Production mode

In production mode all source-file I/O is skipped entirely. The engine looks up the template path in .lexer/compiled/index.php and serves the compiled file directly. See Production mode for the full workflow.

The dependency graph

What is tracked

When a template is compiled, Lex walks its AST and records every static dependency it finds:
Source constructTracked as
#extends('layouts.app')Layout dependency
#include('partials.header')Include dependency
#includeIf, #includeWhen, #includeFirst (string literal args)Include dependencies
<Card />, <Alert> component tagsComponent dependencies

What is not tracked

Dynamic include expressions — where the template name is a variable — cannot be statically resolved and are silently skipped:
{{-- This dependency is NOT tracked --}}
#include($dynamicTemplateName)

{{-- This dependency IS tracked --}}
#include('partials.header')

The view_dependencies.json format

The graph is persisted to .lexer/view_dependencies.json. Keys are absolute template paths; nested keys are absolute dependency paths; values are the Unix filemtime timestamps recorded at compile time.
{
  "/abs/views/pages/home.lex": {
    "/abs/views/layouts/app.lex": 1712000000,
    "/abs/views/partials/header.lex": 1712000001
  },
  "/abs/views/pages/about.lex": {
    "/abs/views/layouts/app.lex": 1712000000
  }
}

Invalidation flow

When header.lex is modified, the sequence is:
header.lex modified  (mtime changes)


Next render of home.lex
  → DependencyGraph::isStale('home.lex') checks header.lex mtime  → changed!
  → compiled cache for home.lex is cleared
  → full recompile runs
  → dependency graph is updated with the new mtime
All templates that transitively depend on the changed file are recompiled on their next render request. No manual intervention is needed.

DependencyGraph API

You can query the graph directly from PHP for tooling, warm-up scripts, or debugging.
use Wik\Lexer\Cache\DependencyGraph;

$graph = new DependencyGraph('/path/to/project/.lexer');

getDeps(string $template): array

Return the recorded dependency map for $template — an associative array of absoluteDepPath => mtime.
$graph->getDeps('/abs/views/pages/home.lex');
// → ['/abs/views/layouts/app.lex' => 1712000000, ...]

getDependents(string $template): array

Return every template that lists $template as a dependency (reverse lookup). Useful for finding all templates that would be invalidated if a shared partial changes.
$graph->getDependents('/abs/views/partials/header.lex');
// → ['/abs/views/pages/home.lex', '/abs/views/pages/about.lex']

isStale(string $template): bool

Return true if at least one recorded dependency of $template has a different filemtime than when the template was last compiled. Returns false when no dependency data has been recorded.
$graph->isStale('/abs/views/pages/home.lex'); // bool

all(): array

Return the full forward dependency map (templatePath => [depPath => mtime]). Useful for CLI tooling and inspection.
$allDeps = $graph->all();
foreach ($allDeps as $template => $deps) {
    echo $template . ' depends on ' . count($deps) . ' file(s)' . PHP_EOL;
}

Clearing the cache

There are two ways to clear the compiled cache:
Delete the entire .lexer/ directory. On the next render request Lex creates a fresh cache from scratch.
rm -rf .lexer/
Deleting the .lexer/ directory in production also removes the precompiled index (index.php). If you are running in production mode you must recompile all templates before serving traffic again.

Build docs developers (and LLMs) love