Render component via view - #214
ghabriel25 wants to merge 31 commits into
Conversation
Benchmark Result: Default
Median of 10 attempts (* = outlier, excluded from result), 5000 iterations x 10 rounds, 17.56s total To run a specific benchmark, comment |
ecc077e to
3634f5b
Compare
|
@ghabriel25 amazing job! Makes me curious what caused such a big difference previously. I realize you weren't optimizing that blaze component, we normally use Can you try to get the benchmark working in the CI? It can be more accurate than running locally. Looks like you already attempted that with the benchmark-on-demand, that might be tricky. I would try changing the default one instead ( |
|
The big difference between previous benchmark and current benchmark was I use I'm not sure either as why using |
|
Yeah, that's weird because that would mean the component simply wasn't compiled by Blaze so the times should be roughly the same as Blade. Anyway, we don't have to investigate that. I was just curious. |
|
@ganyicz As for this
To be honest, I dont really understand how it works and how to use it. |
Benchmark Result: View
Median of 10 attempts (* = outlier, excluded from result), 5000 iterations x 10 rounds, 25.65s total To run a specific benchmark, comment |
|
No worries, I just set it up. I'm just wondering if we're actually testing the class-based components now. In the benchmark we have But the class is named Or, even better we should match the namespace and put the class in |
|
Let me change it |
|
The benchmark still wasn't setup correctly, now it is and this is the real result unfortunately (the comment will update with every push) If you're still up to this I would like to pull the thread a little more before we abandon it and figure out what exactly is causing the regression:
But if you want to abandon it or don't feel like working on it that's fine just let me know. |
I'll investigate this later one last time, if its still causing performance regression then its better to avoid this at the moment |
|
Okay cool, thanks! Give it one more try, now you have a reliable benchmark setup at least, you can just keep pushing here and checking that comment 2 - Yeah if that's the case we shouldn't even need the 'app()' call there |
|
@ganyicz I have tested all approach that I can think about and I can't find happy path for this. Even with this commit 7274636 still causing I think #212 is a good choice only if we can determine the path was class-based component which can be tricky (maybe tackled before compile, idk) |
|
Is there any change? Why it suddenly improved so much? @ganyicz we dont need to extract ob_start(); // PhpEngine
extract($__data, EXTR_SKIP);
require $__path; // → trigger → Blaze function
return ltrim(ob_get_clean()); // PhpEngine So we need to
|
f169a15 to
012d502
Compare
c303b9e to
354a45b
Compare
|
The remaining regression comes from We could use another short-circuit check (tested: not working) if (isset($__path) && ($__path === __FILE__ || basename($__path) === basename(__FILE__) || realpath($__path) === realpath(__FILE__)))Laravel’s |
|
Hey @ghabriel25 Yeah I figured it out yesterday, sorry for not writing an update, it was too late lol Here's what was going on (click to expand):It didn't make sense to me why the code was so much slower, here's how I thought about it: The benchmark showed that Blade + Blaze compilation was 80% slower than Blade alone, meaning the Blaze part alone adds 80% of how long Blade takes. But that doesn't make sense because Blaze is ~90% faster than Blade so it should only add ~10% of how long Blade takes (plus, when rendering via view we don't need the attribute bag etc. so it should be even faster) So I wanted to figure out exactly what part of the code is making it that much slower, I started stripping everything down, benchmarking it, until I was only left with this: <?php
if (!function_exists('_hash')):
function _hash($__blaze, $__data = [], $__slots = [], $__bound = [], $__keys = [], $__this = null) {
?>
<div></div><?php
} endif;
if (true) {
_1639f578bedf972e908202bd4387b693($__blaze, $__data, [], [], [], null);
}
?> And this was still ~20% slower than Blade. That didn't feel right as it's just a function call with nothing in it. I asked Astra what could possibly add overhead here and it figured out the culprit was OPCache. Basically the file was never cached by OPCache so every single render added overhead from compiling the PHP code - which wouldn't happen in a real environment. So basically the benchmark wasn't representative of real production environment. I fixed that in this PR: #215 so now we're seeing realistic numbers. If you're running the benchmarks locally you should also add these to your php.ini to get the same results: Anyway, thanks a lot for picking it up! To your points:
|
fc62d06 to
9e22d70
Compare
Hmm, that's strange, why does it work when you run the test by itself but not with the full test suite? That sounds like a caching issue, maybe there should be a view:clear in beforeEach inside those tests? |
|
Yeah I'm not sure, but we need to understand why it happens before we apply a fix like compiling the echoes, otherwise how do we know if that's the right fix if we don't understand the problem? |
|
@ganyicz I found something that might help to understand this Inside test('echo handlers work for direct view renders', function () {
Blade::stringable(fn (Stringable $v) => $v->upper());
Blaze::optimize()->in(fixture_path('views/components'));
expect(view('components.alert', ['message' => str('hello')])->render())->toBe('<div>HELLO</div>');
});
test('renders components as views', function () {
Blaze::optimize()->in(fixture_path('views/components'));
expect(view('components.alert', ['message' => 'Hello world'])->render())->toBe('<div>Hello world</div>');
});This order will pass on batch run also for single run. Try to flip the order, |
|
Hmm, try to look at the compiled files in vendor/testbench-core/laravel maybe that will tell you something Also it might be that the function is already defined so the second test is still using the original function |
|
I'll leave it for now as it-is to prevent cache-poisoning when test run in batch |
|
Ah I see so it's just an issue of how our tests are set up. We compile the same component two different ways. In that case we should just change the tests, not the code, right? |
|
@ganyicz doesnt it break when user render same component but different props just like test setup? |
|
If I understand it correctly it depends on whether there is an echo handler registered, not props. The echo handler should be registered in a service provider for the entire app so it wouldn't change between component renders. But double check it, make sure you understand it and let me know, I'll have a look at it later. |
|
I don't think we should be appending The issue was about Meaning if you put the I just pushed a commit with what I think the fix should look like. |
Try to flip the scenario and this will break if you put |
|
It should only break if you put a Blaze component with aware into a class-based component, which is a documented limitation The goal here is to make the class-based components behave identical to regular Blade, even though their view might have been compiled by Blaze Related to that, as I mentioned before it would be ideal if we wrote the tests as comparison tests, that would force us to consider what is actually important - parity with Blade - instead of making up new scenarios. |
|
I have adjusted the test based on our goal. I've tried to write comparison test on local and it passes so it basically behave like regular blade component. However I think we still missing something here as this test still fails test('aware resolves parent data on class-based component', function () {
Blaze::optimize()->in(fixture_path('views/components'));
$html = Blade::render('<x-wrapper type="number"><x-aware /></x-wrapper>');
expect($html)->toContain('type="number"');
});your fix e44d58d works for non-Blaze parent -> view class I have pushed c10efc5 which I think solve both
and I'd stop here |
c5d3bf8 to
50826bd
Compare
|
I'm pretty sure that test would fail even before this PR, per readme:
I'm not against solving that (would be nice to get rid of the rest of limitations) but let's make that a separate PR, if we're going to allow Blaze -> Blade, we should also make sure that Blade -> Blaze works even outside of views so it's not confusing. |
|
I see. Then I'll mark it skip for now and change things back as your commit |
|
@ganyicz I have cleaned up things and this is the compiled output <?php
if (!function_exists('_2b5dbee582313bd37e1d6aecb4c2e629')):
function _2b5dbee582313bd37e1d6aecb4c2e629($__blaze, $__data = [], $__slots = [], $__bound = [], $__keys = [], $__this = null, $__view = false) {
if ($__view):
$__bladeCompiler = $__blaze->compiler;
if (($__data['attributes'] ?? null) instanceof \Illuminate\View\ComponentAttributeBag) {
$attributes = new \Illuminate\View\ComponentAttributeBag($__data['attributes']->all()); unset($__data['attributes']);
} else {
$attributes ??= new \Illuminate\View\ComponentAttributeBag([]);
}
extract($__data, EXTR_SKIP);
else:
$__env = $__blaze->env;
if (($__data['attributes'] ?? null) instanceof \Illuminate\View\ComponentAttributeBag) { $__data = $__data + $__data['attributes']->all(); unset($__data['attributes']); }
extract($__slots, EXTR_SKIP); unset($__slots);
extract($__data, EXTR_SKIP);
$attributes = \Livewire\Blaze\Runtime\BlazeAttributeBag::make($__data, $__bound, $__keys);
unset($__data, $__bound, $__keys);
ob_start();
endif;
?>
// template
<?php
if (!$__view) { echo ltrim(ob_get_clean()); }
} endif;
if (isset($__path) && ($__path === __FILE__ || realpath($__path) === __FILE__)) {
_2b5dbee582313bd37e1d6aecb4c2e629($__blaze, $__data, [], [], [], $__this ?? null, true);
}
?>I'll leave it as it is for now but let me know if you find another regression. |



The scenario
Rendering a Blaze component via
view()produces no output.This particularly affects class-based components as their views are often compiled by Blaze based on path.
To fix this, the user needs to exclude class-based components like so:
However, this is unintuitive.
The problem
Compiled Blaze components only contain a function definition and do not produce any output when required:
The solution
Call the function from within the compiled file, using
$__pathto detect when Laravel is rendering it as a view:The
$__pathvariable comes fromFile::getRequire()that's used by Blade to render file contents:Fixes #210