Skip to content

Instantly share code, notes, and snippets.

@RianFuro
Last active September 13, 2021 08:54
Show Gist options
  • Save RianFuro/a9bfb8060ea86385350f8c5b7ce26c0b to your computer and use it in GitHub Desktop.
Save RianFuro/a9bfb8060ea86385350f8c5b7ce26c0b to your computer and use it in GitHub Desktop.
Extend the `<x-slot>` and `@slot` directives for laravel's blade engine with scoping capabilities
<?php
class BladeCompiler extends \Illuminate\View\Compilers\BladeCompiler
{
static array $slotStack = [];
/**
* Compile the component tags.
*
* @param string $value
* @return string
*/
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
return $value;
}
return (new ComponentTagCompiler(
$this->classComponentAliases, $this->classComponentNamespaces, $this
))->compile($value);
}
protected function compileSlot($expression)
{
[$slot, $data] = strpos($expression, ',') !== false
? array_map('trim', explode(',', substr($expression, 1, -1)))
: [trim($expression, '()'), ''];
$isScoped = preg_match('/\((?<args>.+)\)( use (?<uses>\(.+\)))?/', $data, $matches);
static::$slotStack[] = compact('isScoped');
if ($isScoped) {
$uses = isset($matches['uses'])
? array_map('trim', explode(',', $matches['uses']))
: [];
array_push($uses, '$__env');
$uses = implode(', ', $uses);
return implode('\n', [
"<?php \$__env->slot({$slot}, function ({$matches['args']}) use ({$uses}) { ?>"
]);
} else return "<?php \$__env->slot{$expression}; ?>";
}
/**
* Compile the end-slot statements into valid PHP.
*
* @return string
*/
protected function compileEndSlot()
{
$slotMeta = array_pop(static::$slotStack);
return $slotMeta['isScoped']
? '<?php }); ?>'
: '<?php $__env->endSlot(); ?>';
}
}
<?php
class ComponentTagCompiler extends \Illuminate\View\Compilers\ComponentTagCompiler
{
public function compileSlots(string $value)
{
$value = preg_replace_callback('/<\s*x[\-\:]slot\s+(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))(\s+bindings=(?<bindings>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))?\s*>/', function ($matches) {
$isScoped = array_key_exists('bindings', $matches);
$name = $this->stripQuotes($matches['name']);
if ($matches[1] !== ':') {
$name = "'{$name}'";
}
if ($isScoped) {
$bindings = trim($matches['bindings'], '"');
return " @slot({$name}, ({$bindings}))";
} else return " @slot({$name}) ";
}, $value);
return preg_replace('/<\/\s*x[\-\:]slot[^>]*>/', ' @endslot', $value);
}
}
<?php
use Illuminate\View\DynamicComponent;
use Illuminate\Support\ServiceProvider;
class SomeServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->extend(\Illuminate\View\Compilers\BladeCompiler::class, function ($_, $app) {
return tap(new BladeCompiler($app['files'], $app['config']['view.compiled']), function ($blade) {
$blade->component('dynamic-component', DynamicComponent::class);
});
});
}
}
@RianFuro
Copy link
Author

RianFuro commented Sep 13, 2021

Usage:

{{-- some-view.blade.php --}}
<x-component>
  <x-slot name="slotname" bindings="$var1, $var2">
    <b>{{ $var1 }}, {{ $var2 }}!</b>
  </x-slot>
</x-component>
{{-- component.blade.php --}}
@isset($slotname)
  {{ $slotname('Hello', 'world') }}
@endisset

Notes:

  • The compiler for <x-slot> is super simple and cannot deal with reordering the arguments, so <x-slot bindings="" name=""> will not work!
  • This change is also rewriting how slots are handled to facilitate scoped slots. Besides <x-slot> the old @slot syntax should also work like so:
    @component('component')
      @slot('slotname', ($var1, $var2))
        <b>{{ $var1 }}, {{ $var2 }}!</b>
      @endslot
    @endcomponent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment