Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0fa6606
Adds an HTML parser + template instrumentation to Antlers 😮‍💨
JohnathonKoster Sep 10, 2026
dcf33a6
Config adjustments
JohnathonKoster Oct 11, 2026
a4e0cef
Refine the Antlers HTML analyzer
JohnathonKoster Oct 11, 2026
eb49c7f
Update HTML instrumentation
JohnathonKoster Oct 11, 2026
21d722a
Update the Antlers parser and runtime for instrumentation
JohnathonKoster Oct 11, 2026
2d3a94f
Integrate instrumentation with tags, views and caching
JohnathonKoster Oct 11, 2026
3005f4b
Update instrumentation review notes
JohnathonKoster Oct 11, 2026
127337c
Merge 6.x
JohnathonKoster Oct 13, 2026
2964362
Collapse the instrumentation result memo into one tier
JohnathonKoster Oct 13, 2026
518b715
Make instrumentation manager mutators chainable
JohnathonKoster Oct 13, 2026
709e916
Add HTML analyzer and parser tests
JohnathonKoster Oct 13, 2026
60b1357
Add runtime instrumentation and tracing tests
JohnathonKoster Oct 13, 2026
f136200
Add view instrumentation tests
JohnathonKoster Oct 13, 2026
dc27039
Remove instrumentation review notes
JohnathonKoster Oct 13, 2026
8a1c579
Clear the render node cache from the instrumentation manager
JohnathonKoster Oct 14, 2026
89721be
Let instrumenters contribute marker patterns
JohnathonKoster Oct 14, 2026
5623736
Avoid unsafe new static in instrumentation classes
JohnathonKoster Oct 14, 2026
3f8e497
Trim prose from instrumentation docblocks
JohnathonKoster Oct 15, 2026
cd59ffd
Move analyzer lookup tables into the HTML spec
JohnathonKoster Oct 15, 2026
5d1292f
Add Antlers::parseWithoutInstrumentation
JohnathonKoster Oct 22, 2026
e0182db
Merge remote-tracking branch 'origin/6.x' into feat/antlers-html-inst…
JohnathonKoster Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions config/antlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,41 @@

'debugbar' => env('STATAMIC_ANTLERS_DEBUGBAR', true),

/*
|--------------------------------------------------------------------------
| Tracing
|--------------------------------------------------------------------------
|
| When tracing is enabled, every tracer listed here is resolved from the
| container and notified as every Antlers node is entered and exited.
|
*/

'tracing' => env('STATAMIC_ANTLERS_TRACING', false),

'tracers' => [],

/*
|--------------------------------------------------------------------------
| HTML Instrumentation
|--------------------------------------------------------------------------
|
| When enabled, rendered Antlers regions are wrapped in HTML comments and
| attributes that point back to the templates they came from. Analysis
| is cached by template content; set "cache" to false to skip that.
|
*/

'instrumentation' => [
'enabled' => env('STATAMIC_ANTLERS_INSTRUMENTATION', false),
'cache' => env('STATAMIC_ANTLERS_INSTRUMENTATION_CACHE', true),
'comments' => true,
'prefix' => 'antlers',
'attributes' => [],
'metadata' => [],
'componentPrefixes' => [],
],

/*
|--------------------------------------------------------------------------
| Guarded Variables
Expand Down
7 changes: 7 additions & 0 deletions src/Contracts/View/Instrumentation/ComposesRenderedOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Statamic\Contracts\View\Instrumentation;

interface ComposesRenderedOutput
{
}
1 change: 1 addition & 0 deletions src/Facades/Antlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @method static Parser parser()
* @method static mixed usingParser(Parser $parser, \Closure $callback)
* @method static AntlersString parse(string $str, array $variables = [], bool $trusted = false)
* @method static AntlersString parseWithoutInstrumentation(string $str, array $variables = [], bool $trusted = false)
* @method static string parseLoop(string $content, array $data, bool $supplement = true, array $context = [], bool $trusted = false)
* @method static array identifiers(string $content)
*
Expand Down
3 changes: 2 additions & 1 deletion src/Forms/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use DebugBar\DebugBarException;
use Illuminate\Support\Collection;
use Statamic\Contracts\Forms\Form as FormContract;
use Statamic\Contracts\View\Instrumentation\ComposesRenderedOutput;
use Statamic\Facades\Antlers;
use Statamic\Facades\Blink;
use Statamic\Facades\Blueprint;
Expand All @@ -19,7 +20,7 @@
use Statamic\Tags\Tags as BaseTags;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;

class Tags extends BaseTags
class Tags extends BaseTags implements ComposesRenderedOutput
{
use Concerns\GetsFormSession,
Concerns\GetsQueryResults,
Expand Down
2 changes: 1 addition & 1 deletion src/Modifiers/CoreModifiers.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public function antlers($value, $params, $context)
{
$trusted = Arr::get($params, 0) === 'trusted' && ! GlobalRuntimeState::$isEvaluatingUserData;

return (string) Antlers::parse($value, $context, $trusted);
return (string) Antlers::parseWithoutInstrumentation($value, $context, $trusted);
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class EventServiceProvider extends ServiceProvider
\Statamic\Listeners\ClearState::class,
],
\Illuminate\Foundation\Http\Events\RequestHandled::class => [
\Statamic\View\Instrumentation\RemoveMisplacedMarkersFromResponse::class,
\Statamic\Listeners\ClearState::class,
],
\Illuminate\Console\Events\CommandFinished::class => [
Expand Down
7 changes: 6 additions & 1 deletion src/Providers/ViewServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Statamic\View\Cascade;
use Statamic\View\Debugbar\AntlersProfiler\PerformanceCollector;
use Statamic\View\Debugbar\AntlersProfiler\PerformanceTracer;
use Statamic\View\Instrumentation\InstrumentationManager;
use Statamic\View\Interop\Stacks;

class ViewServiceProvider extends ServiceProvider
Expand Down Expand Up @@ -85,6 +86,8 @@ private function registerAntlers()
return new PerformanceTracer();
});

$this->app->singleton(InstrumentationManager::class);

$this->app->bind(ParserContract::class, function ($app) {
/** @var RuntimeParser $parser */
$parser = $app->make(RuntimeParser::class)->cascade($app[Cascade::class]);
Expand Down Expand Up @@ -152,14 +155,16 @@ private function registerAntlers()
}

if ($this->profilerEnabled()) {
if (! $isTracingOn) {
if ($runtimeConfig->traceManager === null) {
$runtimeConfig->traceManager = new TraceManager();
$runtimeConfig->isTracingEnabled = true;
}

$runtimeConfig->traceManager->registerTracer(app(PerformanceTracer::class));
}

$app->make(InstrumentationManager::class)->applyTo($runtimeConfig);

$parser->isolateRuntimes(GlobalRuntimeState::$requiresRuntimeIsolation)
->setRuntimeConfiguration($runtimeConfig);

Expand Down
9 changes: 9 additions & 0 deletions src/StaticCaching/Middleware/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Statamic\StaticCaching\NoCache\Session;
use Statamic\StaticCaching\Replacer;
use Statamic\StaticCaching\ResponseStatus;
use Statamic\View\Antlers\Language\Facades\Runtime;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

use function Statamic\trans as __;
Expand Down Expand Up @@ -213,11 +214,19 @@ private function canBeCached($request)
return false;
}

if (Runtime::instrumenting()) {
return false;
}

return true;
}

private function shouldBeCached($request, $response)
{
if (Runtime::instrumenting()) {
return false;
}

// Only GET requests should be cached. For instance, Live Preview hits frontend URLs as
// POST requests to preview the changes. We don't want those to trigger any caching,
// or else pending changes will be shown immediately, even without hitting save.
Expand Down
3 changes: 3 additions & 0 deletions src/StaticCaching/NoCache/StringFragment.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Statamic\Facades\File;
use Statamic\Support\Arr;
use Statamic\Support\Str;
use Statamic\View\Antlers\Language\Runtime\RuntimeParser;

class StringFragment
{
Expand Down Expand Up @@ -32,6 +33,8 @@ public function render(): string

$this->data['__frontmatter'] = Arr::pull($this->data, 'view', []);

$this->data[RuntimeParser::CAPTURED_CONTENT] = true;

$rendered = view('nocache::'.$view, $this->data)->render();

File::delete($path);
Expand Down
3 changes: 2 additions & 1 deletion src/StaticCaching/NoCache/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

namespace Statamic\StaticCaching\NoCache;

use Statamic\Contracts\View\Instrumentation\ComposesRenderedOutput;
use Statamic\Facades\Antlers;
use Statamic\StaticCaching\Middleware\Cache;

class Tags extends \Statamic\Tags\Tags
class Tags extends \Statamic\Tags\Tags implements ComposesRenderedOutput
{
public static $handle = 'nocache';
public static $stack = 0;
Expand Down
5 changes: 5 additions & 0 deletions src/Tags/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Statamic\Facades\Site;
use Statamic\Facades\URL;
use Statamic\View\Antlers\Language\Runtime\LiteralReplacementManager;
use Statamic\View\Antlers\Language\Runtime\RuntimeParser;
use Statamic\View\Antlers\Language\Runtime\StackReplacementManager;
use Statamic\View\State\CachesOutput;

Expand Down Expand Up @@ -68,6 +69,10 @@ private function isEnabled()
return false;
}

if ($this->parser instanceof RuntimeParser && $this->parser->instrumenting()) {
return false;
}

return true;
}

Expand Down
31 changes: 27 additions & 4 deletions src/Tags/ComponentProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\View\ComponentAttributeBag;
use Illuminate\View\DynamicComponent;
use ReflectionClass;
use Statamic\View\Instrumentation\InstrumentationState;
use Throwable;

class ComponentProxy extends Tags
Expand All @@ -35,11 +36,15 @@ public function setParameters($parameters)
public function index()
{
$___obLevel = ob_get_level();
$__env = null;
$renderIncremented = false;
$componentStackPushed = false;

try {
$__env = $this->context['__env'] ?? view();

$__env->incrementRender();
$renderIncremented = true;

$componentName = $this->params['component_name___'];
$tagCompiler = $this->makeComponentTagCompiler();
Expand Down Expand Up @@ -93,22 +98,38 @@ public function index()
]);

self::$componentStack[] = [$component, $contextData];
$componentStackPushed = true;

echo $this->parse($contextData);
echo InstrumentationState::withoutInstrumentation(
fn () => $this->parse($contextData)
);
}

$result = $__env->renderComponent();
$result = InstrumentationState::withinComponentView(
fn () => $__env->renderComponent()
);

$__env->decrementRender();
$renderIncremented = false;
$__env->flushStateIfDoneRendering();

if ($this->content) {
if ($componentStackPushed) {
array_pop(self::$componentStack);
$componentStackPushed = false;
}

return ltrim($result);
} catch (Throwable $e) {
$this->handleViewException($e, $___obLevel);
} finally {
if ($componentStackPushed) {
array_pop(self::$componentStack);
}

if ($renderIncremented && $__env !== null) {
$__env->decrementRender();
$__env->flushStateIfDoneRendering();
}
}
}

Expand All @@ -125,7 +146,9 @@ public function componentSlot()

$__env->slot($slot, null, $this->escapeBoundAttributes($context));

echo $this->parse($contextData);
echo InstrumentationState::withoutInstrumentation(
fn () => $this->parse($contextData)
);

$__env->endSlot();
} catch (Throwable $e) {
Expand Down
29 changes: 28 additions & 1 deletion src/Tags/Concerns/RendersViews.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
namespace Statamic\Tags\Concerns;

use Illuminate\Support\HtmlString;
use Statamic\View\Antlers\AntlersString;
use Statamic\View\Antlers\Language\Facades\Runtime;
use Statamic\View\Instrumentation\InstrumentationState;

trait RendersViews
{
protected ?string $instrumentedSlotContent = null;

protected function viewName($partial)
{
$partial = str_replace('/', '.', $partial);
Expand Down Expand Up @@ -49,7 +54,11 @@ protected function shouldRender(): bool

protected function getSlotContent(): HtmlString|string
{
$content = trim($this->parse());
$parsed = $this->parse();

[$content, $this->instrumentedSlotContent] = Runtime::splitMarkers(
$parsed instanceof AntlersString ? $parsed->instrumented() : (string) $parsed
);

if ($this->isAntlersBladeComponent()) {
return new HtmlString($content);
Expand All @@ -58,6 +67,24 @@ protected function getSlotContent(): HtmlString|string
return $content;
}

protected function withInstrumentedSlot(array $variables): array
{
$key = InstrumentationState::twinsKey();
$outputs = $variables[$key] ?? [];

unset($outputs['slot'], $variables[$key]);

if ($this->instrumentedSlotContent !== null && is_string($variables['slot'] ?? null)) {
$outputs['slot'] = [$variables['slot'], $this->instrumentedSlotContent];
}

if ($outputs !== []) {
$variables[$key] = $outputs;
}

return $variables;
}

/**
* The {{ exists }} tag.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Tags/IncludeTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ protected function render($view)

$cascade = Cascade::toArray();

$scope = array_merge(
$scope = $this->withInstrumentedSlot(array_merge(
$this->params->bool('cascade') ? $cascade : [],
$data,
$this->resolveSlots($parameters, $data, $isBlade),
Expand All @@ -92,7 +92,7 @@ protected function render($view)
'__frontmatter' => $data,
],
$isBlade ? [self::CONTEXT_KEY => true] : []
);
));

$hadViews = array_key_exists('views', $cascade);
$viewsState = $cascade['views'] ?? null;
Expand Down
4 changes: 2 additions & 2 deletions src/Tags/Partial.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ protected function render($partial)

$context = array_diff_key($this->context->all(), array_flip(IncludeTag::VIEW_DATA_KEYS));

$variables = array_merge($context, $this->params->all(), [
$variables = $this->withInstrumentedSlot(array_merge($context, $this->params->all(), [
'__frontmatter' => $this->params->all(),
'slot' => $this->isPair ? $this->getSlotContent() : null,
]);
]));

return view($this->viewName($partial), $variables)
->withoutExtractions()
Expand Down
11 changes: 9 additions & 2 deletions src/View/Antlers/Antlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Statamic\Contracts\View\Antlers\Parser;
use Statamic\View\Antlers\Language\Parser\IdentifierFinder;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;
use Statamic\View\Instrumentation\InstrumentationState;

class Antlers
{
Expand All @@ -29,17 +30,23 @@ public function usingParser(Parser $parser, Closure $callback)

public function parse($str, $variables = [], $trusted = false)
{
$parser = $this->parser();
$previousState = GlobalRuntimeState::$isEvaluatingUserData;
GlobalRuntimeState::$isEvaluatingUserData = ! $trusted;

try {
return $parser->parse($str, $variables);
return $this->parser()->parse($str, $variables);
} finally {
GlobalRuntimeState::$isEvaluatingUserData = $previousState;
}
}

public function parseWithoutInstrumentation($str, $variables = [], $trusted = false)
{
return InstrumentationState::withoutInstrumentation(
fn () => $this->parse($str, $variables, $trusted)
);
}

/**
* Iterate over an array and parse the string/template for each.
*
Expand Down
Loading
Loading