vuejs / vitepress

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

Images from custom components are not working when running build #3700

Closed fractalf closed 1 month ago

fractalf commented 1 month ago

Describe the bug

I have a custom component that renders an image for each markdown. It works fine running in dev, but not when building. VitePress does not detect it and copy it to the assets folder

Reproduction

VPDocHeader.vue

<script setup>
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
import { useRoute } from 'vitepress'

const { frontmatter: fm } = useData()
const route = useRoute()
</script>

<template>
    <div>
        <img class="rounded-t-xl" :src="route.path + (fm.image || 'banner.jpg')">
    </div>
</template>

.vitepress/theme/index.js (simplified for this example)

import VPDocHeader from '../components/VPDocHeader.vue'
export default {
    Layout() {
        return h(DefaultTheme.Layout, null, {
            'doc-before': () => h(VPDocHeader),
        })
    },
}

When running this with npm run dev it is rendered out like this

<img class="rounded-t-xl" src="/articles/001-vitepress-as-a-blog/banner.jpg">

Expected behavior

I would expect VitePress to copy this to to assets etc when building

System Info

Linux EndeavourOS
VitePress 1.0.1
NodeJs 21.7.1

Additional context

I know I can probably solve this by rewriting to use public/ ..but I would rather keep the images where the markdown files are.

Validations

brc-dd commented 1 month ago

That string is not statically analyzable. In dev it works because stuff outside public is also served and browsers resolve that during runtime. If you want it to work you'll either need to keep it in public or import that image and then pass it as prop. Importing in frontmatter is not supported though yet (it's planned but vite is missing certain public APIs).

Alternatively, you can write some script that copies non-markdown stuff to dist in buildEnd hook. But those assets won't be fingerprinted.

fractalf commented 1 month ago

Hi Divyansh, thanks a lot for getting back at me and your work on this probject :)

I woke up this morning and thought I actually could make this work. My first thought that came to me was the this is done in the initial example with features and icons. I was stoked and ran out of bed to try it out, but unfortunately this doesn't work as well, probably because of what you are saying here..

So, just to make that part clear

https://vitepress.dev/reference/default-theme-home-page#features-section

features:
  - icon:
      src: /cool-feature-icon.svg
    title: Another cool feature
    details: Lorem ipsum...

This out of the box example also doesn't work then, event though it's in the documentation.

Thanks for providing some possible ways to work around this issue

Public folder I know would work, but it's unfortunate because of the way I organize my content (each markdown in its own folder with images belonging to the document in the same folder).

import could potentially work, but the problem here is that the images are "dynamic" and not available until after imports are done.

I guess I'm left with the option of making a script to copy files to the dist folder. I already have a deploy script that rsyncs the content to my server, so this should be managable.

When you say "finterprinted", you are talking about the cache-bust string in the filename right?

my-image.P2GuQSTo.jpg

Thanks again and have a great day :)

brc-dd commented 1 month ago

When you say "finterprinted", you are talking about the cache-bust string in the filename right?

Yeah.

This out of the box example also doesn't work then, event though it's in the documentation.

It's using /cool-feature-icon.svg -- base relative path -- which are for content inside public directory.

fractalf commented 1 month ago

Ok, cool, thanks.

I made a quick script to fix this, pasting it here for reference if anyone else has use for it

// .vitepress/config.mjs
export default defineConfig({
    ..
    async buildEnd(siteConfig) {
        // Get each article (markdown with frontmatter data)
        const articles = await createContentLoader('/articles/**/*.md').load()
        for (const article of articles) {
            // article.url is the directory where I keep the images
            const image = article.frontmatter.image || 'banner.jpg'
            const src = __dirname + '/..' + article.url + image
            const dst = __dirname + '/dist' + article.url + image
            fs.copyFileSync(src, dst);
        }
    },
    ..
})

Also for reference, this is what I'm working on (a tutorial on how to set up VitePress from scratch) https://abc.fractalf.net/articles/001-vitepress-as-a-blog/