content icon indicating copy to clipboard operation
content copied to clipboard

Resolve assets in markdown content

Open TixieSalander opened this issue 4 years ago • 98 comments

I would like to know if it's possible to have the webpack assets resolution inside of markdown content ? To display webpack handeled's images.

Reproduction link: https://codesandbox.io/s/dawn-snowflake-fv7l4?file=/content/post.md

  1. I have an image in the assets folder
  2. I link the image in the md file ![](~/assets/photo.jpg) (or <img src="~/assets/photo.jpg"> in html) the same way I would have in a component template

I'm not interested in putting files in the static folder since there is no compression and no hash for cache-busting.

TixieSalander avatar Jun 05 '20 14:06 TixieSalander

As far as I know, this is currently not supported out of the box.

Maybe you can solve this with a component that dynamically loads the image for you, something like this? https://codesandbox.io/s/nuxt-content-106-hwhbv?file=/content/post.md

dtmzr avatar Jun 06 '20 06:06 dtmzr

Yeah I thought about a solution like that, but not really compliant with content management tools :/

TixieSalander avatar Jun 08 '20 09:06 TixieSalander

Hi there,

The main issue here is that @nuxt/content is independent of Webpack, meaning that if you change anything in content/ you can directly run nuxt export (Nuxt 2.13) without having to wait for Webpack to build.

We are thinking of a way to support assets/images/ with Webpack dynamic import so it can still support nuxt export without having to build again (expect if you add an image in assets/images/)

atinux avatar Jun 08 '20 09:06 atinux

I see it was removed from the todo list. Any update on the situation of this feature request?

TixieSalander avatar Jun 25 '20 17:06 TixieSalander

Bumping, would love an update seeing as it has been removed from the To do?

MattFaz avatar Jul 10 '20 02:07 MattFaz

Don't pay too much attention to the todo list, we use it internally to plan the next release.

We have considered this issue and will be working on it soon.

benjamincanac avatar Jul 10 '20 16:07 benjamincanac

The tricky part is to not involve Webpack for it when using images since it would force a rebuild and loose te speed of content update. This is why it is not an such easy task 😁

atinux avatar Jul 13 '20 08:07 atinux

@Atinux might overwriting the html img tag by a custom component with the same attributes work for this case? Maybe even as an opt in? 🤔

dtmzr avatar Jul 13 '20 08:07 dtmzr

Indeed your solution was quite good actually, if you can open a PR so we can work on it it would be great :)

atinux avatar Jul 13 '20 09:07 atinux

The tricky part is to not involve Webpack

@Atinux Maybe I'm missing something but why wouldn't I want webpack involved doing it's thing? Wouldn't I want webpack to rerun if content involving a webpack asset gets updated?

Spunkie avatar Jul 13 '20 19:07 Spunkie

The idea behing content is that you can change your Markdown without rebuilding your entire Nuxt app, making it faster redeployment.

Tracking assets is a bit more tricky in that case since Content does not depend of Webpack at all.

atinux avatar Jul 14 '20 14:07 atinux

I fiddled around with it today and think I understood the way in which it could be implemented.

I would be really interested in your thoughts about the API design. Should we support both, markdown images and HTML tags?

![image alt text](~/assets/localimage.jpg)
<img src="~/assets/localimage.jpg" alt="image alt text>

I have a few doubts about using the default tags and attributes because in this case, we would've to check if the developer is trying to load a local asset or remote image.
Maybe we could avoid this check by adding a custom attribute:

<img local-src="~/assets/localimage.jpg" alt="image alt text>

dtmzr avatar Jul 15 '20 21:07 dtmzr

Yeah both should be supported, this will give more control since you can't do basic things like center an image with markdown

dr1ss avatar Jul 16 '20 21:07 dr1ss

Is it possible to store the images under the content directory itself instead of in the assets directory? Then you could create a structure like:

content/articles/my-new-article/index.md content/articles/my-new-article/img/pic1.jpg content/articles/my-new-article/img/pic2.jpg content/articles/my-new-article/img/pic3.jpg

which would keep all the content together.

davydnorris avatar Jul 19 '20 13:07 davydnorris

Yes but this should not be the only solution.. again to have more control pointing to a specific folder is useful in case someone wants to use a CDN/reverse proxy to serve the images.

dr1ss avatar Jul 20 '20 14:07 dr1ss

@davydnorris suggestion was helpful to me

I created a global Image component to work around with that and stored my images inside the /content/blog/my-blog-slug/images/

<template>
  <img :src="imgSrc()" :alt="alt" />
</template>

<script>
export default {
  props: {
    src: {
      type: String,
      required: true
    },
    alt: {
      type: String,
      required: true
    }
  },
  methods: {
    imgSrc() {
      try {
        return require(`~/content${this.src}`)
      } catch (error) {
        return null
      }
    }
  }
}
</script>

Make sure to register this component globally. Inside components/global/Image.vue and set the components:true in nuxt.config.js

<image
          v-if="blog.Featured_Image"
          :src="blog.dir + '/images/' + blog.Featured_Image"
          :alt="blog.Title"
        >
</image>

Inside Markdown:

<image src="/blog/my-blog-slug/images/Scripts.png" alt="Scripts"></image>

I hope this will be helpful to someone. 🙌

MuhaddiMu avatar Jul 25 '20 16:07 MuhaddiMu

I did the same thing but I am getting warnings when I compile the files of the form:

WARNING in ./content/blog/2020-02-15-blog-number-3/index.md 1:2
Module parse failed: Assigning to rvalue (1:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> ---
| title: 'Blog #3 Headline'
| description: Lorem Ipsum type stuff
 @ . sync ^\.\/content.*$ ./content/blog/2020-02-15-blog-number-3/index.md
 @ ./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib??vue-loader-options!./components/BlogImage.vue?vue&type=script&lang=js&
 @ ./components/BlogImage.vue?vue&type=script&lang=js&
 @ ./components/BlogImage.vue
 @ ./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib??vue-loader-options!./pages/blog/_slug.vue?vue&type=script&lang=js&
 @ ./pages/blog/_slug.vue?vue&type=script&lang=js&
 @ ./pages/blog/_slug.vue
 @ ./.nuxt/router.js
 @ ./.nuxt/index.js
 @ ./.nuxt/client.js
 @ multi ./.nuxt/client.js

I thought I could ignore the warning, however when I then try to run export or dev, the process hangs and never completes, and I see the above warning in the generated javascript instead of the actual content.

I'm not a webpack/babel config file expert, but it looks like the error is from webpack when it goes looking for the image files. I assume you could write a rule to exclude the md files somehow? But then would that mess up the content generation?

Any help would be greatly appreciated

davydnorris avatar Jul 26 '20 04:07 davydnorris

Unfortunately, this is the only solution worked for me. I have the same warnings. However, it doesn't affect the build process.

MuhaddiMu avatar Jul 26 '20 08:07 MuhaddiMu

OK so I have made progress...

I couldn't work out why my images were being converted during the build but their paths weren't - then I realised that the images I happened to be using were IMG_xxxx.JPG and I had them in the code as IMG_xxxx.jpg. As soon as I made them exactly the same they all got picked up.

Still trying to get the custom image component inside the .md to work - @MuhaddiMu did you register yours as a global?

davydnorris avatar Jul 28 '20 04:07 davydnorris

@davydnorris My Image component is registered globally. I have a working example, you can check that too by cloning https://github.com/MuhaddiMu/Portfolio

MuhaddiMu avatar Jul 28 '20 04:07 MuhaddiMu

Many thanks - I have now got it working 🥇

The components directive doesn't seem to do exactly what it says in the guide. I had set up a subdirectory specifically for blog components, called 'content', and had registered that in my nuxt config with:

components: [
  '~/components/content'
]

but it didn't seem to work. I then moved the components into the top level and set components:true, but still no luck.

It finally worked when I copied your example exactly and added a 'global' subdirectory with components:true.

Thank you for all your assistance!!

davydnorris avatar Jul 28 '20 05:07 davydnorris

Besides images used in HTML and markdown, what about images referenced in frontmatter? I've had to add these to static/images in order for this to work instead of assets.

img: /images/journey-ux.jpg
alt: Rocket soaring through space

djmtype avatar Jul 29 '20 21:07 djmtype

This were actually the first ones I got to work. I used the same approach as above but could use a normal component in my _slug.vue and preview components:

<template>
  <div>
    <v-img
      :src="require(`@/content${post.dir}/img/${post.image}`)"
      :alt="post.title"
      height="400"
    />
    <h1>{{ post.title }}</h1>
    <p>
      {{ post.description }}
    </p>
    <nuxt-content
      :document="post"
    />
  </div>
</template>

<script>
export default {
  async asyncData ({ params, error, $content }) {
    try {
      const postPath = `/blog/${params.slug}`;
      const [post] = await $content('blog', { deep: true })
        .where({ dir: postPath })
        .fetch();
      return { post };
    } catch (err) {
      error({
        statusCode: 404,
        message: 'Page could not be found'
      });
    }
  }
};
</script>

in your case you've already included the image subdirectory so just adjust the template accordingly

davydnorris avatar Jul 29 '20 23:07 davydnorris

@davydnorris Regarding the Module parse failed: Assigning to rvalue (1:2) error. Using ignore-loader solved this for me:

// nuxt.config.js
// ...
build: {
	extend(config, { isDev, isClient }) {
		config.module.rules.push({
			test: /\.md$/i,
			loader: 'ignore-loader'
		})
	}
}
// ...

lucien144 avatar Jul 30 '20 10:07 lucien144

Many thanks @lucien144 - that's exactly what I was hoping someone would know! Will try that tomorrow

davydnorris avatar Jul 30 '20 13:07 davydnorris

My original point was to be able using the webpack asset management for images (compression, cache-busting), while writing posts on content management tools. For that, it's needs to work with vanilla , and not some custom vuejs elements.

TixieSalander avatar Aug 05 '20 20:08 TixieSalander

Hi there,

The main issue here is that @nuxt/content is independent of Webpack, meaning that if you change anything in content/ you can directly run nuxt export (Nuxt 2.13) without having to wait for Webpack to build.

We are thinking of a way to support assets/images/ with Webpack dynamic import so it can still support nuxt export without having to build again (expect if you add an image in assets/images/)

What about linking to assets that are not images? For example, linking to a local pdf file with [A file](/assets/sample.pdf) in the markdown? It's not mentioned on this thread but I presume it's essentially the same issue?

ZeanQin avatar Aug 13 '20 06:08 ZeanQin

Yes. Assets from Markdown written with fully qualified URL ~/images/ to be resolvable either from Vue pages/ or components/ components as equal to from a Markdown file. I'm assuming we could have the place where to store those as configurable. Either from static, or from content, or assets. So we avoid having more than one notation for the same resource and the resource resolver in its context (remark's toTHML, or WebPack, or vite) to pick which ever its running.

renoirb avatar Sep 10 '20 18:09 renoirb

So many ugly crutches just to render image from Markdown, oh God. Nuxt is so 'intuitive' that I had less time to do it by myself with 11ty or at my self made ssg on Python.

Defite avatar Dec 03 '20 13:12 Defite

extend(config, { isDev, isClient }) {
		config.module.rules.push({
			test: /\.md$/i,
			loader: 'ignore-loader'
		})
	}

now page with image doesn't render at all, thank you very much!

Defite avatar Dec 03 '20 13:12 Defite