-
Notifications
You must be signed in to change notification settings - Fork 74
Flux Capacitor: A runtime attribute cache for Flux components #1498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Should be good to go for initial rounds of testing and review 💪 |
@JohnathonKoster I haven't had a chance to test this PR out, but I love the name Flux Capacitor. Would there be value in making default caching a |
I'd have to spend some more time thinking about that - but my immediate thought is you'd probably want it to be running both locally and in production. The way this is implemented is it wouldn't have any impact on view caching itself (or requiring extra steps to bust the cache), nor does it store anything within an actual cache store - just an in-memory cache for that specific request. |
If we have some code before the the first aware/props, it's probably important for adjusting state before those things evaluate. An example of this is the Avatar component. Instead of having to rethink how all of that works, this change simply hoists that content so the component does not need to be reworked in order to use "optimized"
Thought of a better way to do this
Only really needing that value in component compiler for now
This will be used to process aware/props/etc., even for cached components. This change currently requires manual demarking like: ``` @setup .... @endsetup ``` Some of this can be done automatically later
@JohnathonKoster I some of the components I am using, changing the livewire / component / blade files and first tried Am I missing something, or you still working through things. |
@nfauchelle Which components are you working with, and do you have a brief example of how they are being used? |
I was playing with I've pulled in your recent changes since my comment, and reset the blade components removing any @cached and Flux::cache I added. I then created a flux-test.blade.php file, pulling in the css/js needed for the site. Loads fast, and 1 view. I added
Which bumps it up to 13 views rendered. Adding Adding several more options (this comes from the FluxUI docs)
gives us 32 views and the page speed is now over twice as long. I notice when I open the select all the options say Photography. Maybe that is because the docs are missing the wire:key. Simply doing this really shows the slow down and high view count.
Do I need to change something in the templates? (since the stubs are in the repo should they be updated? Appreciate you looking into the speed. |
Leave the original content alone temporarily so Livewire can get in there and insert its markers the way it wants to. If things are wrapped in the uncached callbacks too early, Livewire's patterns may not match correctly inside attributes, etc.
Prevents pushing markers to the end of the compiled document by putting the swap placeholder back where it was extracted. Consistent newlines
I've left the stubs in the repo alone for now so the team can decided if/how they want to update. In the case of the select option, if you add the @cached
@aware([ 'variant' ])
@props([
'variant' => 'default',
])
// ... you will get the behavior of each option repeating the same slot content. This is happens because the cache layer stores the output for each unique set of attributes. We can tell the cache system to swap out the slot content, even for cached instances, by wrapping that part of the component in the // ...
<flux:with-field :$attributes>
<flux:delegate-component :component="'select.option.variants.' . $variant"><flux:uncached>{{ $slot }}</flux:uncached></flux:delegate-component>
</flux:with-field> That would prevent additional views being loaded for the same set of attributes, while still swapping out the slot contents on-the-fly. There also exists the @optimized
@aware([ 'variant' ])
@props([
'variant' => 'default',
])
// ... When using I'm in the process of updating the PR description, since it is out of date, but hope this helps in the meantime! |
Thanks @JohnathonKoster I will pull in these recent changes and have another play. Which version of flux pro do you have in your composer? I've been using the latest, I wonder if that could be causing me issues since the non pro (this repo) might be expecting flux pro 2.1.2 or something, or have you rebased recently? |
I'm using latest 2.x for Pro 👍 |
Thanks. I've pulled in the latest changes. Reset all my stubs to default.
|
@nfauchelle Laravel 11 shouldn't make a difference. Hard to say what's happening in your setup without seeing more. If you have a repo (a private one would work as well, if has pro stubs/etc. or there are things you'd rather not make public) you can share I'd be more than happy to dive in and help debug! |
@nfauchelle can I offer a suggestion? I would suggest holding off testing until what we are proposing is ready. You're more than welcome to keep trying to get this running and testing it if you like. But, John, Caleb and myself are currently all working on these performances issue in both Flux and Livewire. The work John is doing here is going to be the base of the speed improvements in Flux, but it won't be something that users will need to be concerned about directly. We will be applying this caching, etc. to Flux's components once this is stable and merged into Flux. The thing that started all of this for us was due to selects being slow. By all means though, if you're still keen to play around with it knowing all of the above, then feel free. 🙂 |
No worries Josh, just let me know when it's ready for testing and I am more than happy to give feedback. |
@nfauchelle can do, that'd be great! 😁 |
Hey guys, just want to check in, feels like this is dead now but I am sure lots is happening behind the scenes and it's complex (I've listened to the pod cast). It is having an impact on things I am doing so wanted to see if there was any news. Thanks. |
@nfauchelle we're still working on it 🙂 not a lot more I can say at this stage |
fyi, I created a PR in Laravel 12.x that was merged on 2025-05-10 and was released with v12.14.0 on 2025-05-13 that decreases the compilation times of components by up to 80%. Also this was not backported to Laravel 11.x So if you have inconsistencies in compilation time between Laravel 11.x and Laravel 12.x, this might be the issue |
This PR introduces an alternative compiler for Flux. This new compiler is largely the same as the existing
FluxTagCompiler
,but uses custom Flux component directives in order to inject cache hooks into the compiled output.
The new compiler will emit the cache hooks for all components even though the cache system is opt-in per component. This is to enable simpler caching of delegate components, as well as prevent the need to parse/analyze components while compiling the main Blade views.
The following annotated output provides a high-level overview of the steps the new compiled output performs:
Enabling the Component Cache
The attribute cache is an opt-in feature per component. This decision was made to allow for strategic caching of components, as well as not interfere with any components developers may have already published within their existing projects.
The component cache may be enabled for a component by using either the
@cached
or@optimized
Flux compiler directives. The component's template must begin with one of these directives in order to enable the cache system:When that component is executed, it will now be cached by Flux's attribute cache. The cache system is very explicit, and the
$slot
and$value
output will remain the same across renderings if the attributes do not differ, even if the slot content is different. We can solve this by telling Flux not to cache specific sections of the component.We will solve the slot first using the
<flux:uncached></flux:uncached>
compiler component:Important
The
<flux:uncached>
component is not a real component. It is only considered at compile time. If the component does not have the attribute cache enabled, you may receive an error stating the component view could not be located.Now, when the component is rendered, the main structure of the component will remain cached, but the slot contents will be dynamically swapped out for you. We can do something similar for the
$value
inside the HTML attributes using the@uncached
/@enduncached
directive pair:If we were to render the following:
We would now receive unique output for all three renderings. However, this is probably not what we want in this scenario, as we would have three different cache entries because of the different
value
attributes. For some components, this is desired, but for trivial "just output" variables, we may want to exclude them from the cache.We can achieve this by passing the name of the variables to ignore as the arguments to the
@uncached
directive:After this change, we will only have one cache entry for all three renderings, and the value will still be updated dynamically.
If you need to exclude values from the cache using the
<flux:uncached></flux:uncached>
compiler component, you may pass them as a comma-delimited list to theexclude
attribute:We may also exclude arbitrary values like so:
This is particularly useful in "wrapper" components that include delegate components.
The Setup Directive and Component
The cache compiler has the concept of a "setup" code block. You can think of this is as a function that gets called before the component is rendered, regardless of it is cached or not. This is useful for any logic that must be executed to ensure the correct behavior of the component, such as the
@aware
and@props
directives.If you'd like this logic to run each time the component is rendered, you may wrap that section of your component's template in a
@setup
/@endsetup
directive pair:You may also use the
<flux:setup></flux:setup>
syntax:It is critical that any important logic that can impact the rendering of a component is wrapped in a setup block. A good example of where this is necessary is the
icon/index.blade.php
component. The$icon
resolution logic should be included in the setup block:Ignoring Specific Attributes
You may ignore specific attributes from the cache key system by passing an array of options to the
@cached
directive:When the cache system encounters these excluded attributes, it will substitute a placeholder value during the initial render in order to track where it would appear in the output. Because of this behavior, you should avoid any logic that depends on the existence of an attribute.
For example, the following types of components would no longer function correctly as a placeholder would always be available:
You may ignore multiple attributes by supplying an array of attribute names:
Cache and
@aware
VariablesThe attribute cache is capable of tracking variables inherited using the
@aware
directive. Under the hood, the@aware
directive is swapped out for a@fluxAware
directive when the cache is enabled on the component.The
@fluxAware
will alert the cache manager that an inherited variable was used (made possible through the "observation" stack). These variables will be considered when computing new cache keys for that same component on subsequent renders.The
@optimized
Compiler DirectiveThe
@optimized
directive is a simpler counterpart to the@cached
directive. When using the optimized directive, all of the component's logic will still be evaluated each time the component is rendered, but the component's view will only be loaded once.Let's take a look at what happens if we add
optimized
to the default select option variant:The caching component compiler will rewrite that to the following behind the scenes:
As you can see,
optimized
is simply a shortcut for the wrapping your component's template in anuncached
directive pair. You may also notice that the compiler added asetup
/endsetup
directive pair to the output. This happens automatically foroptimized
components whenever the compiler sees the@aware
or@props
directive being used. By default, it will wrap those directives and anything that appears before them in thesetup
pair. Most of the time this is fine, but there may be other logic you'd like to run before a component is rendered.To do this, you may manually specify a setup block:
Using the setup directive:
Using the setup component:
If you have manually specified a setup block the compiler will not add its own, but will still add the
uncached
directive pair for you.Flux Compiler Directives and Components
It is important to note that the
@uncached
,@enduncached
,@setup
, and@endsetup
directives as well as the<flux:uncached></flux:uncached>
and<flux:setup></flux:setup>
components are only compiled if the component begins with@cached
or@optimized
.Nesting these features is not supported, so don't do things like:
If the caching compiler is disabled entirely, the original raw contents that appear between the directive or component pairs will be returned.
Example Implementation
The following is an example of how the
select
component may be updated.In
select/option/variants/default.blade.php
:In
select/option/index.blade.php
:Using a template like:
Produces results similar to the following:
With cache compiler enabled:
With cache compiler disabled:
Views loaded count determined by Laravel view creation events.