Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/Blaze.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* @method static \Livewire\Blaze\Tokenizer\Tokenizer tokenizer()
* @method static \Livewire\Blaze\Parser\Parser parser()
* @method static \Livewire\Blaze\Folder\Folder folder()
* @method static \Livewire\Blaze\Imprinter\Imprinter imprinter()
* @method static array flushFoldedEvents()
* @see \Livewire\Blaze\BlazeManager
*/
Expand Down
18 changes: 17 additions & 1 deletion src/BlazeManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Livewire\Blaze\Events\ComponentFolded;
use Livewire\Blaze\Nodes\ComponentNode;
use Livewire\Blaze\Imprinter\Imprinter;
use Livewire\Blaze\Tokenizer\Tokenizer;
use Illuminate\Support\Facades\Event;
use Livewire\Blaze\Memoizer\Memoizer;
Expand All @@ -27,6 +28,7 @@ public function __construct(
protected Walker $walker,
protected Folder $folder,
protected Memoizer $memoizer,
protected Imprinter $imprinter,
) {
Event::listen(ComponentFolded::class, function (ComponentFolded $event) {
$this->foldedEvents[] = $event;
Expand Down Expand Up @@ -109,7 +111,16 @@ public function compile(string $template): string
array_pop($dataStack);
}

return $this->memoizer->memoize($this->folder->fold($node));
foreach ([
$this->imprinter->imprint(...),
$this->folder->fold(...),
$this->imprinter->restore(...),
$this->memoizer->memoize(...),
] as $process) {
$node = $process($node);
}

return $node;
},
);

Expand Down Expand Up @@ -167,4 +178,9 @@ public function folder(): Folder
{
return $this->folder;
}

public function imprinter(): Imprinter
{
return $this->imprinter;
}
}
4 changes: 4 additions & 0 deletions src/BlazeServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Livewire\Blaze;

use Livewire\Blaze\Directive\BlazeDirective;
use Livewire\Blaze\Imprinter\Imprinter;
use Livewire\Blaze\Tokenizer\Tokenizer;
use Illuminate\Support\ServiceProvider;
use Livewire\Blaze\Memoizer\Memoizer;
Expand Down Expand Up @@ -37,6 +38,9 @@ protected function registerBlazeManager(): void
new Memoizer(
componentNameToPath: fn ($name) => $bladeService->componentNameToPath($name),
),
new Imprinter(
componentNameToPath: fn ($name) => $bladeService->componentNameToPath($name),
),
));

$this->app->alias(BlazeManager::class, Blaze::class);
Expand Down
125 changes: 125 additions & 0 deletions src/Imprinter/Imprinter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

namespace Livewire\Blaze\Imprinter;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\File;
use Livewire\Blaze\Nodes\ComponentNode;
use Livewire\Blaze\Nodes\Node;
use Livewire\Blaze\Nodes\TextNode;

class Imprinter
{
protected $componentNameToPath;
protected $imprintPlaceholders = [];
protected $cacheDirectory;
protected $cacheNamespace = 'blaze';

public function __construct(callable $componentNameToPath)
{
$this->componentNameToPath = $componentNameToPath;
$this->cacheDirectory = storage_path('framework/views/livewire/blaze/components');
Blade::anonymousComponentPath($this->cacheDirectory, $this->cacheNamespace);
}

public function imprint(Node $node): Node
{
if (! $node instanceof ComponentNode) {
return $node;
}

$this->capture($node);

return $node;
}

public function restore(Node $node): Node
{
if (! $node instanceof TextNode) {
return $node;
}

// Look for IMPRINT_PLACEHOLDER throughout $node->content and capture the full string
$node->content = preg_replace_callback('/IMPRINT_PLACEHOLDER_[a-zA-Z0-9]{10}/i', function (array $matches) {
$imprintPlaceholder = $matches[0];

$imprint = $this->imprintPlaceholders[$imprintPlaceholder];
$attributes = $imprint['attributes'];
$content = $imprint['content'];

foreach ($attributes as $name => $value) {
$content = preg_replace('/\$'.$name.'(?![a-zA-Z0-9_])/', '\''.$value.'\'', $content);
}

return $content;
}, $node->content);

return $node;
}

public function getAttributes(string $imprintPlaceholder): array
{
return $this->imprintPlaceholders[$imprintPlaceholder]['attributes'] ?? [];
}

public function getContent(string $imprintPlaceholder): string
{
return $this->imprintPlaceholders[$imprintPlaceholder]['content'];
}

public function storeAttributes(string $imprintPlaceholder, array $attributes): void
{
$this->imprintPlaceholders[$imprintPlaceholder]['attributes'] = $attributes;
}

protected function capture(ComponentNode $node): void
{
$componentPath = ($this->componentNameToPath)($node->name);

if (empty($componentPath) || ! file_exists($componentPath)) {
return;
}

$source = file_get_contents($componentPath);

preg_match_all('/(\s*)@imprint\((.*?)\)(.*?)@endimprint/s', $source, $matches);

if (empty($matches[0])) {
return;
}

$modifiedSource = $source;

foreach ($matches[0] as $index => $match) {
$imprintBlock = $matches[0][$index];
$whitespace = $matches[1][$index];
$attributes = $matches[2][$index];
$content = $matches[3][$index];

$placeholder = 'IMPRINT_PLACEHOLDER_' . str()->random(10);

$output = $whitespace;
$output .= '<'.'?php \Livewire\Blaze\Blaze::imprinter()->storeAttributes(\'' . $placeholder . '\', ' . $attributes . '); ?'.'>';
$output .= $placeholder;

$modifiedSource = str_replace($imprintBlock, $output, $modifiedSource);

$this->imprintPlaceholders[$placeholder] = [
'attributes' => [],
'content' => $content,
];
}

$name = $node->name;
$path = str_replace('.', '/', $name);

$directory = $this->cacheDirectory . '/' . str($path)->beforeLast('/');
$filename = str($path)->afterLast('/')->value() . '.blade.php';

File::ensureDirectoryExists($directory);

File::put($directory . '/' . $filename, $modifiedSource);

$node->name = $this->cacheNamespace . '::' . $name;
}
}
22 changes: 22 additions & 0 deletions tests/ImprintTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

describe('imprint components', function () {
beforeEach(function () {
app('blade.compiler')->anonymousComponentPath(__DIR__ . '/fixtures/components');

\Illuminate\Support\Facades\Artisan::call('view:clear');
});

it('can imprint components with nested components that use the error bag', function () {
$input = '<x-field wire:model="search">Search</x-field>';
$output = <<<'HTML'
<div>
<div>Search</div>

<x-error name="search" />
</div>
HTML;

expect(app('blaze')->compile($input))->toContain('<x-error :name="\'search\'" />');
});
});
13 changes: 13 additions & 0 deletions tests/fixtures/components/error.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@props([
'name' => null,
])

@php
$message = isset($errors) ? $errors->first($name) : null;
@endphp

<div {{ $attributes->class($message ? 'mt-3 text-sm font-medium text-red-500 dark:text-red-400' : 'hidden') }}>
<?php if ($message) : ?>
{{ $message }}
<?php endif; ?>
</div>
13 changes: 13 additions & 0 deletions tests/fixtures/components/field.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@blaze

@props([
'name' => $attributes->whereStartsWith('wire:model')->first(),
])

<div>
<div>{{ $slot }}</div>

@imprint(['name' => $name])
<x-error :name="$name" />
@endimprint
</div>