vuejs / vitepress

Vite & Vue powered static site generator.
https://vitepress.dev
MIT License
13.23k stars 2.13k forks source link

Accessing Vite asset URLs in head config #3161

Open fvsch opened 1 year ago

fvsch commented 1 year ago

Is your feature request related to a problem? Please describe.

To inject HTML tags in a page's <head>, as far as I understand the solution is to use the head or transformHead options.

My issue is that I’d like to reference assets in the <head>, for instance to link to a SVG favicon (same image for all pages) or to an opengraph image (different image for each page).

Currently I’m handling this by using absolute paths from the root of the domain, and put those images in the public/img folder. So the config might look like:

export default {
  head: [
    ['meta', { rel: 'icon', type: 'image/svg+xml', href: `/img/theme/favicon.svg` }]
  ],
  transformHead({ pageData }) {
    const { og_image } = pageData.frontmatter;
    if (og_image) {
      return [['meta', {property: 'og:image', content: `/img/og/${og_image}` }]];
    }
  }
}

Here the downsides are:

Describe the solution you'd like

It would be great if we could use Vite to handle those assets.

Currently that's not possible in .vitepress/config.js, since this is executed in a Node.js context and special bundler imports like import svgFaviconUrl from './img/favicon.svg'; won't work.

I wonder if that's something that could be done in the transformHead hook or in a different hook. If not, that's fine, I can live with the workarounds; but I just thought I'd ask.

Describe alternatives you've considered

One possible workaround would be to load those assets somewhere, maybe in a custom layout component, to register those assets in the Vite assets.

Then in the transformHead hook, you actually have access to a context.assets array, which can look like:

[
  '/assets/abcd.77593610.png',
  '/assets/efgh.2984b36b.gif',
  '/assets/ijkl.5e11e538.gif',
  '/assets/mnop.afe27ee2.png',
  // …
]

That's almost usable, but because it's not a manifest which also shows the original paths, it's hard to resolve the correct paths. If I’m looking for .vitepress/theme/img/favicon.svg, and there is another favicon.svg somewhere else in the assets, how do I know if I should use /assets/favicon.2e0fc5e5.svg or /assets/favicon.c7ab5b73.svg?

So I’m wondering, if the Vite manifest available at this point, and could it be exposed in the context for the transformHead hook?

Or is that something that is too low-level for transformHead, and it should be a custom Vite plugin instead?

Additional context

No response

Validations

fvsch commented 1 year ago

Hmm maybe using a mix of:

  1. Frontmatter to define the name or path of an asset to load.
  2. Vite glob imports to pre-load all the necessary assets.
  3. A custom layout component using a <Teleport> to render a meta tag.
  4. And the postRender hook to write the teleport's HTML result to the <head>.

Quite a bit of indirection, but maybe doable?

fvsch commented 1 year ago

Looks like this docs example uses the context.assets array in transformHead, which works if you're sure assets won't have similar names:

https://vitepress.dev/guide/extending-default-theme#using-different-fonts

brc-dd commented 1 year ago

Can be done once Vite exposes their assets API - https://github.com/vitejs/vite/issues/7162 -- we can also support some syntax like og_image: import('./path/to/img') in frontmatter itself then.