Skip to content

Conversation

@adamdehaven
Copy link
Contributor

@adamdehaven adamdehaven commented Oct 18, 2024

πŸ”— Linked issue

#263

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

Note

The playground from this PR is available on StackBlitz

With the changes in this PR, the MDCRenderer will also support rendering nested async components by wrapping any child components in defineAsyncComponent which will allow its tree to resolve their internal top-level async setup() before allowing the parent MDCRenderer to resolve.

This behavior allows for introducing MDC block components that themselves can perform async actions, such as fetching their own data before allowing the parent component to resolve.

In order for the parent MDCRenderer component to properly wait for the child async component(s) to resolve:

  1. All functionality in the child component must be executed within an async setup function with top-level await (if no async/await behavior is needed in the child, e.g. no data fetching, then the component will resolve normally).

  2. The child component's template content should be wrapped with the built-in Suspense component with the suspensible prop set to true.

    <template>
      <Suspense suspensible>
        <pre>{{ data }}</pre>
      </Suspense>
    </template>
    
    <script setup>
    const { data } = await useAsyncData(..., {
      immediate: true, // This is the default, but is required for this functionality
    })
    </script>

    In a Nuxt application, this means that setting immediate: false on any useAsyncData or useFetch calls would prevent the parent MDCRenderer from waiting and the parent would potentially resolve before the child components have finished rendering, causing hydration errors or missing content.

Example: MDC "snippets"

To demonstrate how powerful these nested async block components can be, you could allow users to define a subset of markdown documents in your project that will be utilized as reusable "snippets" in a parent document.

You would create a custom block component in your project that handles fetching the snippet markdown content from the API, use parseMarkdown to get the ast nodes, and render it in its own MDC or MDCRenderer component.

See the code in the playground PageSnippet component as an example, and to see the behavior in action, check out the playground by running pnpm dev and navigating to the /async-components route.

Handling recursion

If your project implements a "reusable snippet" type of approach, you will likely want to prevent the use of recursive snippets, whereby a nested MDCRenderer attempts to then load another child somewhere in its component tree with the same content (meaning, importing itself) and your application would be thrown into an infinite loop.

One way to get around this is to utilize Vue's provide/inject to pass down the history of rendered "snippets" so that a child can properly determine if it is being called recursively, and stop the chain. This can be used in combination with parsing the ast document nodes after calling the parseMarkdown function to strip out recursive node trees from the ast before rendering the content in the DOM.

For an example on how to prevent infinite loops and recursion with this pattern, please see the code in the playground's PageSnippet component.

The changes in the MDCRenderer.vue component are actually not needed
to support async rendering. I've updated the PR to only include detailed
documentation and a playground example.
adamdehaven added a commit to adamdehaven/mdc that referenced this pull request Oct 24, 2024
@adamdehaven adamdehaven mentioned this pull request Oct 24, 2024
6 tasks
@adamdehaven
Copy link
Contributor Author

Unless I'm missing something, the functionality described in this PR actually works with no changes to the existing module, provided the "snippets" are implemented as described in the PR linked below (meaning, I think just more documentation is needed).

@farnabaz I'm closing this PR in favor of #270 which provides the playground example and documentation.

With "reusable content" being on the roadmap, I think adding the docs now in the linked PR is ideal. Adding the docs resolves my current need with no changes and opens the door for anyone else wanting to implement something similar.

@adamdehaven adamdehaven deleted the feat/async-rendering branch October 24, 2024 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant