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

Support for partials #697

Closed
matthewp opened this issue Sep 13, 2023 · 26 comments
Closed

Support for partials #697

matthewp opened this issue Sep 13, 2023 · 26 comments

Comments

@matthewp
Copy link
Contributor

matthewp commented Sep 13, 2023

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

  • The ability to request a URL from Astro that excludes the usual page elements (doctype, head injection).
  • Some way to optionally communicate extra dependencies, such as styles and scripts, so libraries that support head-diffing can achieve that.
  • The base feature should work the same in SSG and SSR apps. Partials are still output as .html files in a static build.

Non-Goals

  • 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.
    • We may take into account special headers from popular libraries if there is a way to enhance the base experience.
  • No client-side scripts from Astro will be part of this change.
  • Routing should mostly be unchanged; this is metadata about a page but doesn't change the location or where routes live (still in the pages folder).
@lilnasy
Copy link
Contributor

lilnasy commented Sep 13, 2023

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
Copy link
Contributor Author

@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
Copy link

webpro commented Sep 13, 2023

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
Copy link

ayoayco commented Sep 13, 2023

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

@aFuzzyBear
Copy link

a bear can only dream....

@philipschm1tt
Copy link

philipschm1tt commented Sep 22, 2023

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: #713

@sasoria
Copy link

sasoria commented Sep 23, 2023

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

@matthewp
Copy link
Contributor Author

matthewp commented Oct 2, 2023

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
Copy link
Contributor Author

matthewp commented Oct 2, 2023

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
Copy link

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
Copy link

michrome commented Oct 3, 2023

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
Copy link
Contributor Author

matthewp commented Oct 3, 2023

@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
Copy link

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

@ayoayco
Copy link

ayoayco commented Oct 3, 2023

🙋‍♂️ 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
Copy link

michrome commented Oct 3, 2023

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:

  • appending/replacing <li>s in a list;
  • or replacing the text content of a element that's already on screen.

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
Copy link

georgwittberger commented Oct 3, 2023

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
Copy link

michrome commented Oct 3, 2023

@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
Copy link

@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
Copy link
Contributor Author

matthewp commented Oct 3, 2023

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
Copy link

@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
Copy link

ayoayco commented Oct 3, 2023

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

@matthewp
Copy link
Contributor Author

matthewp commented Oct 3, 2023

@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
Copy link

ayoayco commented Oct 3, 2023

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
Copy link
Contributor Author

matthewp commented Oct 3, 2023

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 matthewp mentioned this issue Oct 3, 2023
@matthewp
Copy link
Contributor Author

matthewp commented Oct 3, 2023

RFC is up: #721 would love feedback there.

@matthewp
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Implemented
Development

No branches or pull requests