angus-mcritchie / blade-repeated-directive
Lightning-Fast Blade Components with `@boost` Directive
Fund package maintenance!
Angus McRitchie
Requires
- php: ^8.1
- illuminate/contracts: ^10.0||^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^2.9||^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^10.0.0||^9.0.0||^8.22.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.3||^2.0
- phpstan/phpstan-deprecation-rules: ^1.1||^2.0
- phpstan/phpstan-phpunit: ^1.3||^2.0
README
Adds a @boost
Blade directive to your Laravel application. Caches the HTML rendered from Blade code. Optionally you can perform a simple str_replace
for any variables passed to the directive.
Table of Contents
- Installation
- Signature
- Parameters
- Usage
- Escaping
- Cache Store
- Configuration
- Benchmarks
- Testing
- Changelog
- Contributing
- Security Vulnerabilities
- Credits
- License
Installation
You can install the package via composer:
composer require angus-mcritchie/blade-boost-directive
Signature
@boost(string|array $key, array $options = []) <x-component /> @endboost @boost(array $options = []) <x-component /> @endboost
Parameters
$key: string|array // Required, arrays will be joined with a "."
$options: [
'key': string|array, // see above (required)
'store': string, // The cache store to use (default: array)
'raw': bool, // Whether to escape the HTML or not (default: false)
'replace': [ // Variables to replace in the HTML (optional)
'{foo}' => 'bar'
],
];
Usage
The @boost
directive can be used to wrap any Blade code. It will render the blade code once and cache the HTML and can optionally replace any variables passed to the directive via the second argument.
Repeating Code Use Case
A common use case would be that you're rendering a page of <x-post.card />
components which looks like this:
@foreach($posts as $post) <x-card> <x-link href="{{ route('post.show', $post) }}"> <x-image src="{{ $post->image }}" /> </x-link> <x-card.body> <x-heading as="h3">{{ $post->title }}</x-heading> <x-text>{{ $post->description }}</x-text> </x-card.body> <x-badge class="absolute top-0 left-0">{{ $post->author }}</x-badge> <x-card.footer> <x-button href="{{ route('post.show', $post) }}"> Read More </x-button> </x-card.footer> </x-card> @endforeach
That is a total of 9 components per post, if we have 30 per page, that is 270 components in total and there are only 4 variables for the whole card.
Let's see how we can use the @boost
directive to speed this up.
@foreach($posts as $post) @boost([ 'key' => 'post-card', 'replace' => [ '{title}' => $post->title, '{name}' => $post->name, '{url}' => route('post.show', $post), '{image}' => $post->image, '{author}' => $post->author, '{description}' => $post->description ] ]) <x-card> <x-link href="{url}"> <x-image src="{image}" /> </x-link> <x-card.body> <x-heading as="h3">{title}</x-heading> <x-text>{description}</x-text> </x-card.body> <x-badge class="absolute top-0 left-0">{author}</x-badge> <x-card.footer> <x-button href="{url}"> Read More </x-button> </x-card.footer> </x-card> @endboost @endforeach
Wrapping the whole component in the @boost
directive will store the HTML in the cache, then replace the passed variables with the values from the $post
object and results in Blade only rendering 1 component instead of 270 🚀.
You could use the file
cache store and render zero components (after the first request) but you'll need to clear the cache when you make changes to the component.
But I Have an if
Statement in There!
If you want to conditionally show the badge, you'll want to update the key to include the condition so that the cache is different for each condition.
Passing the key as an array with all your conditions, @boost
will join the array with a .
and use that as the cache key.
@foreach($posts as $post) @boost([ 'key' => ['post-card', $post->show_author_badge], // changed 'replace' => [ '{name}' => $post->name, '{author}' => $post->author, '{url}' => route('post.show', $post), ] ]) <x-card> <x-heading as="h3">{title}</x-heading> @if($post->show_author_badge) <x-badge class="absolute top-0 left-0">{author}</x-badge> @endif <x-button href="{url}"> Read More </x-button> </x-card> @endboost @endforeach
Large Component Use Case
Another common use case would be that you're rendering a page with a single components with many smaller other components.
<x-footer> <x-grid cols="4"> <x-grid.item> <x-link href="{{ route('home') }}"> <x-image src="{{ asset('images/logo.png') }}" /> </x-link> </x-grid.item> <x-grid.item> <x-list> <x-list.item> <x-link href="{{ route('home') }}"> Home </x-link> </x-list.item> <x-list.item> <x-link href="{{ route('about') }}"> About </x-link> </x-list.item> <x-list.item> <x-link href="{{ route('contact') }}"> Contact </x-link> </x-list.item> </x-list> <!-- more columns, links etc --> </x-grid.item> </x-grid> <div class="text-center"> <x-text> © {{ date('Y') }} {{ config('company.name') }} </x-text> </div> </x-footer>
To speed this up we can use the @boost
directive to cache the HTML for the footer and only render it once.
By default, @boost
uses the array
cache store, which is the fastest cache store available but is not persistent, this use case you'll want to use the file
cache store.
You can do this by passing a third argument to the @boost
directive.
Let's see how we can use the @boost
directive to speed this up.
@boost([ 'key' => 'footer', 'store' => 'file' 'replace' => [ '{year}' => date('Y') ], ]) <x-footer> <x-grid cols="4"> <x-grid.item> <x-link href="{{ route('home') }}"> <x-image src="{{ asset('images/logo.png') }}" /> </x-link> </x-grid.item> <x-grid.item> <x-list> <x-list.item> <x-link href="{{ route('home') }}"> Home </x-link> </x-list.item> <x-list.item> <x-link href="{{ route('about') }}"> About </x-link> </x-list.item> <x-list.item> <x-link href="{{ route('contact') }}"> Contact </x-link> </x-list.item> </x-list> <!-- more columns, links etc --> </x-grid.item> </x-grid> <div class="text-center"> <x-text> © {year} {{ config('company.name') }} </x-text> </div> </x-footer> @endboost
Now, Blade will just render this component once and store the HTML in the cache. The next time the page is loaded, the pre-rendered component will be stored in the file
cache store. This is useful if you want to speed up large, but simple components that are used on every page and only rendered once.
Simple Small Repeating Use Case
Alternative syntax you can pass the key as the first parameter which is handle for simple cases where you don't need to pass any options, or you just prefer to pass the key as the first parameter.
@boost('recently-added-badge') <x-badge> Recently Added </x-badge> @endboost
Mind you that the @boost
directive will use the Cache::rememberForever()
under the hood so it's up to you to clear the cache when you make changes to the component.
Escaping
The @boost
directive will escape the HTML by default using Laravel's e() function. If you want to render the HTML without escaping, you can pass the raw
option to the directive. This will allow you to output raw HTML without any escaping, which can be useful in certain scenarios where you trust the content being rendered.
@boost([ 'key' => 'raw-html', 'replace' => [ '{foo}' => 'bar' ], 'raw' => true ])
Cache Store
The @boost
directive will prefix the cache key with the blade-boost-directive.
prefix which is configurable in the config file. This is to avoid collisions with other packages or parts of your application.
If you wish to clear a specific cache key, you can use the following.
// get the real cache key @boost uses $key = \AngusMcRitchie\BladeBoostDirective\Boost::prefix('my-key'); cache()->store('array')->forget($key);
Configuration
You can publish the configuration file with the following command:
php artisan vendor:publish --tag="blade-boost-directive-config"
This is the contents of the published config file:
return [ /** * Enable or disable the package. * If disabled, the package will not register any Blade directives. */ 'enabled' => env('BLADE_BOOST_ENABLED', true), /** * The default cache store to use. * This is used when no cache store is specified in the directive. */ 'default_cache_store' => env('BLADE_BOOST_DIRECTIVE_DEFAULT_CACHE_STORE', 'array'), /** * The prefix to use for cache keys. * This is used to avoid key collisions with other packages or parts of your application. */ 'prefix' => env('BLADE_BOOST_DIRECTIVE_PREFIX', 'blade-boost-directive.'), ];
Benchmarks
Our benchmarks show the performance improvements you can expect when using the @boost
directive. The benchmarks are not exhaustive and only serve as a demonstration of the performance improvements you can expect.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.