vitepress
vitepress copied to clipboard
Import custom component in MarkdownIt plugin
I have a VitePress plugin which allows me to render a component next to the code for that component. This is very useful for showcasing how to use components in a library.
From the Markdown input:
# MyButton
An example of the button component!
:@/ButtonSample.vue:
The desired output is the following:
Where ButtonSample.vue
is a file containing the code for the 2 snippets and renders to show the two buttons at the bottom.
As you can see, this plugin was working with Vitepress 1.0.0-alpha.10
but after upgrading to 1.0.0-alpha.11
, the snippets are rendered but the component is missing.
Here is the relevant snippet from the plugin:
const TEMPLATE_START = /^<template>$/
const TEMPLATE_END = /^<\/template>$/
const SCRIPT_START = /^<script setup lang="ts">$/
const SCRIPT_END = /^<\/script>$/
const COMPONENT_SNIPPET = (file: string) => `
<script setup lang="ts">
import DemoComponent from "${file}";
</script>
<div class="vp-raw">
<demo-component/>
</div>
`
// when creating a demo section, `@/` will be replaced with `srcDir`
export const demoPlugin = (md: MarkdownIt, srcDir: string) => {
const parser: RuleBlock = (state, startLine, _endLine, _silent) => {
/*... some code to extract the file name from the markdown ... */
token.attrSet('src', resolve(filename))
return true
}
const renderer: RenderRule = (...args) => {
/* ... some code to get `src` and check that the file exists ... */
const file = readFileSync(src, 'utf8')
// script
token.info = `ts`
token.content = findSection(file, SCRIPT_START, SCRIPT_END)
const script = md.renderer.rules.fence!(...args)
// template
token.info = `vue-html`
token.content = findSection(file, TEMPLATE_START, TEMPLATE_END)
const template = md.renderer.rules.fence!(...args)
// component
const demo = md.render(COMPONENT_SNIPPET(src))
return script + template + demo
}
md.renderer.rules.demo = renderer
md.block.ruler.before(md.block.ruler.getRules('')[0].name, 'demo', parser)
}
// returns everything between the first match of `start` and the subsequent first match for `end`
function findSection(content: string, start: RegExp, end: RegExp): string {
//...
}
So the markdown rendered used to take the line const demo = md.render(COMPONENT_SNIPPET(src))
and automagically put the import
statement where it needs to be and happily render the component. However, since updating to 1.0.0-alpha.11
this no longer works. In fact if I manually write out the import statement in the markdown file, everything works - so something is wrong with the way my plugin is handling the import.
I looked into the way plugin-sfc works and it looks like there was a change from 0.10.0
to 0.11.0
in the way script SFC blocks are handled.
I tried to copy the way the changed code works in my plugin, but I have not managed to get it working:
const block = `<script setup lang="ts"> import DemoComponent from "${src}"; </script>`
sfcBlocks.scriptSetup = block
sfcBlocks.scripts.push(block)
token.content = `<div class="vp-raw"> <demo-component/> </div>`
const demo = md.renderer.rules.html_block!(...args)
Does anyone know how this can be done? How can my plugin tell VitePress that it needs to import the demo component for this page?
I tried to do that.. (I think I managed.. but since I needed to add it once I change it again.. to be imported only once in another place...)
The step I did was 1 - Test adding the component in the md file itself by hand(testing it if is working this way) 2 - Remove the script section manually and in the plugin only for md files try inserting the script portion exactly as worked in your test (mine was one single line) 3 - If this worked you are halfway there.. Now you need to check if there is already an script tag in the file and change that , I always use script setup but we can't have 2 of those.
I don't think you should register the component in the markdown plugin. you should use an rollup hook for that.
I do that in here but it is always the same component and I have to register it globally
@emersonbottero Yes, as I mentioned, manually adding the import statement in the Markdown file does work. When I try to add the import statement from the plugin it doesn't work.
In the code you linked, you are just registering one component globally, where as I am trying to register arbitrary components for different pages. If I knew all the components ahead of time I would just register them globally as shown in the docs.
You seem to be just pushing a string to scripts. You need to push a SfcBlock
instead. It looks something like this:
{
content: `<script setup lang="ts"> import DemoComponent from './DemoComponent.vue'; </script>`,
tagOpen: '<script setup lang="ts">',
type: 'script',
contentStripped: " import DemoComponent from './DemoComponent.vue'; ",
tagClose: '</script>'
}
Closing due to inactivity.