vuejs / vitepress

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

Recommended way to use Custom Vue components in .md #157

Closed patak-dev closed 2 years ago

patak-dev commented 3 years ago

I am looking into porting the using vue in Markdown from vuepress: https://vuepress.vuejs.org/guide/using-vue.html

If I understand correctly, vitepress doesn't want to auto register components by convention as Vuepress does. Is this the case? I actually liked this feature, but I understand that vitepress wants to keep the moving parts as small as possible.

What is the recommended way to register the components? I see that in vue-router-next docs they are registered globally inside enhanceApp: https://github.com/vuejs/vue-router-next/search?q=HomeSponsors. Same as with this comment: https://github.com/vuejs/vitepress/issues/92#issuecomment-724645482

Should we document this way in the docs?

Some thoughts about this. It would be great that users that want to use the default theme as is, do not need to learn straight away about enhanceApp to be able to use a vue component in their markdown.

If auto registering by convention in a folder like .vitepress/components is not an option, could we import them directly in the markdown?

# Docs

This is a .md using a custom component

<CustomComponent />

## More docs

...

<script setup>
  import CustomComponent from '../components/CustomComponent.vue'
</script>

Script & style hoisting is working in vitepress: https://vuepress.vuejs.org/guide/using-vue.html#script-style-hoisting, but I tried this example to import a Component and it is not at this point.

kiaking commented 3 years ago

If I understand correctly, vitepress doesn't want to auto register components by convention as Vuepress does. Is this the case?

Yes, at the moment it's. This is a kind of feature that we would want to tell users to use VuePress instead.

A little background on this. It might subject to change in the future, but for now, VitePress is aiming to be smaller VuePress. Like Lumen for Laravel (if you're familiar with PHP 👀). VitePress should have bare minimal features for authoring docs, and for simple markdown based build system for Vue, and all necessary features like Search, Service Workers, built in to the core, where else VuePress has those features enabled by plugins. That's reason why we're merging features like Carbon Ads.

And also to note, VuePress might integrate with Vite in the future. So that user can get benefits from Vite, with all the powerful features in VuePress (we should consider the naming though 😅).

However, maybe we could provide a script that traverse file system and auto include components inside .vitepress/theme/index.js (not sure if possible yet), then add that as a recipe in the docs.

What is the recommended way to register the components? I see that in vue-router-next docs they are registered globally inside enhanceApp

Yes, this is the way to go about it. However, I would really like to make this customization process much more smoother. Currently, creating a whole new theme, is easy but extending the default theme is painful. You have to import theme from vitepress/dist/theme-default and such.

So yes, we should document this, but before doing that I want to rethink, and refactor how to do the customization. So I guess we can use this issue to discuss about it!

Here's what I'm thinking, but not 100% sure yet.

Create brand new theme

export default {
  Layout: CustomLayout,
  NotFound: CustomNotFound,
  enhanceApp({ app, router, siteData }) {}
}

Very well. Easy. Good enough.

Only adding components

import Theme from 'vitepress/dist/client/theme' // <- rename to theme instead of default-theme
import CustomComponent from './components/CustomComponent.vue'

export default {
  ...Theme,
  enhanceApp({ app, router, siteData }) {
    app.component('CustomComponent', CustomComponent)
  }
}

I don't like 'vitepress/dist/client/theme' path... but... hmmm....

Extending default theme layout

import 'vitepress/dist/client/styles' // import global css

import NotFound from 'vitepress/dist/client/theme/NotFound.vue' // Use default NotFound component.
import Layout from './components/Layout.vue' // Use your own layout

export default {
  Layout,
  NotFound,
  enhanceApp({ app, router, siteData }) {
    app.component('CustomComponent', CustomComponent)
  }
}

And in Layout.vue

<template>
  <Layout>
    <!-- Inject something to slot -->
    <template #page-top>
      <p>Hello page top!</p>
    </template>
  </Layout>
</template>

<script setup>
import Layout from 'vitepress/dist/client/theme/Layout.vue'
</script>

I guess simple enough? Importing global CSS is not that... hmmm...

patak-dev commented 3 years ago

And also to note, VuePress might integrate with Vite in the future. So that user can get benefits from Vite, with all the powerful features in VuePress (we should consider the naming though 😅).

So using Vite instead in VuePress, and not extending VitePress? Interesting, there could be a lot of duplication in that case 🤔

However, maybe we could provide a script that traverse file system and auto include components inside .vitepress/theme/index.js (not sure if possible yet), then add that as a recipe in the docs.

I like this, if you place it in the theme, you are not polluting the tool. Maybe it can be a config for the theme? Something like:

  themeConfig: {
    ...,
    components: './docs/components' // defaults to none
  }

(Or componentsDir, globalComponents)

+1 for renaming default-theme to theme.

For the paths, it would be great if we could tell users to just use something like the code uses internally

import Theme from '/@theme'
patak-dev commented 3 years ago

Two ideas about importing the theme, sorry if this was discussed already.

  1. Move the default theme to its own package. So users would import it like

    import { Theme, NotFound } from 'vitepress-docs-theme' 

    This may also help to force vitepress to properly expose everything the default theme uses, so other themes can be play at the same level.

  2. Another option would be to provide a new helper enhanceTheme (or enhanceDefaultTheme) that is defined as:

    function enhanceTheme(enhanceApp,custom) {
    import('../client/styles') // (can we do something like this?)
    return {
    ...Theme,
    ...custom,
    enhanceApp
    }
    }

So your examples can be written as

Only adding components

import CustomComponent from './components/CustomComponent.vue'

export default enhanceTheme(
  ({ app, router, siteData }) => {
    app.component('CustomComponent', CustomComponent)
  }
)

And Extending default theme layout

import Layout from './components/Layout.vue' // Use your own layout

export default enhanceTheme(
  ({ app, router, siteData }) => {
    app.component('CustomComponent', CustomComponent)
  }, 
  { Layout }
)
yyx990803 commented 3 years ago

FWIW, adding this in .md file should already work:

<script setup>
  import CustomComponent from '../components/CustomComponent.vue'
</script>
patak-dev commented 3 years ago

I just tested it and works fine 👍
I think I tried it before vue 3.0.3, my bad, and that is why it wasn't working.

In my opinion, importing the components you use on a page directly in the .md is quite nice. At least for one-of components (like examples, playgrounds, etc). It would be interesting to document this in the use Vue in markdown guide.

For components like or a that you end up using everywhere across your docs, there should still be an easy way to register global components.

harlan-zw commented 3 years ago

While you can write HTML directly into the md file, due to the md parsing it becomes almost impossible to do anything which would need any sort of layout.

When trying to get around this issue myself I ended up copying over the entire default theme folder so I could add my custom component to components and setting it up in enhanceApp.

I think it needs to be clear and easy for developers to add a custom component. It seems like it's trending to magically load them in when they're needed(Nuxt.js, Vuetify, etc).

I'm not sure about the performance cost of it but having a configurable components folder in .vitepress that automatically imported components as you used them, seems like a solid DX improvement.

yyx990803 commented 3 years ago

In the next release you can do

// .vitepress/theme/index.js
import { defaultTheme } from 'vitepress'
import MyGlobalComp from './Comp.vue'

defaultTheme.enhanceApp = ({ app }) => {
  app.component('MyGlobalComp', MyGlobalComp)
}

And yes we can consider auto registering components in a directory as global.

Also not sure what you mean by

due to the md parsing it becomes almost impossible to do anything which would need any sort of layout.

since you can use <script> and <style> in md files just like in an SFC.

harlan-zw commented 3 years ago

Thanks for the reply @yyx990803 and good to know! That would make life easier.

Sorry for not being clear on the markdown parsing. Previously I had an issue using HTML markup within the markdown file, it was rendering out incorrectly (was moving divs around). I've tried to replicate it now but looks like I can't, so was likely an issue on my end.

Irrelevant to this issue either way.

TheDutchCoder commented 3 years ago

I use the following pattern currently:

// @ts-ignore
const modules = import.meta.globEager('../components/**/*.vue')
const components = []

for (const path in modules) {
  components.push(modules[path].default)
}

export default {
  ...DefaultTheme,
  enhanceApp({ app }) {
    components.forEach(component => {
      app.component(component.name, component)
    })
  }
}
kiaking commented 2 years ago

Closing since this is documented with details 👍