Skip to content

Render components via view - #213

Draft
ganyicz wants to merge 2 commits into
mainfrom
filip/direct-view-render
Draft

Render components via view#213
ganyicz wants to merge 2 commits into
mainfrom
filip/direct-view-render

Conversation

@ganyicz

@ganyicz ganyicz commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

The scenario

Rendering a Blaze component via view() produces no output.

view('components.alert')->render(); // ''

This particularly affects class-based components as their views are often compiled by Blaze based on path.

class Alert extends Component
{
    public function render()
    {
        return view('components.alert');
    }
}

To fix this, the user needs to exclude class-based components like so:

Blaze::optimize()
    ->in(resource_path('views/components')
    ->in(resource_path('views/components/alert.blade.php', compile: false);

However, this is unintuitive.

The problem

Compiled Blaze components only contain a function definition and do not produce any output when required:

<?php
if (!function_exists('_e80ab8377fa9b7760ed62de7ee0f017e')):
function _e80ab8377fa9b7760ed62de7ee0f017e() {
?>
<div></div>
<?php } endif; ?>

The solution

Include both rendering paths in the compiled file, using $__path to detect when Laravel is rendering it as a view:

if (isset($__path) && $__path === __FILE__):
    // Render the template directly
elseif (! function_exists('...')):
    // Define the function
endif;

The $__path variable comes from File::getRequire() that's used by Blade to render file contents:

public function getRequire($path, array $data = [])
{
    $__path = $path;
    $__data = $data;

    return (static function () use ($__path, $__data) {
        extract($__data, EXTR_SKIP);

        return require $__path;
    })();
}

Fixes #210

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Result: Default

Attempt Blade Blaze Change
#1 357.24ms 15.02ms 95.8%
#2 366.64ms 14.97ms 95.9%
#3 363.96ms 14.64ms 96%
#4 362.82ms 14.80ms 95.9%
#5 362.70ms 14.77ms 95.9%
#6 362.55ms 14.82ms 95.9%
#7 360.20ms 14.76ms 95.9%
#8 365.49ms 14.86ms 95.9%
#9 360.51ms 14.88ms 95.9%
#10 365.05ms 15.19ms 95.8%
Snapshot 359.75ms 14.76ms 95.9%
Result 362.76ms (~) 14.84ms (~) 95.9% (~)

Median of 10 attempts, 5000 iterations x 10 rounds, 47.61s total

To run a specific benchmark, comment /benchmark <name>
attributes, aware, class, default, forwarding, merge, named-slots, no-attributes, slot, compilation

@ganyicz

ganyicz commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

@ghabriel25, saw you were looking at this issue too, wanna take a stab at it?

I would prefer this approach I outlined here however there are few issues with it and I don't have the capacity to pursue this right now so if you do it would be much appreciated, if not that's also fine.

  1. Recursive components can fail when rendered through view().
    Suppose components.tree renders its children using <x-tree :nodes="$children" />. Calling view('components.tree', ...) renders the template without defining its Blaze function. The recursive tag then reloads the same file, but $__path still selects direct rendering. This can cause endless recursion or an undefined-function error.
  2. @extends renders the layout twice.
    An optimized view containing @extends('layout') and @section('content') renders its layout twice. Blade encounters @extends in both template copies and appends both layout renders outside the conditional branches.
  3. @once can run twice for the same component.
    If a component contains @once <script>initialize()</script> @endonce, rendering it with both @include('components.example') and <x-example /> on one page emits the script twice. Blade gives the two template copies different “once” IDs.
  4. A symlinked view-cache path still causes empty output.
    If a custom VIEW_COMPILED_PATH points through a symlink, $__path contains the symlink path while __FILE__ contains the resolved path. They don’t match, so even view('components.alert', ...) with a simple template returns an empty string.

1: I don't fully understand what is causing the issue or how to fix it

2 & 3: this is an issue of having the source in the compiled file twice, these directives have a side-effect so duplicating the source is probably off the table entirely. I had an LLM suggest hacking into the directive compiler and changing how these directives are rendered but I found that super gross. I'm thinking instead of duplicating the source we could simply define the function upfront and then if we detect that the file is rendered via view() we would simply call the function from inside the compiled file.

4: This should be pretty easy to solve by using realpath(), I'm just slightly worried about performance overhead from calling that function but since it would only be called if $__path is defined, it should only affect performance when the component is rendered via view(), not when rendered via a Blaze component, so should be fine. Either way, needs to be tested, best way to do that is to open a PR, run the benchmark and see how much it affects both Blade and Blaze.

If all fails, we could re-explore your approach, the only issue I had there was all the regexes inside the new isClassBasedComponentView function, I found that a bit fragile, especially how we were checking for components directory in the path, the class-based component could potentially live anywhere, right? Best if we could avoid that.

Of course the approach in this PR is more desirable as it solves the underlying issue of not being able to render Blaze-compiled components via view() but I would settle for at least not breaking class-based components.

If there are issues with that too, or if you don't have the capacity to look into this I'll just close this along with the issue and we will just keep it as a limitation as it is for now.

Let me know, thanks!

@ghabriel25

ghabriel25 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Hi @ganyicz rather than duplicates the compiled template body (one copy for the view() path, one copy inside the function). Why not just defines the function like before, then simply calls that function when the file is required via view() ?

<?php

// define function up here

// call the function if the file is required via `view()`
if (isset($__path) && realpath($__path) === realpath(__FILE__)) {
    foo(
        $__blaze ?? app('blaze.runtime'),
        get_defined_vars(),
        [], [], [], null
    );
}
?>

@ganyicz

ganyicz commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Yes that's exactly what I suggested but have a look at all the other points too please

@ganyicz

ganyicz commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Also @ghabriel25, no rush, think it through. This could be a significant change and I want to get this right. As I've mentioned I don't have the capacity for it right now and I just thought you might want to give it a try since you've already attempted it with the other PR. But I want to make sure we can address all the issues really well and if we can't, or you don't feel like it, that's totally fine, just let me know and we'll table it for now and revisit later. It's already a documented limitation anyway.

@ghabriel25

Copy link
Copy Markdown
Contributor

Yeah. I wanna give it a try but dont expect too much as you can see my previous attempt really too narrow. I'll test each one of issues you mentioned above

@ganyicz

ganyicz commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

@ghabriel25 Awesome, thanks! And no worries, take a good look through my notes above, I think your narrower approach could be acceptable too if this one fails, when you've explored all the options, open a new PR with what you think is the best approach to solving this (if there even is one). Either way, no pressure. Appreciate your help!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimizing directory with Blaze::optimize()->in() renders class-based templates as empty output

2 participants