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

As a user I would like to Customize HEAD contents with application state #8

Closed
tbeseda opened this issue Sep 16, 2022 · 18 comments
Closed
Assignees
Labels
enhancement New feature or request

Comments

@tbeseda
Copy link
Collaborator

tbeseda commented Sep 16, 2022

It would be great to get the state returned from an api middleware/handler/fn/thing.

Use case:
I have app/api/users/$id.mjs
I use the id param to look up a user in my db to get their username (like "tbeseda").
I want to set the <title> of my view to "tbeseda's profile" when a visitor lands on /users/303 (where 303 is tbeseda's id).

Right now I don't think this is possible without running another query in my Head fn. I believe the Head fn only gets the incoming request, so I do have the id, but not the db record.

@jlipps
Copy link

jlipps commented Sep 16, 2022

👍 IMO head should be passed whatever is passed to the page. It is part of the page after all

@crtr0
Copy link

crtr0 commented Sep 17, 2022

I agree with @jlipps. I think the fact that the API for head is different than the one for pages and custom elements is confusing. I'd expect to have access to state and to return the HTML via the html render function.

@jlipps
Copy link

jlipps commented Sep 20, 2022

any movement on this? i really want to give my pages different titles for SEO and general sanity :-)

@brianleroux
Copy link
Collaborator

noodling on the right solution here. one thought is a special set of enhance-* elements that transform at runtime; so if your page had, for example, <enhance-head></enhance-head> the contents would be copied into the <head>. this opens up other problems, as you can imagine. should it override completely? should it be additive? should it try to merge the two w the head.mjs being the 'default' and the page level stuff winning?

@crtr0
Copy link

crtr0 commented Sep 22, 2022

Yeah, there are 100 ways to skin the cat (gross?), but I think the choices the helmet team made are a reasonable place to start:

https://github.com/nfl/react-helmet#readme

I will also say that this topic (how to include custom <head> tags on a page-by-page basis) doesn't address the core issue of this feature request, which is the API itself for head.mjs, which gets passed a req object rather than html and state objects.

@brianleroux
Copy link
Collaborator

brianleroux commented Sep 23, 2022

ok, so the ideal here is symmetrical api signature. I get that.

I am thinking rn that maybe we do the following instead:

  • deprecate head.mjs
  • support <head> in pages and elements
  • figure out a nice resolution algo that lifts <head> from pages into the final rendered result (same as we do for <script> and <style>)

its important to note you cannot use custom elements in a head per the browser spec so we don't want to give an impression you can or should do that. we'd probably warn or error out in this case.

wdyt?

@jlipps
Copy link

jlipps commented Sep 23, 2022

@brianleroux on that plan where would 'common' head stuff go, if not head.mjs? i still wouldn't want to duplicate stuff in <head> across multiple pages.

@crtr0
Copy link

crtr0 commented Sep 23, 2022

When you say "support <head> in pages and elements", do you mean provide a custom-element (i.e. <enhance-head>) that lifts the rendered tags up into the actual <head>? If so, I support that.

Developers need to be able to render markup in the <head> using the full compliment of tools that they can use to render markup in the <body>.

@brianleroux
Copy link
Collaborator

unsure still! looks like our parser impl strips <head> but I do think it should be possible without resorting to <enhance-head>

@brianleroux
Copy link
Collaborator

@brianleroux on that plan where would 'common' head stuff go, if not head.mjs? i still wouldn't want to duplicate stuff in <head> across multiple pages.

think we'll always support the current head.mjs (inc that api sig / it is deliberate!) as the default content, and then pages/elements with <head> would be additive (no clobber, I think)

the other footgun here is <script> and <style> in <head>

@crtr0
Copy link

crtr0 commented Sep 23, 2022

This isn't limited to this issue, but I think the fact that Enhance uses HTML custom elements as it's server-side templating/rendering solution for developers is cool but will require intense education on what HTML custom elements are, what you can do, and what you can't do.

Above you stated that <head> can't support custom elements, per the spec. Fair. But in that case, I think it would be unwise to encourage devs to do something like this:

<head>
  <my-social-tags title="a cool title"></my-social-tags>
</head>

...even if you could get your parser to accept/parse it, because it would fly in the face of the spec violation, which you are trying to educate your users about.

@brianleroux
Copy link
Collaborator

yeah @kristoferjoseph is looking at this because we want you to be able to pass state to the head. idea rn is something like this (notice the is)

<head>
  <my-social-tags title="a cool title" is=title></my-social-tags>
</head>```

@kristoferjoseph kristoferjoseph changed the title Feature Request: pass state from api handler to head function As a user I would like to Customize HEAD contents with application state Sep 23, 2022
@kristoferjoseph kristoferjoseph self-assigned this Sep 23, 2022
@kristoferjoseph kristoferjoseph added the enhancement New feature or request label Sep 23, 2022
@kristoferjoseph
Copy link
Contributor

kristoferjoseph commented Sep 23, 2022

This is a valid use case that we should support.

I would like to focus on the use case and the possible solutions.

Use case seems clear; As a developer I would like to customize the contents of the head tag with application state.

Solutions are going to need some understanding of the real world constraints.

  1. There are certain contexts that do not support custom elements based on the HTML spec, the head tag is one of these
  2. Enhance elements are custom elements which currently adhere to the spec as closely as possible which means we have purposely not added special handling or exceptions yet, which ideally we can remain compliant
  3. When rendering an HTML page there are some major benefits to being able serve the head first; for instance streaming, which would not be possible if we need to check every component to see if it has anything to add to the head before responding
  4. Pages are just HTML by default and do not currently support passing in application state.
  5. head.mjs does not currently get passed application state because of consideration 3.

Keep these considerations in mind when we outline the possible solutions

  1. Enhance could supply a react-helmet like solution i.e. enhance-helmet.
  • This would require us to add an exception to the custom element rendered ( consideration 2 ) and check every component for head content before returning ( consideration 3 )
  1. We could pass application state to head.mjs. This would still require supporting non-custom elements rendering.

We would still need to do something like add support for the contentious is attribute. Bonus is that this could also solve other situations where one might want to supply a custom element like inside a p tag or supplying a custom element to supply option elements to a select

Open to other ideas but this is much as my poor brain can come up with in my current sickly state. Hoping I can thought much gooder soon in order to help solve this for you good people.

@tbeseda
Copy link
Collaborator Author

tbeseda commented Sep 23, 2022

Oh this is a more interesting problem than I thought. Thanks for explaining these points, KJ.

Side-note: As for rendering a custom element in the Head function (number 2 above) and returning anything other than a string: I don't really think that's necessary and not so much what my original post was about -- though, I understand how we got there because it would be a slick shortcut.
But I want to stick to the spec and be limited in my ability to color outside the lines when using Enhance. (Though, we can improve Enhance to better message to developers when they do end up outside the spec.)

IMO, sending store to Head is the most straightforward solve for my original use-case.
But I didn't consider how this might affect streaming response where we start sending the result of the Head function before the api/ handler function has finished.

I do like the helmet idea from an ergonomics perspective. But it does add a bit of magic; which Enhance has mostly shied away from. Which I appreciate. I'm torn.

If it comes down to spec compliance or ergonomics, I will always be in favor of compliance.

I need to think on it some more... :D

@crtr0
Copy link

crtr0 commented Sep 23, 2022

@kristoferjoseph, addressing your points one by one:

  1. <head> does not support custom elements, but you can create custom elements that append to <head> (example), which I think is sufficient for folks.
  2. A custom tag as described in (1) should be fully spec compliant.
  3. This is not a use case I've ever considered. I'd be curious how prevalent this is, and if it turned out to be outside the meat of the bell curve, it might make more sense to provide an escape hatch for it.
  4. See my comment on (3)

I'm surprised to hear that creating an <enhance-head> that appends tags to the DOM <head> would not be spec compliant, given the simplistic Codepen example I included above.

Regarding the inability to stream the HTML response prior to full rendering the entirety of the HTML document, that's a tradeoff I could live with.

@jlipps
Copy link

jlipps commented Sep 26, 2022

Just to say I agree with @crtr0 that trading off HTML streaming for greater ergonomics here is worth it to me as well. Maybe just documenting that if you take advantage of custom stuff in head you give up streaming would be enough to let people make the decision on their own?

@kristoferjoseph
Copy link
Contributor

kristoferjoseph commented Sep 26, 2022

@crtr0 What you are outlining there is JavaScript that manipulates the DOM in the browser.
This is different than what I was referring to when I spoke of the html spec which does not allow custom elements i.e. <my-meta> in the contents of the head.

You are allowed to manipulate the DOM with JavaScript in the browser but at that time it will be after the page and JavaScript has been parsed which is too late for most head use cases.

You can however create a string template literal that returns html that is allowed in the head and use that inside your head component if you'd like.

export default function MyMetaTag() {
  return `
<meta ${someVariable}/>
`
}

Then in your head.mjs you could use it:

import myMetaTag from './my-meta-tag.mjs'
export default function Head(state) {
  return `
  <head>
    ${myMetaTag(state)}
  </head>
  `
}

It's subtle but the difference is this is just returning a string without custom element parsing since they aren't allowed.
<meta/> allowed <my-meta> is not.

@kristoferjoseph
Copy link
Contributor

I've added a PR for arc-plugin-enhance that will pass API state to a user's custom head tag.
All you need to do once that lands is add valid tags and you should be good 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants