Skip to content

Commit 487b465

Browse files
Custom component compiler callbacks (#29)
* Test cleanup * Custom compiler callback support * Update README.md
1 parent 7ab4ca5 commit 487b465

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+230
-145
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [v1.4.0](https://github.com/Stillat/dagger/compare/v1.3.1...v1.4.0) - 2025-06-15
4+
5+
- Adds the ability to compile components using a callback function
6+
37
## [v1.3.1](https://github.com/Stillat/dagger/compare/v1.3.0...v1.3.1) - 2025-05-07
48

59
- Corrects dependencies for Laravel 12 (#28)

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ The main visual difference when working with Dagger components is the use of the
7979
- [Disabling Compile Time Rendering on a Component](#disabling-compile-time-rendering-on-a-component)
8080
- [Enabling/Disabling Optimizations on Classes or Methods](#enablingdisabling-optimizations-on-classes-or-methods)
8181
- [Notes on Compile Time Rendering](#notes-on-compile-time-rendering)
82+
- [Component Compiler Callbacks](#component-compiler-callbacks)
83+
- [Hanling Inner Content](#handling-inner-content)
84+
- [Wildcard Component Patterns](#wildcard-component-patterns)
8285
- [The View Manifest](#the-view-manifest)
8386
- [License](#license)
8487

@@ -1654,6 +1657,71 @@ class MyAwesomeClass
16541657
- You should *never* attempt to force a component to render at compile time, outside of applying the `EnableOptimization` or `DisableOptimization` attributes to your own helper methods
16551658
- If an exception is raised while rendering a component at compile time, CTR will be disabled for that component and the compiler will revert to normal behavior
16561659

1660+
## Component Compiler Callbacks
1661+
1662+
There are times you way wish to compile a component *without* a corresponding view file, similar to a directive. This can be accomplished by registering your custom component compiler callback using the `Compiler::compileComponent` method:
1663+
1664+
```php
1665+
<?php
1666+
1667+
use Stillat\Dagger\Facades\Compiler;
1668+
1669+
Compiler::compileComponent('c:mycomponent', function ($node) {
1670+
return 'My Component';
1671+
});
1672+
1673+
```
1674+
1675+
Whenever the `<c-mycomponent />` component is compiled our custom callback will be invoked and that used as the compiled output.
1676+
1677+
### Handling Inner Content
1678+
1679+
For component tag pairs, the template compiler will provide the pre-compiled inner content as the second argument to the callback:
1680+
1681+
```php
1682+
<?php
1683+
1684+
use Stillat\Dagger\Facades\Compiler;
1685+
1686+
Compiler::compileComponent('c:mycomponent', function ($node, $innerContent) {
1687+
return '<div class="simple-wrapper">'.$innerContent.'</div>';
1688+
});
1689+
1690+
```
1691+
1692+
This is useful if you would like to wrap the compiled output and not have to manage compiling the inner content yourself:
1693+
1694+
```blade
1695+
<c-mycomponent>
1696+
<x-alert title="Other Components" />
1697+
</c-mycomponent>
1698+
```
1699+
1700+
### Wildcard Component Patterns
1701+
1702+
You may use wildcards when registering your compiler callbacks:
1703+
1704+
```php
1705+
<?php
1706+
1707+
use Illuminate\Support\Str;
1708+
use Stillat\Dagger\Facades\Compiler;
1709+
1710+
Compiler::compileComponent('c:stack:*', function ($node) {
1711+
$stackName = Str::after($node->tagName, ':');
1712+
1713+
// ...
1714+
});
1715+
1716+
```
1717+
1718+
Our callback would now be invoked for all of the following:
1719+
1720+
```blade
1721+
<s-stack:styles />
1722+
<c-stack:scripts />
1723+
```
1724+
16571725
## The View Manifest
16581726

16591727
You may have noticed JSON files being written to your compiled view folder. These files are created by Dagger's "view manifest", which tracks dependencies of compiled views.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace Stillat\Dagger\Compiler\Concerns;
4+
5+
use Closure;
6+
use Illuminate\Support\Str;
7+
use Stillat\BladeParser\Nodes\Components\ComponentNode;
8+
use Stillat\Dagger\Exceptions\InvalidArgumentException;
9+
10+
trait ManagesComponentCompilerCallbacks
11+
{
12+
protected array $componentCompilers = [];
13+
14+
public function compileComponent(string $component, Closure $callback): static
15+
{
16+
$prefix = Str::before($component, ':');
17+
$component = Str::after($component, ':');
18+
19+
if (! $prefix || ! in_array($prefix, $this->componentNamespaces)) {
20+
throw new InvalidArgumentException("[$component] must have a registered component prefix.");
21+
}
22+
23+
if (! $component) {
24+
throw new InvalidArgumentException("[$component] is not a valid component name.");
25+
}
26+
27+
return $this->compileComponentWithPrefix(
28+
$prefix,
29+
$component,
30+
$callback
31+
);
32+
}
33+
34+
public function compileComponentWithPrefix(string $prefix, string $component, Closure $callback): static
35+
{
36+
if (! array_key_exists($prefix, $this->componentCompilers)) {
37+
$this->componentCompilers[$prefix] = [];
38+
}
39+
40+
$this->componentCompilers[$prefix][$component] = $callback;
41+
42+
return $this;
43+
}
44+
45+
protected function compileComponentCallback(ComponentNode $node): mixed
46+
{
47+
if (! array_key_exists($node->componentPrefix, $this->componentCompilers)) {
48+
return false;
49+
}
50+
51+
/**
52+
* @var $pattern
53+
* @var Closure $callback
54+
*/
55+
foreach ($this->componentCompilers[$node->componentPrefix] as $pattern => $callback) {
56+
if (! Str::is($pattern, $node->tagName)) {
57+
continue;
58+
}
59+
60+
ray($pattern);
61+
62+
return $callback->call(
63+
$this,
64+
$node,
65+
$this->compileNodes($node->childNodes ?? [])
66+
);
67+
}
68+
69+
return false;
70+
}
71+
}

src/Compiler/TemplateCompiler.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Stillat\Dagger\Compiler\Concerns\CompilesPhp;
2525
use Stillat\Dagger\Compiler\Concerns\CompilesSlots;
2626
use Stillat\Dagger\Compiler\Concerns\CompilesStencils;
27+
use Stillat\Dagger\Compiler\Concerns\ManagesComponentCompilerCallbacks;
2728
use Stillat\Dagger\Compiler\Concerns\ManagesComponentCtrState;
2829
use Stillat\Dagger\Compiler\Concerns\ManagesExceptions;
2930
use Stillat\Dagger\Exceptions\CompilerException;
@@ -54,6 +55,7 @@ final class TemplateCompiler
5455
CompilesPhp,
5556
CompilesSlots,
5657
CompilesStencils,
58+
ManagesComponentCompilerCallbacks,
5759
ManagesComponentCtrState,
5860
ManagesExceptions;
5961

@@ -401,6 +403,12 @@ protected function compileNodes(array $nodes): string
401403
$currentView = $this->manifest->last();
402404
$currentViewPath = $currentView?->getPath();
403405

406+
if ($callbackResult = $this->compileComponentCallback($node)) {
407+
$compiled .= $callbackResult;
408+
409+
continue;
410+
}
411+
404412
if ($this->isDynamicComponent($node)) {
405413
$compiled .= $this->compileDynamicComponentScaffolding($node, $currentViewPath ?? '');
406414

src/Facades/Compiler.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Stillat\Dagger\Facades;
44

5+
use Closure;
56
use Illuminate\Support\Facades\Facade;
67
use Stillat\Dagger\Compiler\CompilerOptions;
78
use Stillat\Dagger\Compiler\TemplateCompiler;
@@ -11,6 +12,8 @@
1112
* @method static void addComponentPath(string $namespace, string $path)
1213
* @method static void registerComponentPath(string $componentPrefix, string $path, ?string $namespace = null)
1314
* @method static string compile(string $template)
15+
* @method static TemplateCompiler compileComponent(string $component, Closure $callback)
16+
* @method static TemplateCompiler compileComponentWithPrefix(string $prefix, string $component, Closure $callback)
1417
* @method static string compileWithLineNumbers(string $template)
1518
* @method static string getDynamicComponentPath(string $proxyName, string|null $componentName = null)
1619
* @method static bool compiledDynamicComponentExists(string $proxyName, string $componentName)

tests/AttributeCache/AttributeForwardingTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
<?php
22

3-
use Stillat\Dagger\Tests\CompilerTestCase;
4-
5-
uses(CompilerTestCase::class);
6-
73
test('forwarded attributes can be cached', function () {
84
$template = <<<'BLADE'
95
<c-cache.root #inner:title="The Title" />

tests/AttributeCache/DynamicComponentsAttributeForwardingTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
<?php
22

3-
use Stillat\Dagger\Tests\CompilerTestCase;
4-
5-
uses(CompilerTestCase::class);
6-
73
test('forwarded attributes can be cached inside dynamic components', function () {
84
$template = <<<'BLADE'
95
<c-dynamic-component component="cache.root" #inner:title="The Title" />

tests/AttributeCache/DynamicComponentsForwardedSlotsTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
<?php
22

3-
use Stillat\Dagger\Tests\CompilerTestCase;
4-
5-
uses(CompilerTestCase::class);
6-
73
test('forwarded named slots can be cached within dynamic components', function () {
84
$template = <<<'BLADE'
95
@for ($i = 0; $i < 3; $i++)

tests/AttributeCache/DynamicComponentsNamedSlotsTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
<?php
22

3-
use Stillat\Dagger\Tests\CompilerTestCase;
4-
5-
uses(CompilerTestCase::class);
6-
73
test('dynamic named slot variables bust the cache within dynamic components', function () {
84
$template = <<<'BLADE'
95
@for ($i = 0; $i < 2; $i++)

tests/AttributeCache/DynamicComponentsSlotsTest.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
<?php
22

33
use Illuminate\Support\Str;
4-
use Stillat\Dagger\Tests\CompilerTestCase;
5-
6-
uses(CompilerTestCase::class);
74

85
test('it replaces basic slot content within dynamic components', function () {
96
$template = <<<'BLADE'

0 commit comments

Comments
 (0)