vuejs / vitepress

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

Metadata for collections of pages (blogs etc.) #96

Closed petedavisdev closed 1 year ago

petedavisdev commented 3 years ago

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

I would like to be able to display a collection of pages (e.g. my latest blog posts or an alphabetized list of API docs). VuePress has $site.pages, but VitePress does not ship metadata about all pages, which is great except when I need that data for certain pages.

Describe the solution you'd like

I would like to specify collections of pages that I need metadata for in config.js, for example:

module.exports = {
  title: "My Tech Blog",
  collections: [
    {
      name: 'blog',
      directory: '_posts/',
      layout: 'BlogPostLayout',
    },
    {
      name: 'api',
      directory: 'guide/api/',
      layout: 'TechDocLayout',
    }
  ],
};

This would produce an array of metadata for pages inside the _posts directory could then be accessed via $site.collections.blog

I've also included a layout option that could be used to define a default layout for pages in that collection. That's a separate idea, but the point is that collections could have additional benefits.

You could possibly specify which metadata you need - e.g. you may or may not need the frontmatter for every page in the collection.

I've borrowed the term "collections" from NetlifyCMS, which I use with VuePress currently.

Describe alternatives you've considered

Alternatively, you could simply have a config option to ship metadata for all pages, but that would be all or nothing.

Additional context

Here's an example of how I've implemented collections in a VuePress theme: themeConfig.js, PostList.vue, GlobalLayout.vue

petedavisdev commented 3 years ago

To keep it lightweight I'd suggest collections are shallow - only include metadata for .md files that are directly inside the specified folder. If you wanted a subfolder, that would be another collection.

Also to keep it light, the inclusion of frontmatter in the metadata could be optional.

kiaking commented 3 years ago

Currently, VitePress is designed to be very small, and opinionated. While this feature sounds nice, I think this feature is more toward blogging. We're currently not sure if we want VitePress to have these kind of feature.

I think we need to wait until we really decide the key difference between VuePress and VitePress.

petedavisdev commented 3 years ago

That makes sense for now - I love how minimalist VitePress is.

I do think this will be a common request. Even if the primary purpose of VitePress is for documentation sites, it's common for people to add a simple blog or some other kind of page listing to their docs site.

I might have a play and see if I can get this working, just as an example of a possible implementation.

jivane-perich commented 3 years ago

Seems possible with a customMetadata (as shown here in vuejs blog : https://github.com/vuejs/blog/blob/master/.vitepress/config.js )

brc-dd commented 1 year ago

I just tested writing a blog with VitePress. It was quite easy actually. I was able to write a short script that generated a json file for all articles:

import fs from 'node:fs/promises'
import matter from 'gray-matter'
import removeMd from 'remove-markdown'

const articles = await fs.readdir('./blog/')

const data = await Promise.all(
  articles.map(async (article) => {
    const file = matter.read(`./blog/${article}`, {
      excerpt: true,
      excerpt_separator: '<!-- more -->'
    })

    const { data, excerpt, path } = file
    const contents = removeMd(excerpt).trim().split(/\r\n|\n|\r/)

    return {
      ...data,
      title: contents[0].replace(/\s{2,}/g, '').trim(),
      path: path.replace(/\.md$/, '.html'),
      excerpt: contents.slice(1).join('').replace(/\s{2,}/g, '').trim()
    }
  })
)

await fs.writeFile('./data.json', JSON.stringify(data), 'utf-8')

Here is the complete repo: https://github.com/brc-dd/vitepress-blog-demo

Final result (didn't do any styling):

image

I guess this feature is less likely to be supported officially. It will be better if someone can write a Vite plugin to auto-generate that data before build.

Something like this can also be done: https://github.com/vuejs/blog

staghouse commented 1 year ago

Has there been any more discussion about wether or not to include something like this.$site.pages from VuePress in to VitePress? I understand VitePress should be light weight but if the core belief is to provide a SSG website based on markdown then to me it makes sense to provide some sort of leveraging in to all data of the generated pages. The more relevant data the better IMO.

Charles7c commented 1 year ago
import fs from 'node:fs/promises'
import matter from 'gray-matter'
import removeMd from 'remove-markdown'

const articles = await fs.readdir('./blog/')

const data = await Promise.all(
  articles.map(async (article) => {
    const file = matter.read(`./blog/${article}`, {
      excerpt: true,
      excerpt_separator: '<!-- more -->'
    })

    const { data, excerpt, path } = file
    const contents = removeMd(excerpt).trim().split(/\r\n|\n|\r/)

    return {
      ...data,
      title: contents[0].replace(/\s{2,}/g, '').trim(),
      path: path.replace(/\.md$/, '.html'),
      excerpt: contents.slice(1).join('').replace(/\s{2,}/g, '').trim()
    }
  })
)

await fs.writeFile('./data.json', JSON.stringify(data), 'utf-8')

thanks.

image image

zRains commented 1 year ago

@brc-dd Is it possible to get all page info in distributed theme?

brc-dd commented 1 year ago

Is it possible to get all page info in distributed theme?

@zRains there is no direct way at the moment. The alternatives I had mentioned would work. New transformHTML/buildEnd hooks might be helpful too.

staghouse commented 1 year ago

While I have found work around with custom scripts to create page data I think VitePress needs to make a stand about wether or not its going be support site level page data like VuePress has, or not. I understand we have these hooks to help us now but there's not documentation examples on really how to use these hooks to do anything meaningful.

I'd like to see some updated docs for examples of getting all page data if VitePress is really never going to return a collection of pages meta data.

kiaking commented 1 year ago

Now we have Build-Time Data Loading. Would this solve this issue?

brc-dd commented 1 year ago

Refer the example given here: https://vitepress.dev/guide/data-loading#data-from-local-files

We won't be providing data from all the pages like VuePress does.

Charles7c commented 1 year ago
// article.data.js
import fs from 'node:fs';
import path from 'node:path';
import parseFrontmatter from 'gray-matter';

const excludedFiles = ['index.md', 'tags.md', 'archives.md', 'me.md'];

export default {
  watch: ['./docs/**/*.md'],
  load(watchedFiles) {
    // 排除不必要文件
    const articleFiles = watchedFiles.filter(file => {
      const filename = path.basename(file);
      return !excludedFiles.includes(filename);
    });
    // 解析文章 Frontmatter
    return articleFiles.map(articleFile => {
      const articleContent = fs.readFileSync(articleFile, 'utf-8');
      const { data } = parseFrontmatter(articleContent);
      return {
        ...data,
        path: articleFile.substring(articleFile.lastIndexOf('/docs/') + 6).replace(/\.md$/, ''),
      }
    })
  }
}

How to exclude unnecessary files in watch? @brc-dd

brc-dd commented 1 year ago

@Charles7c Something like this should work (might need to slightly adjust paths):

  watch: ['./**/*.md', `!${__dirname}/docs/{index,tags,archives,me}.md`],
brc-dd commented 1 year ago

We also provide a createContentLoader helper: https://vitepress.dev/guide/data-loading#createcontentloader

So, now you don't need to do much custom stuff, just a simple export and optionally some configuration would do the job!