Skip to content
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

Optimization when rendering Turbo Frames #1

Open
nickjj opened this issue Feb 12, 2021 · 7 comments
Open

Optimization when rendering Turbo Frames #1

nickjj opened this issue Feb 12, 2021 · 7 comments
Labels
question Further information is requested

Comments

@nickjj
Copy link

nickjj commented Feb 12, 2021

The Ruby on Rails implementation will remove the layout from responses that are determined to be frame responses by looking for the Turbo-Frame header in the request. This can drastically reduce the payload size for frame responses.

That implementation in Ruby is here: https://github.com/hotwired/turbo-rails/blob/main/app/controllers/turbo/frames/frame_request.rb

I've been using Hotwire for a while now in Flask apps (since it launched), and have gotten around this by setting this in all of my templates:

{% if not request.headers.get("Turbo-Frame") %}
  {% extends "layouts/index.html" %}
{% endif -%}

But maybe there's a cleaner way to do it at the Flask level through an extension, especially if you want to factor in sending a different ETag value since technically the same URL can now serve different content (layout or no layout based on that header).

That's partly why I haven't created a Turbo extension for Flask, since technically with the above you don't need any type of Flask extension unless you plan to use streams.

@miguelgrinberg
Copy link
Owner

This is an interesting idea, but how can you be sure that the target stream isn't part of the base page layout?

@miguelgrinberg miguelgrinberg added the question Further information is requested label Feb 12, 2021
@nickjj
Copy link
Author

nickjj commented Feb 12, 2021

In this case it would be limited to Frames not Streams. Since the framed content is already broken up into included partial templates I think it could only ever be either the base layout or no layout. This functionality is built into the Rails (original) version of Hotwire.

Although I suppose this could be harder to pull off in Flask. In Rails the controller rendering the template is aware of what layout to use (something that can be obtained automatically) and can easily be optionally skipped. I think in Flask it would have to be configured at the extension level, but it might also not be possible to separate the layout using render_template.

It may require a custom render_template with an argument to skip the layout and that would always be set to skip if that header existed. I've never explored the guts of how template parsing works with Jinja / Flask so this proposal might not make any sense either.

It would be nice to figure out how to solve tho because littering all of your templates with that if statement (or a macro) is a bit tedious. Also there's no way to solve the ETag header problem using this method alone.

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Feb 13, 2021

Yeah, I don't really understand how the rails optimization works, to be honest. A turbo frame can use a different turbo frame as a navigation target, so I can totally have a frame in my template that targets a frame in the base layout. The general optimization would be to render the entire page with layout and everything and then fish out the target frame (assuming you know which frame it is) maybe in an after_request handler. I don't see how you can do better than this if you want to cover all possible cases.

The application, on the other side, could render just the intended frame if it knows what it is and the Turbo-Frame header is present.

@nickjj
Copy link
Author

nickjj commented Feb 13, 2021

As far as I know it's to control the payload of the frame's HTML and the Rails code above does nothing more than not render the layout when that header is present. But yes, I'm not sure how it fishes out the specifics because it looks like it's only telling it to not load the layout and nothing else.

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Feb 13, 2021

the Rails code above does nothing more than not render the layout when that header is present

If you have a turbo frame in the template that has a target set to some other turbo frame like maybe some section of a sidebar which happens to be defined in the layout, then how does it work? The rails code that you linked would break under such a case.

As I said above, the correct optimization would be to render the whole thing, and then in an after_request handler trim anything that is unnecessary, like maybe any HTML that is not inside a turbo frame. I don't think you can fish out the one frame in a generic way, because you don't know which of all the frames is the one that is the target.

I think as far as I'm willing to go is to provide a helper method similar to the can_stream(), say, can_use_frames() or something like that, and then leave it to the application to optimize the rendering if it wishes to do so.

@nickjj
Copy link
Author

nickjj commented Feb 13, 2021

I'm pretty sure if you need to alter a different area of the page, then Frames isn't the right Turbo feature to use. For that you would use Streams.

The folks who created Hotwire and Turbo extracted these patterns out of building https://hey.com. That's what the Rails implementation is based upon. That use case of wanting to alter a different area of the page is covered in their screencast video (9:46 and onwards) at https://hotwire.dev/.

@miguelgrinberg
Copy link
Owner

I'm pretty sure if you need to alter a different area of the page, then Frames isn't the right Turbo feature to use. For that you would use Streams.

It's a documented feature though: https://turbo.hotwire.dev/handbook/frames#targeting-navigation-into-or-out-of-a-frame. From that section: "Or it can drive another named frame by setting the target to the ID of that frame.".

I coded this package according to the turbo.js feature set, not turbo-rails. I'm totally open to come up with optimizations, but not at the cost of breaking a documented feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants