Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f3e0b01
Initial commit
JohnathonKoster Apr 15, 2025
7de0311
More tests, some little tweaks
JohnathonKoster Apr 15, 2025
db19251
Update FluxComponentCache.php
JohnathonKoster Apr 15, 2025
ad31b0b
Simplify
JohnathonKoster Apr 23, 2025
c9d19d6
Cleanup
JohnathonKoster Apr 26, 2025
86d7e1a
Rename methods for happiness
JohnathonKoster Apr 26, 2025
0939c15
Refactor PHP block to cache directive-like thing
JohnathonKoster Apr 26, 2025
18bd054
Make it clearer what this is for
JohnathonKoster Apr 26, 2025
aa21773
Rename ignore to exclude
JohnathonKoster Apr 26, 2025
498fa71
Rename to align more closely with the Blade feature this works with
JohnathonKoster Apr 26, 2025
38faa8a
Typo
JohnathonKoster Apr 26, 2025
8ff4de0
Process props in cache callbacks
JohnathonKoster Apr 26, 2025
8215fe0
Rename nocache to uncache
JohnathonKoster Apr 26, 2025
53327a5
Rename "use" to "exclude" for consistency
JohnathonKoster Apr 26, 2025
50c3e92
Adds a flux "optimized" compiler flag/directive
JohnathonKoster Apr 26, 2025
17f9c90
Ensure the environment is always available
JohnathonKoster Apr 26, 2025
b8f04a4
Host any leading content
JohnathonKoster Apr 26, 2025
37a6456
Yeet all this
JohnathonKoster Apr 27, 2025
9342ba2
Make it less annoying to swap between versions
JohnathonKoster Apr 27, 2025
34aade7
Update ComponentCompiler.php
JohnathonKoster Apr 27, 2025
38dc010
Revert some changes
JohnathonKoster Apr 27, 2025
c88ea54
Update ComponentCompiler.php
JohnathonKoster Apr 27, 2025
816bbac
Going to change approach on this
JohnathonKoster Apr 27, 2025
ccdbed3
Much simpler
JohnathonKoster Apr 27, 2025
d494590
Internal infra to run a "setup" function
JohnathonKoster Apr 28, 2025
455611f
Add compiler support for setup
JohnathonKoster Apr 28, 2025
800b187
Makes optimized a bit smarter
JohnathonKoster May 2, 2025
a90e052
No longer needed
JohnathonKoster May 2, 2025
30137ec
Only register/boot when enabled
JohnathonKoster May 2, 2025
89152b1
Update ComponentCompiler.php
JohnathonKoster May 2, 2025
c494d67
Convert to real directive
JohnathonKoster May 2, 2025
2ceb5b5
Add support for ignored attributes + tests
JohnathonKoster May 3, 2025
08e6870
Update OptimizedComponentCompiler.php
JohnathonKoster May 3, 2025
5582719
Add coverage to ensure extra newlines don't get added
JohnathonKoster May 3, 2025
c7ccd01
Update detection, some simple cleanup
JohnathonKoster May 3, 2025
dcf4417
Help prevent undefined array key ""
JohnathonKoster May 3, 2025
026cee5
Cleanup
JohnathonKoster May 3, 2025
695fa4f
Tweaks for morph markers
JohnathonKoster May 3, 2025
3e31153
Improve compilation when disabled
JohnathonKoster May 3, 2025
07c24e6
Keep markers in the same relative position
JohnathonKoster May 3, 2025
ff3ab52
Remove extra helper 🤌
JohnathonKoster May 3, 2025
d19f9f2
Some cleanup, remove hoisted tmp var
JohnathonKoster May 3, 2025
7ca9705
Tidy up
JohnathonKoster May 3, 2025
25f17a1
Update IgnoredAttributesTest.php
JohnathonKoster May 3, 2025
dfef32d
Add coverage for multiple ignored attributes
JohnathonKoster May 3, 2025
06124bc
Create multiple_ignored_attributes.blade.php
JohnathonKoster May 3, 2025
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
14 changes: 14 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,20 @@
"livewire/livewire": "^3.5.19",
"laravel/prompts": "^0.1|^0.2|^0.3"
},
"require-dev": {
"orchestra/testbench": "^9.2 || ^10.0",
"pestphp/pest": "^2"
},
"autoload": {
"psr-4": {
"Flux\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Flux\\Tests\\": "tests"
}
},
"extra": {
"laravel": {
"providers": [
Expand All @@ -32,5 +41,10 @@
"Flux": "Flux\\Flux"
}
}
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
}
159 changes: 159 additions & 0 deletions src/Compiler/ComponentCompiler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

namespace Flux\Compiler;

use Flux\Flux;
use Illuminate\Support\Str;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\Compilers\ComponentTagCompiler;

class ComponentCompiler extends ComponentTagCompiler
{
protected $isOptimizedComponent = false;

public $outputOptimizations = false;

protected $uncachedBuffer = [];

public function isFluxComponent($value)
{
return Str::startsWith(ltrim($value), ['@cached', '@optimized']);
}

protected function compileOptimizedComponent($value)
{
$this->isOptimizedComponent = true;

return OptimizedComponentCompiler::compile($value);
}

protected function compileComponent($value)
{
$value = $this->compileSetupComponent($value);
$value = $this->compileSetupDirective($value);
$value = $this->compileUncachedComponent($value);
$value = $this->compileUncachedDirective($value);

$value .= implode('', $this->uncachedBuffer);

$this->uncachedBuffer = [];

return $value;
}

protected function removeCacheFeatures($value)
{
$value = preg_replace('/(?<!@)@optimized/', '', $value);
$value = preg_replace('/(?<!@)@cached/', '', $value);

return $this->compileComponent($value);
}

public function compile($value)
{
if (! $value) {
return $value;
}

if (! $this->isFluxComponent($value)) {
return $value;
}

if (! $this->outputOptimizations) {
return $this->removeCacheFeatures($value);
}

$value = ltrim($value);

if (Str::startsWith($value, '@optimized')) {
$value = $this->compileOptimizedComponent($value);
}

if (! $this->isOptimizedComponent) {
$value = preg_replace('/(?<!@)@aware\(/', '@fluxAware(', $value);
}

return $this->compileComponent($value);
}

protected function compileSetup($content)
{
if (! $this->outputOptimizations) {
return $content;
}

return <<<PHP
<?php
\Flux\Flux::cache()->registerSetup(function (\$__tmpComponentData) {
extract(\$__tmpComponentData);
\$__env = \$__env ?? view();
?>{$content}<?php
unset(\$__tmpComponentData);
return get_defined_vars();
});
extract(\Flux\Flux::cache()->runCurrentComponentSetup(get_defined_vars()));
?>
PHP;
}

protected function compileUncached($content, $excludeExpression)
{
if (! $this->outputOptimizations) {
return $content;
}

$replacement = '__FLUX::SWAP_REPLACEMENT::'. Str::random();

if (strlen($excludeExpression) > 0) {
$this->uncachedBuffer[] = "<?php \Flux\Flux::cache()->exclude({$excludeExpression}); ?>";
}

return <<<PHP
[FLUX_SWAP:BEGIN $replacement]
$content
[FLUX_SWAP:END]
PHP;
}

protected function compileSetupComponent($value)
{
return preg_replace_callback('/<flux:setup(?:\s+([^>]+))?>(.*?)<\/flux:setup>/s', function ($matches) {
return $this->compileSetup(trim($matches[2]));
}, $value);
}

protected function compileUncachedComponent($value)
{
return preg_replace_callback('/<flux:uncached(?:\s+([^>]+))?>(.*?)<\/flux:uncached>/s', function ($matches) {
$excludeExpression = '';

if ($matches[1]) {
$attributes = $this->getAttributesFromAttributeString($matches[1]);

if (isset($attributes['exclude'])) {
$variables = str(mb_substr($attributes['exclude'], 1, -1))
->explode(',')
->map(fn ($var) => "'{$var}'")
->implode(', ');
$excludeExpression = '['.$variables.']';
}
}

return $this->compileUncached(trim($matches[2]), $excludeExpression);
}, $value);
}

protected function compileSetupDirective($value)
{
return preg_replace_callback('/@setup(?:\((.*?)\))?([\s\S]*?)@endsetup/s', function ($matches) {
return $this->compileSetup(trim($matches[2]));
}, $value);
}

protected function compileUncachedDirective($value)
{
return preg_replace_callback('/@uncached(?:\((.*?)\))?([\s\S]*?)@enduncached/s', function ($matches) {
return $this->compileUncached(trim($matches[2]), $matches[1] ?? '');
}, $value);
}
}
18 changes: 18 additions & 0 deletions src/Compiler/ComponentHashes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Flux\Compiler;

use Illuminate\View\Compilers\BladeCompiler;

class ComponentHashes extends BladeCompiler
{
public static function newComponentHash(string $component)
{
return parent::newComponentHash($component);
}

public static function popHash()
{
return array_pop(static::$componentHashStack);
}
}
143 changes: 143 additions & 0 deletions src/Compiler/FluxComponentDirectives.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

namespace Flux\Compiler;

use Flux\Support\StrUtil;
use Illuminate\Support\Str;
use Illuminate\View\AnonymousComponent;

class FluxComponentDirectives
{
public static function compileFluxComponentClass($expression)
{
[$component, $alias, $data] = str_contains($expression, ',')
? array_map('trim', explode(',', trim($expression, '()'), 3)) + ['', '', '']
: [trim($expression, '()'), '', ''];

$component = trim($component, '\'"');

$hash = ComponentHashes::newComponentHash(
$component === AnonymousComponent::class ? $component.':'.trim($alias, '\'"') : $component
);

if (Str::contains($component, ['::class', '\\'])) {
return static::compileClassComponentOpening($component, $alias, $data, $hash);
}

return "<?php \$__env->startComponent{$expression}; ?>";
}

public static function compileClassComponentOpening(string $component, string $alias, string $data, string $hash)
{
$componentData = $data ?: '[]';

return implode("\n", [
'<?php if (isset($component)) { $__componentOriginal'.$hash.' = $component; } ?>',
'<?php if (isset($attributes)) { $__attributesOriginal'.$hash.' = $attributes; } ?>',
'<?php $component = '.$component.'::resolve('.$data.' ?? [] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>',
'<?php $component->withName('.$alias.'); ?>',
'<?php if ($component->shouldRender()): ?>', /* START: RENDER */
'<?php $__env->startComponent($component->resolveView(), $component->data()); ?>',
]);
}

public static function compileEndFluxComponentClass()
{
$hash = ComponentHashes::popHash();

return <<<PHP
<?php
if (isset(\$__fluxCacheKey{$hash})) { \$__fluxCacheKeyOriginal{$hash} = \$__fluxCacheKey{$hash}; }
\$__componentRenderData{$hash} = \$__env->fluxComponentData();
\$__fluxCacheKey{$hash} = \Flux\Flux::cache()->key(\$component->componentName, \$__componentRenderData{$hash}, \$__env);
if (\$__fluxCacheKey{$hash} === null || \Flux\Flux::cache()->has(\$__fluxCacheKey{$hash}) === false): /* START: CACHE BLOCK */

\Flux\Flux::cache()->startObserving(\$component->componentName);
\$__fluxTmpOutput{$hash} = \$__env->renderFluxComponent(\$__componentRenderData{$hash});
\Flux\Flux::cache()->stopObserving(\$component->componentName);
\$__fluxCacheKey{$hash} = \Flux\Flux::cache()->key(\$component->componentName, \$__componentRenderData{$hash}, \$__env);

if (\$__fluxCacheKey{$hash} !== null) {
\Flux\Flux::cache()->put(\$component->componentName, \$__fluxCacheKey{$hash}, \$__fluxTmpOutput{$hash});
\$__fluxTmpOutput{$hash} = \Flux\Flux::cache()->swap(
\$component->componentName,
\$__fluxTmpOutput{$hash},
\Flux\Flux::cache()->runComponentSetup(\$component->componentName, \$__componentRenderData{$hash})
);
}

echo \$__fluxTmpOutput{$hash};
unset(\$__fluxTmpOutput{$hash});

else: /* ELSE: CACHE BLOCK */
\$__env->popFluxComponent();
\$__fluxTmpOutput{$hash} = \Flux\Flux::cache()->swap(
\$component->componentName,
\Flux\Flux::cache()->get(\$__fluxCacheKey{$hash}),
\Flux\Flux::cache()->runComponentSetup(\$component->componentName, \$__componentRenderData{$hash})
);
echo \$__fluxTmpOutput{$hash};

unset(\$__fluxTmpOutput{$hash});
endif; /* END: CACHE BLOCK */

endif; /* END: RENDER */


if (isset(\$__fluxCacheKey{$hash})) { unset(\$__fluxCacheKey{$hash}); }
if (isset(\$__fluxCacheKeyOriginal{$hash})) {
\$__fluxCacheKey{$hash} = \$__fluxCacheKeyOriginal{$hash};
unset(\$__fluxCacheKeyOriginal{$hash});
}
if (isset(\$__componentRenderData{$hash})) { unset(\$__componentRenderData{$hash}); }
if (isset(\$__attributesOriginal{$hash})) {
\$attributes = \$__attributesOriginal{$hash}; unset(\$__attributesOriginal{$hash});
}

if (isset(\$__componentOriginal{$hash})) {
\$component = \$__componentOriginal{$hash}; unset(\$__componentOriginal{$hash});
}
?>
PHP;
}

public static function compileCached($expression)
{
$expression = trim($expression);

while (Str::startsWith($expression, '(') && Str::endsWith($expression, ')')) {
$expression = trim(Str::substr($expression, 1, -1));
}

if (! $expression) {
return '<?php Flux::shouldCache(); ?>';
}

return <<<PHP
<?php
Flux::shouldCache();
\$__fluxCacheOptions = $expression;

if (isset(\$__fluxCacheOptions['except'])) {
\$attributes = Flux::cache()->ignoreAttributes(\$__fluxCacheOptions['except'], get_defined_vars());
}

unset(\$__fluxCacheOptions);
?>
PHP;
}

public static function compileFluxAware($expression)
{
return "<?php foreach ({$expression} as \$__key => \$__value) {
\$__consumeVariable = is_string(\$__key) ? \$__key : \$__value;
if (is_string (\$__key)) {
\$\$__consumeVariable = \$__env->getConsumableComponentData(\$__key, \$__value);
\Flux\Flux::cache()->usesVariable(\$___key, \$\$__consumeVariable, \$__value);
} else {
\$\$__consumeVariable = \$__env->getConsumableComponentData(\$__value);
\Flux\Flux::cache()->usesVariable(\$__value, \$\$__consumeVariable);
}
} ?>";
}
}
Loading