Skip to content

Influence HTML head from Blazor #23833

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

Merged
merged 21 commits into from
Jul 22, 2020
Merged

Conversation

MackinnonBuck
Copy link
Member

@MackinnonBuck MackinnonBuck commented Jul 10, 2020

Summary of the changes

  • Added Title component to modify the document title.
  • Added Meta component to add or modify meta tags.
  • Added Link component.
  • Wrote functional and E2E tests.
  • Added prerendering support

Implementation details

I've changed my implementation to be simpler at the cost of some functionality. If users request additional functionality, we should consider taking pieces from this.

Usage sample:

<Title Value="@title" />
<Meta name="description" content="Modifying the head from a Blazor component." />
<Link href="main.css" rel="stylesheet" />

Note that these components don't require a surrounding Head component.

Addresses #10450

@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Jul 10, 2020
@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Jul 15, 2020

This is a great start. Thanks @MackinnonBuck! A couple of thoughts:

  1. Do we definitely want <Link>? Because we're working towards a CSS isolation feature in .NET 5 which will give people a more first-class way to include custom CSS within a component rather than needing any special tags. And for people whose goal is to dynamically reference an external .css file, isn't it equally good just to use a regular <link> element in a component? I know that <Link> will add the element to <head> whereas a regular <link> would put it in the middle of the document, but since this is being added dynamically I'm not sure it makes any difference to the end result. So maybe we should not have <Link>.
  2. I don't think the hierarchical behavior will be quite right in all cases (update actually I am siding with your design now!):
    • The approach here relies on parents always being rendered before children. While it's true that parents are initially rendered before their descendants, it's entirely possible for a parent to be re-rendered later without necessarily re-rendering its descendants. Since you overwrite the title on each call to OnParametersSet, the parent could overwrite the title written by one of its descendants. Additionally, when a <Title> is removed, it doesn't set the title back to what it was previously, so you can't just temporarily set a title using an @if block.
    • Update: The more I think about this, the less I'm convinced there's any solution that is simultaneously "robust" while also being nice to use. To make it really robust you'd need to make people wrap <Title> around the contents of their page so they were really nested inside each other, not on sibling branches, then there's a well-defined order. But the usability would suck. So maybe the design you have is already the best choice, and we should just document something like "Each <Title> overwrites the previous document title when rendered, so be sure to only render one at a time." and discourage people from having multiple <Title> components at different levels of their hierarchy.

@MackinnonBuck MackinnonBuck marked this pull request as ready for review July 15, 2020 21:46
@MackinnonBuck MackinnonBuck requested a review from a team as a code owner July 15, 2020 21:46
@MackinnonBuck
Copy link
Member Author

Thanks for the feedback, @SteveSandersonMS! My main reason for adding <Link> was to match functionality provided by similar libraries for React. For example, react-helmet supports base, bodyAttributes, htmlAttributes, link, meta, noscript, script, style, and title. It should be trivial to add our own support for more tags so I think it's fine to remove <Link> unless others think there's a good reason to leave it in.

@jimspillane
Copy link

The link element can be used for a number of other link types such as rel="icon" that are not body-ok types.

Copy link
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed removing special code paths for prerendering and yoloing it all the time. This should remove the dependency on Server.Circuits from this project.

protected override void OnInitialized()
{
_headManager = ServiceProvider.GetHeadManager() ??
throw new InvalidOperationException($"{GetType()} requires a {typeof(HeadManager)} service.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SteveSandersonMS \ @javiercn, would we consider having a way for Inject to tell you what to do to fix this?

[Inject(Error = "Did you forget to invoke AddBlah()?")]

It shouldn't be too much work to include that in the diagnostics here:

throw new InvalidOperationException($"Cannot provide a value for property " +

if (grandchildrenArray) {
while (grandchildrenArray.length > 0) {
removeLogicalChild(childToRemove, 0);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting. Was there a bug before? What scenario triggered it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I encountered a bug where grandchildrenArray was undefined if a component rendered a comment during prerendering.

@MackinnonBuck MackinnonBuck requested a review from pranavkm July 15, 2020 23:59
Copy link
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. Could you rebase this on to the release/5.0-preview8 branch?

@MackinnonBuck MackinnonBuck changed the base branch from master to release/5.0-preview8 July 16, 2020 18:45
@MackinnonBuck MackinnonBuck requested a review from a team July 16, 2020 18:45
@MackinnonBuck MackinnonBuck force-pushed the t-mabuc/influence-head branch from b71849d to 29d5a71 Compare July 16, 2020 23:35
<p>
Meta:<br />
<input id="input-meta-binding" @bind="metaContent" placeholder="Set the meta content" />
<Meta id="meta-with-bindings" content="@metaContent" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the IDE experience with this? We've had problems in the past with Components that overlapped with HTML tag names, since Meta is a valid HTML5 tag (they are case-insensitive)

Copy link
Member

@SteveSandersonMS SteveSandersonMS Jul 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point. Based on experiences with <Button>/<button>, it seems likely that VS may interfere with typing <Title> and try to auto-correct it to <title>.

While this is annoying, I don't think we should block the PR on it, because there isn't a great alternative right now, and developers can self-correct this, and eventually the IDE can do a better job with it.

Also a better solution still would be if we implemented #22194. Then it would be possible for people not to have a @using directive for the full namespace here, but instead type things like:

<Head.Title>My title</Head.Title>

However that would only work if the namespaces containing these elements was something like Microsoft.AspNetCore.Components.Web.Extensions.Head.

Do you think it might be worth putting them in that namespace proactively so that we can get this developer experience eventually when #22194 is implemented? In the short term developers will still get the same experience of typing <Title>Hey</Title> as long as they have a @using for the full namespace. This also fixes one other small concern I had that these component names are super-generic and people might have their own reasons for creating unrelated components called <Title>/<Link> etc.

Interested in opinions from @javiercn as well as @MackinnonBuck here!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @SteveSandersonMS's suggestion to proactively put the components in a Head namespace. It would make good use of #22194 (when implemented) without making the current user experience worse.

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some minor comments, but overall looks great!

Copy link
Member

@SteveSandersonMS SteveSandersonMS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic work, @MackinnonBuck! This looks great to me.

As with @javiercn there are a couple of remaining small comments but hopefully this should be able to merge really soon.

@mkArtakMSFT mkArtakMSFT merged commit b121a2f into release/5.0-preview8 Jul 22, 2020
@mkArtakMSFT mkArtakMSFT deleted the t-mabuc/influence-head branch July 22, 2020 20:25
@amis92
Copy link

amis92 commented Jul 23, 2020

@MackinnonBuck I'm sorry to bother you, but I've browsed this PR and haven't found an answer: how is the prerendering actually working here? From what I see, there are only comments added to the render tree, and all actual DOM work is done in JavaScript. Is JS being run now as part of prerender?

I ask because in the #10450 there was some talk about using such a feature for impacting social previews. Would such scenario be covered by this PR?

@javiercn
Copy link
Member

@amis92 the way it works is during prerendering the content gets emitted as a comment into the DOM and then a small piece of JS updates it when the page loads on the browser.

@amis92
Copy link

amis92 commented Jul 23, 2020

Ah. So no game, then. It's not actually prerendered for static HTML processing like link previews on websites.

@sfmskywalker
Copy link
Contributor

Fantastic, this PR adds just the thing I was looking for :D

Seeing that this PR was merged into https://github.com/dotnet/aspnetcore/tree/release/5.0-preview8, I assumed I would be able to find it on the aspnetcore preview feed in the microsoft.aspnetcore.components.web\5.0.0-preview.8.20364.16 package, but I can't seem to find anything containing these bits. No doubt I'm looking in the wrong place or misunderstanding what branches get build where.

Any pointers on where I might try these things out (ideally using a package feed)?

@sfmskywalker
Copy link
Contributor

Typical. I did one more attempt at finding it after writing the above, and of course I found it. I was looking on the Installed tab in Visual Studio rather than on the Browse tab 😅 One day I'll get the hang of this VS NuGet UI 🤞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants