-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support asynchronous rendering for MDC components as a path to reusable "MDC snippet" components #263
Comments
Hello @adamdehaven In order to support async rendering, We need to update this function mdc/src/runtime/components/MDCRenderer.vue Lines 336 to 344 in 9f25fa1
Note that if you use MDC module outside Nuxt environment, everything should wrap inside |
@farnabaz would you be able to show me an example of how you would suggest updating this function to wrap the component? I've tried a few different variations (in this function, and a few other places) utilizing Any guidance/example here would be much appreciated |
I tried this, but it still doesn't wait to resolve: const resolveVueComponent = (component: any) => {
if (typeof component === 'string') {
return htmlTags.includes(component)
? component
: defineAsyncComponent(() => new Promise((resolve) => {
resolve(resolveComponent(pascalCase(component), false) as any)
}))
}
return component
} @farnabaz is this what you had in mind? |
Did you wrap everything in suspense? |
It’s inside a Nuxt app page, so the page itself is already wrapped I believe? Would I need to wrap each individual component as well? I didn’t try using a nested |
I attempted to utilize <template>
<Suspense suspensible>
<MDCRenderer
v-if="!!snippetName && ast?.body && !snippetError"
:body="ast.body"
:data="ast.data"
:data-testid="!!snippetName ? snippetName : undefined"
/>
</Suspense>
</template> (I also attempted adding another Suspense in But it still doesn't wait to resolve 😅 - @farnabaz show me what I'm missing? |
Interesting, Could you provide a reproduction with suspense? |
@farnabaz here you go: https://stackblitz.com/github/adamdehaven/mdc/tree/feat/async-rendering?file=src%2Fruntime%2Fcomponents%2FMDCRenderer.vue This reproduction is a fork of the repo with the following changes:
|
I see, the glitch you are facing is not related to content renderer anymore. You have two async data in your component, and the second one is Merge two async data. const { data: ast, execute: parseSnippetData } = await useAsyncData(`parsed-${fetchKey.value}`, async (): Promise<MDCParserResult> => {
const { content } = await $fetch('/api/markdown', {
query: {
name: 'snippet'
}
})
const parsed = await parseMarkdown(content)
// Extract the `body` and destructure the rest of the document
const { body, ...parsedDoc } = parsed
// Important: Remove invalid snippets from the AST
const processedBody = removeInvalidSnippets(body)
// Return the MDCParserResult with the sanitized body
return {
...parsedDoc,
body: processedBody as MDCRoot
}
}, {
transform,
getCachedData
}) |
Ah I see! I can actually just move the conditional logic that fires the immediate: !!snippetName.value && (!!sanitizedData && !!snippetData.value?.content) && !snippetError.value With my repro, I never want to fetch the data if these conditionals evaluate to @farnabaz this all works perfectly now (thanks for your help!) and I've put together a pull request, along with adding a playground example and documentation: Please let me know if I can make any changes to the PR; otherwise, I'd love to see this merged in 🎉 |
This PR closes nuxt-modules#266. This PR resolves nuxt-modules#263.
Update: #266 (comment) |
Supporting reusable "snippet" components
It would be great to allow users to define a subset of markdown documents (MDC) that can be utilized in a project as reusable "snippets" that can be utilized in a parent document by means of a custom block component that handles fetching the snippet markdown content, and rendering in a nested/child
MDCRenderer
component.For my use case, the snippet markdown will be fetched from an external API; however, I believe the Nuxt Content module, along with Nuxt Studio could easily be adapted to support reusable "snippets" that can be embedded in parent documents as block components.
My goal is to get this working for my own project, but I 100% would like to port this functionality to Nuxt Content and Nuxt Studio for use by the wider ecosystem.
Questions
Is it possible to defer the resolution of a parent
MDCRenderer
component so that it waits for all child nodes to be resolved?I realize there could be a performance hit here if the page were to contain ~100 separate calls for other documents. Any suggestions on optimization or alternative solutions are welcome.
For the first iteration in the reproduction outlined below, I was thinking of limiting the number of "levels" of components allowed (see the
snippetNestingLevel
variable in/components/global/PageSnippet.vue
).Reproduction
https://stackblitz.com/github/adamdehaven/mdc-repros/tree/chore/snippets?file=README.md
This reproduction renders nested markdown content (e.g. reusable "snippets") from nested
MDCRenderer
components.The initial markdown content fetched by a page would contain additional MDC
page-snippet
block components that internally fetch additional raw markdown to be rendered in their ownMDCRenderer
component. Since the child components need to fetch their content before the top-level parent should resolve, there needs to be a way to "defer rendering" of the parent until all of the child nodes have been processed and had a chance to fetch and render their content.If there are too many
page-snippet
components (e.g. if the data in all components takes a bit to load), the parent component inindex.vue
resolves before all of the internalpage-snippet
components have finished fetching their data.I have also added a
/utils/serveCachedData.ts
helper that caches the data after the first load so that navigating to the second page at/second
and coming back to the home route/
then renders the data instantly from cache. This is not relevant to the actual issue seen, but shows that once the snippet data is fetched and cached, there's no issue in rendering the nested components. You can even go multiple nesting levels deep.When running the project, you'll observe:
Nested Content
list items only load 2-3 on the server, and then reload and fill in the other items in the client, causing a hydration error in the console.PageSnippet.vue
in aClientOnly
tag doesn't resolve the actual loading/timing issue (and the point here is to render on the server on initial load)servedCachedData
function is providing atransform
andgetCachedData
configuration, the data is immediately available since it has already been fetched.Other details
/api/markdown
endpoint serves a response of{ content: string }
of raw markdown, and adds a simulated250ms
delay on the endpoint to simulate loading the data from a remote server.page-snippet
component passes a query param to the same/api/markdown
endpoint so that it simply returns a single list item.removeInvalidSnippets
utility that processes the markdownAST
nodes and removes any nested children of the sametag
, as well as a regular expression that removes thename
prop from thePageSnippet
component inline in the raw markdown. (again, none of this impacts the reproduction as-is)Running the project
chore/snippets
branch.pnpm install
pnpm dev
The text was updated successfully, but these errors were encountered: