withastro / roadmap

Ideas, suggestions, and formal RFC proposals for the Astro project.
290 stars 29 forks source link

Support for partials #697

Closed matthewp closed 9 months ago

matthewp commented 11 months ago

Summary

Allow pages to be marked as being partials, preventing the doctype tag and any head injection from taking place. A partial is defined as a template which belongs in the context of another page and is dependent on that page in order to be fully functional.

Background & Motivation

Partials are a technique that has been used by web applications for decades, popularized in frameworks such as Ruby on Rails. Frontend oriented JavaScript frameworks have typically not used partials, but instead use JSON APIs and front-end templating in order to dynamically change parts of the page. Nevertheless, partials have remained a niche feature, and with Astro's backend focus we have had interest in support for a long time.

Recently the popularity of projects like htmx and Unpoly have revived interest in this technique. Since Astro treats each page request as a request for a full page it automatically attaches the doctype and head elements, making it difficult to simply inject into the page.

Goals

Non-Goals

lilnasy commented 11 months ago

I would like to bring into the conversation Github.com's web-components-based framework, catalyst.

The relevant aspect is that associated javascript and css would still need to be fetched and deduplicated.

matthewp commented 11 months ago

@lilnasy Thanks for mentioning it, will make certain the solution here works with that.

My current thinking is that if a partial contains a <head> then we will do head injection. If there is no head then we will not. So libraries that can do head diffing should work.

webpro commented 11 months ago

Makes me think of RSS feed. Could this maybe also be used/hooked into when rendering RSS content? So there would be no need for a custom Markdown parser/renderer (e.g. markdown-it).

ayoayco commented 11 months ago

This is amazing. Would unlock so many use cases for .astro files as API endpoints.

aFuzzyBear commented 11 months ago

a bear can only dream....

philipschm1tt commented 11 months ago

I am very interested in this in the context of micro-frontends using classic Server-Side Includes. That is, I would like to include header and footer on a page via SSI as fragments. Ideally, header, footer, and the body of the page would come from three different Astro applications and be server-side rendered. The three applications would be owned by three different teams and upgrade to newer Astro versions at their own pace.

I started a related discussion for that use case: https://github.com/withastro/roadmap/discussions/713

sasoria commented 11 months ago

Awesome. I'd be very interested in trying this out, with and without htmx!

matthewp commented 11 months ago

For htmx users, the head support extension already does almost exactly what I would want. If you create a page like this:

<head>
  <style>
    .clicked {
      color: blue;
    }
  </style>
</head>
<div class="clicked">I was clicked</div>

The styles/scripts are merged into the real head of the document. The fragment target does not have any head content.

Is the desire to not use the head support? What is the problem with using Astro with htmx exactly?

matthewp commented 11 months ago

Here's my research on head merging support:

Library Head targeting Head merging
htmx ✅ (head-support plugin)
Unpoly
jQuery

Given this, I think htmx users are already in a really good place. For the other two, it would not be difficult to create head merging plugins. Someone should do that!

That leaves me to think that simply adding the ability to treat pages like fragments, should be enough to make everyone happy here. It would mean losing styles, but if you're ok with that and don't want to write the code to merge the head then that can work.

dougmoscrop commented 11 months ago

I'd like to have a page that might contain multiple components rendered on it and only update one via htmx, without having to rerender the other ones. Partials/fragments seem like they'd suit this, but there might be a simpler approach I'm not thinking of?

michrome commented 11 months ago

What is the problem with using Astro with htmx exactly?

From the previous discussions, there isn't a problem. There's a desire to not have to use hx-select. Today, we have to use hx-select because the response is a complete HTML document. It won't be required when the response can be a partial. So the desire is met with the goal:

  • The ability to request a URL from Astro that excludes the usual page elements (doctype, head injection).

And what you're suggesting here is perfect for more advanced cases 👍

matthewp commented 11 months ago

@michrome Ok thanks. If you use the head-support plugin then the head contents are moved into the head and you don't need to use hx-select. Is that not true? Or do you just not want to use head-support.

I don't consider that to be an advanced case. Astro components contain styles, which is very normal. I would say that not having the styles and dealing with it through global styles is the more advanced case.

ZebraFlesh commented 11 months ago

I would like to use Astro partials and am not using htmx.

ayoayco commented 11 months ago

🙋‍♂️ Also want to mention I'm thinking of using this for plain JS or vanilla custom elements. I think Partials will save developers time and complexity cleaning up HTML or selecting just a portion.

michrome commented 11 months ago

First up: what's being proposed above is exactly what I want! 😎

Ok thanks. If you use the head-support plugin then the head contents are moved into the head and you don't need to use hx-select. Is that not true? Or do you just not want to use head-support.

I don't consider that to be an advanced case. Astro components contain styles, which is very normal. I would say that not having the styles and dealing with it through global styles is the more advanced case.

The simplest use cases in my mind are:

Neither require additional styles. What's being proposed is good because there's no need to use use hx-select (for htmx users) or do any extraction from the response (for any framework). 👍

Assuming I am being too reductive then perhaps the use case of a new component with styles is more typical. It feels more 'advanced' to me because client side I have to detect content from the rest of the partial … but that's exactly what head-support is for so I personally wouldn't have any problem using it—in fact I'd want to use it for in this case. 👍

TLDR—this is all good.

georgwittberger commented 11 months ago

I have tried the HTMX head-support extension today and it plays nicely with Astro's current way of injecting styles and scripts into the <head> element.

Assuming you have the following initial page markup:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Page</title>
    <script src="https://unpkg.com/htmx.org@1.9.6"></script>
    <script src="https://unpkg.com/htmx.org@1.9.6/dist/ext/head-support.js"></script>
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body hx-ext="head-support">
    <h1>My Page</h1>
    <button hx-post="/result" hx-swap="outerHTML">Click me!</button>
  </body>
</html>

Then the following Astro page src/pages/result.astro can be used to swap the <button> for new content with potentially new styles:

<html>
  <head></head>
  <body>
    <div class="result">This is pink!</div>
  </body>
</html>

<style>
  .result {
    background-color: hotpink;
  }
</style>

Astro injects the <style> tag into the <head> when sending the response. HTMX then picks it up from there and inserts it into the existing <head> element of the page. This happens only once, so even if you would render the <button> again inside the result the new <style> tag is not inserted over and over again on each request. The head-support extension is smart enough to detect which elements have already been inserted.

If you are concerned about having the "real" content inside html > body in the Astro page: HTMX also handles that. In this case only the inner HTML of the <body> is swapped in. So the resulting HTML document looks like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Page</title>
    <script src="https://unpkg.com/htmx.org@1.9.6"></script>
    <script src="https://unpkg.com/htmx.org@1.9.6/dist/ext/head-support.js"></script>
    <link rel="stylesheet" href="/style.css" />
    <style>.result[data-astro-cid-hhnqfkh6] { background-color: hotpink; }</style>
  </head>
  <body hx-ext="head-support">
    <h1>My Page</h1>
    <div class="result" data-astro-cid-hhnqfkh6>This is pink!</div>
  </body>
</html>
michrome commented 11 months ago

@georgwittberger are you inferring partial support isn’t required? If so, I disagree 😀

Note the non-goals above:

This isn't an integration specifically for HTMX or any one library. It should work with any library or manual DOM manipulation that does innerHTML.

georgwittberger commented 11 months ago

@georgwittberger are you inferring partial support isn’t required? If so, I disagree 😀

No, I don't. I just thought it could be helpful to point out what is possible with the current state of Astro.

I agree that a general HTML partial support would be a really useful feature of Astro. However, no matter if you are using HTMX or some other library to patch the DOM client-side the question remains how Astro should support style and script injection for HTML partials. So, if the final decision should be that styles and scripts are simply not supported in HTML partials then I think it could be nice for developers to know how they can achieve some solution with "normal" Astro pages, even if that requires a specific library like HTMX to make head-merging work.

matthewp commented 11 months ago

Don't worry everyone, I still intend to add support for partials and am working on the RFC right now! My main concern going into this project was that head merging was something we would need to account for and figure out a solution for. But the fact that htmx has support for it already, and you could build similar things for jQuery and anywhere else, actually means that we don't (in my currently opinion) need to try and find a solution for that in Astro. So this can be as simple as a config flag in the page to opt in to partial fragment behavior.

georgwittberger commented 11 months ago

@matthewp , this sounds very reasonable. 👍

I totally agree that partial support in Astro can very well come with limitations, e.g. not being able to inject styles or scripts. The docs can explain such caveats and also workarounds like the one above for those who want to respond with HTML fragments including style / script injection.

ayoayco commented 11 months ago

Oh yeah, I don’t expect Astro to handle head merging itself. Will scoped styles be supported still?

matthewp commented 11 months ago

@ayoayco No, scoped styles requires head merging. So if you need that you can do that today without any new features just by extracting them from the returned HTML. This proposal will be to explicitly opt-out of styles/scripts.

ayoayco commented 11 months ago

Okay clear 👍 partials for plain unstyled HTML. Another question, sorry if out of scope for this story, but in the receiving page of a partial what’s going to be the best approach to style the coming in HTML?

matthewp commented 11 months ago

I would assume global CSS, either with <style is:global> or imported CSS or an atomic CSS library like Tailwind. I would love for @michrome and others who want this feature to chime in on that.

matthewp commented 11 months ago

RFC is up: https://github.com/withastro/roadmap/pull/721 would love feedback there.

matthewp commented 9 months ago

This was implemented and is in the stable version of Astro.