docusaurus icon indicating copy to clipboard operation
docusaurus copied to clipboard

feat: add linkTags API

Open johnnyreilly opened this issue 1 year ago • 11 comments

Pre-flight checklist

  • [x] I have read the Contributing Guidelines on pull requests.
  • [x] If this is a code change: I have written unit tests and/or added dogfooding pages to fully verify the new behavior.
  • [x] If this is a new API or substantial change: the PR has an accompanying issue (closes #0000) and the maintainers have approved on my working plan.

Motivation

I'm trying to add a custom link tag to the header of all the pages on my Docusaurus site. In my case I'm looking to render a web monetization link which looks like this:

<link rel="monetization" content="https://ilp.uphold.com/LwQQhXdpwxeJ" />

It looks like there isn't a way to generically supply global link tags with Docusaurus. At least... until now!

I've implemented a new API called linkTags which is a generic mechanism for rendering link tags. It is modelled after the metadata API and sits alongside it in the ThemeConfig of docusaurus.config.js

Test Plan

I'm not sure where unit tests for this would go - but if you give me a pointer I'll write one! In the meantime you can see a screenshot of this working in this PR and see it working in the preview website also.

Test links

image

Look at that! It works!

Deploy preview: https://deploy-preview-8077--docusaurus-2.netlify.app/

Crack open the devtools and you can see the link tag present and correct.

Related issues/PRs

https://github.com/facebook/docusaurus/issues/8049

johnnyreilly avatar Sep 09 '22 20:09 johnnyreilly

[V2]

Built without sensitive environment variables

Name Link
Latest commit c6f5cf9ac75f9942203d1570697d928098abcaeb
Latest deploy log https://app.netlify.com/sites/docusaurus-2/deploys/63372f0de5e2dc0009acea0c
Deploy Preview https://deploy-preview-8077--docusaurus-2.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

netlify[bot] avatar Sep 09 '22 20:09 netlify[bot]

⚡️ Lighthouse report for the deploy preview of this PR

URL Performance Accessibility Best Practices SEO PWA Report
/ 🟠 56 🟢 98 🟢 100 🟢 100 🟠 80 Report
/docs/installation 🟠 78 🟢 100 🟢 100 🟢 100 🟢 90 Report

github-actions[bot] avatar Sep 09 '22 20:09 github-actions[bot]

I don't have any opinions on this, but I wonder whether we should make it a theme API (like metadata) or a core API (like stylesheets). I don't really know why we even have the latter.

Josh-Cena avatar Sep 09 '22 20:09 Josh-Cena

I put it where it is as it seems like the logical bedfellow to metadata. It would feel perhaps a little strange if you globally configured meta tags in one place and link tags in another. This way at least it's consistent. But I don't feel super strongly

johnnyreilly avatar Sep 09 '22 20:09 johnnyreilly

Me neither. Feels weird since stylesheets does quite the same thing, but I can see why it lives in core (we might do some other optimizations with them).

Josh-Cena avatar Sep 09 '22 20:09 Josh-Cena

Awesome - will do! Just heading to bed now; will put some docs up tomorrow I expect.

johnnyreilly avatar Sep 09 '22 20:09 johnnyreilly

Could you add some docs in https://docusaurus.io/docs/api/themes/configuration and possibly https://docusaurus.io/docs/seo?

Added docs:

  • https://deploy-preview-8077--docusaurus-2.netlify.app/docs/api/themes/configuration#linktags
  • https://deploy-preview-8077--docusaurus-2.netlify.app/docs/seo

johnnyreilly avatar Sep 10 '22 11:09 johnnyreilly

Do these docs work for you @Josh-Cena?

johnnyreilly avatar Sep 12 '22 11:09 johnnyreilly

Paging @Josh-Cena 😅

johnnyreilly avatar Sep 14 '22 05:09 johnnyreilly

Sorry! All public API additions need to be reviewed by @slorber but he'a on holiday til end of month, so this won't be merged in a while anyway... I would take a look at the documentation later.

Josh-Cena avatar Sep 14 '22 12:09 Josh-Cena

Awesome - thanks for the update!

johnnyreilly avatar Sep 14 '22 12:09 johnnyreilly

There's a failing Windows test, but by the looks of it, it's a flaky test and not linked to this PR. My guess is that if you rerun it'll pass.

johnnyreilly avatar Sep 19 '22 07:09 johnnyreilly

What prevents you from using a plugin instead?

The API is not easy to discover but I think this impl should be good enough?

function pluginMonetization(context, options) {
  return {
    name: "plugin-monetization",
    injectHtmlTags() {
      return {
        headTags: [
          {
            tagName: "link",
            attributes: {
              rel: "monetization",
              content: options.monetizationContent
            }
          }
        ]
      };
    }
  };
}

Similarly it should be possible to create a more generic links plugin


Although I think we discussed in the past it might be convenient to add a such shortcut to add global site links more conveniently, I'm not sure it's the responsibility of the theme to do so, as it would be duplicated in another theme that we'd want to implement.

Maybe it's better to add this to createBootstrapPlugin() that is always added to any Docusaurus site in core (used to provide similar shortcuts?

export function createBootstrapPlugin({
  siteDir,
  siteConfig,
}: LoadContext): LoadedPlugin {
  const {
    stylesheets,
    scripts,
    clientModules: siteConfigClientModules,
  } = siteConfig;
  return {
    name: 'docusaurus-bootstrap-plugin',
    content: null,
    options: {
      id: 'default',
    },
    version: {type: 'synthetic'},
    path: siteDir,
    getClientModules() {
      return siteConfigClientModules;
    },
    injectHtmlTags: () => {
      const stylesheetsTags = stylesheets.map(
        (source): string | HtmlTagObject =>
          typeof source === 'string'
            ? `<link rel="stylesheet" href="${source}">`
            : {
                tagName: 'link',
                attributes: {
                  rel: 'stylesheet',
                  ...source,
                },
              },
      );
      const scriptsTags = scripts.map((source): string | HtmlTagObject =>
        typeof source === 'string'
          ? `<script src="${source}"></script>`
          : {
              tagName: 'script',
              attributes: {
                ...source,
              },
            },
      );
      return {
        headTags: [...stylesheetsTags, ...scriptsTags],
      };
    },
  };
}

Also not a fan of "linkTags" naming. We already have stylesheets, scripts and metadata (which afaik is plural of metadatum) so "links" makes more sense to me (also similar to Remix: https://remix.run/docs/en/v1/api/conventions#links)


Side note but stylesheets + scripts are in core/siteConfig, and metadata is in the theme/themeConfig (as you noted), that's a bit weird

https://docusaurus.io/docs/api/themes/configuration#metadata

Maybe we'll want to add a site.metadata in core too for consistency? 🤷‍♂️ Maybe we'll have both site.metadata + theme.metadata? 🤷‍♂️

Some important thing to consider: we set some global non-core opinionated meta in the theme currently (I think just <meta name="twitter:card" content="summary_large_image" />) and users should be able to easily override this on a global site level. Declaration order of metadata will matter to keep this behavior.

Unlike metadata, I'm not sure links are supposed to override each others (any use-case for this?). That's worth checking if React Helmet does override/deduplicate itself links (it does for meta).

For example, let's imagine we want to fund Docusauurs with your monetization system, and added our own monetization link by default to all newly created sites: could users override such link? 🤷‍♂️

slorber avatar Sep 28 '22 17:09 slorber

What about just using the synthetic boottrap plugin above to provide a siteConfig.head or siteConfig.headTags API?

Like suggested here: https://github.com/facebook/docusaurus/issues/8049#issuecomment-1239493341

In the end it's the most flexible way to add any tag in the head, no matter what it is. anything else would just be a shortcut.

I'd rather start with more generic, less convenient apis first, and then add shortcuts like linkTags later (eventually). That the philosophy of the extensible web manifesto (https://extensiblewebmanifesto.org/).

Note that we already have a low-level api (plugin), but agree this shortcut (siteConfig.head) would be convenient.

slorber avatar Sep 29 '22 10:09 slorber

The API is not easy to discover but I think this impl should be good enough?

It works - but this is hard to discover and very verbose for what you're trying to achieve (add some link tags). Feels like you have to buy a car when you only want to drive a mile 😄

Also not a fan of "linkTags" naming. We already have stylesheets, scripts and metadata (which afaik is plural of metadatum) so "links" makes more sense to me (also similar to Remix: https://remix.run/docs/en/v1/api/conventions#links)

Happy with links as a name; would be happy to switch.

What about just using the synthetic boottrap plugin above to provide a siteConfig.head or siteConfig.headTags API?

Like suggested here: https://github.com/facebook/docusaurus/issues/8049#issuecomment-1239493341

I'd be happy to have a go at this - do you have any examples in the codebase of how this is done already? I can't see any piping examples in the codebase so far?

johnnyreilly avatar Sep 29 '22 12:09 johnnyreilly

Similarly, it can also be handled in the core synthetic bootstrap plugin:

export function createBootstrapPlugin({
  siteDir,
  siteConfig,
}: LoadContext): LoadedPlugin {
  const {
    stylesheets,
    scripts,
    head, // <==== NEW
    clientModules: siteConfigClientModules,
  } = siteConfig;
  return {
    name: 'docusaurus-bootstrap-plugin',
    // other things
    injectHtmlTags: () => {
      const stylesheetsTags = [];
      const scriptsTags = [];
      return {
        headTags: [
          ...head, // <==== NEW
          ...stylesheetsTags, ...scriptsTags],
      };
    },
  };
}

Does it make sense?

slorber avatar Sep 29 '22 13:09 slorber

Sorry no - I don't follow! I don't know what the createBootstrapPlugin does.... And I'm not sure what consumption would then look like in a docusaurus.config.js?

johnnyreilly avatar Sep 29 '22 13:09 johnnyreilly

I think what you're saying is add something extra to this: https://github.com/facebook/docusaurus/blob/5746c58f4145f56b92512095bce71428638d9d45/packages/docusaurus/src/server/plugins/synthetic.ts#L17

But I don't know how that connects to the docusaurus.config.js?

I guess the question is: how do users use it? I'm guessing that all users are somehow using the createBootstrapPlugin and they just don't know it? Is that right? If so, how does that play out? Where would they supply links? Or rather head? And what does that look like?

johnnyreilly avatar Sep 29 '22 13:09 johnnyreilly

I'm guessing that all users are somehow using the createBootstrapPlugin and they just don't know it?

Exactly: all sites implicitly have this plugin by default.

I don't know what the createBootstrapPlugin does

It's just a default plugin added to all docusaurus sites. We use it to provide shortcuts because some features are already provided by lifecycle plugins so we can just implement something in core with... a plugin.

If we were to provide an siteConfig.webpack.configure() api, we'd use this too ;)


Use the code above where I added the NEW comments, and users will be able to use in siteconfig:

{head: {tag: "link", attributes: {rel: "monetization"}}}

slorber avatar Sep 29 '22 13:09 slorber

okay cool and they'd do it at the same level as stylesheets by the looks of it?

I think I could have a crack at this. Probably at the weekend.

johnnyreilly avatar Sep 29 '22 13:09 johnnyreilly

Yes, I think handling siteConfig.stylesheets, siteConfig.scripts and siteConfig.head in the same way makes sense. The 2 former are just shortcuts for the later which is more low-level and flexible.

I'm not against adding a siteConfig.links too as it looks like a convenient shortcut and if we already have 2 similar shortcuts, so why not 3?

But the more important is to provide the low-level API so that if someone wants to add a weird unexpected tag to head (not script, link nor stylesheet), they could. For some reason we started by providing shortcuts instead of the more flexible low-level API 🤷‍♂️

slorber avatar Sep 29 '22 13:09 slorber

Cool - I think I get it. I'll have a go and report back!

johnnyreilly avatar Sep 29 '22 13:09 johnnyreilly

Closed in favour of @slorber's suggested alternate implementation: https://github.com/facebook/docusaurus/pull/8151

johnnyreilly avatar Sep 30 '22 19:09 johnnyreilly