Skip to content

Conversation

@adamdehaven
Copy link
Contributor

@adamdehaven adamdehaven commented Oct 24, 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

The MDCRenderer supports rendering nested async components and can allow its child component tree to resolve their internal top-level async setup() before allowing the parent MDCRenderer to resolve. The implementation, while powerful, isn't immediately clear in the current documentation.

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.

@farnabaz
Copy link
Collaborator

farnabaz commented Nov 6, 2024

Thanks for the PR
The change for MDCRenderer is missing in the final state of the PR.
Also, Could we simplify the examples and playground to a simple component with timeout?

We can have a simple delayed component. Maybe using a simple timeout would be enough to describe async rendering

await new Promise(resolve => setTimeout(resolve, 2000))

The current example is complicated to understand

Allow resolving nested components via defineAsyncComponent.
@adamdehaven adamdehaven changed the title docs: async nested rendering feat: async nested rendering Nov 6, 2024
@adamdehaven
Copy link
Contributor Author

Thanks for the PR The change for MDCRenderer is missing in the final state of the PR.

Added back the defineAsyncComponent change into MDCRenderer.

Also, Could we simplify the examples and playground to a simple component with timeout?

We can have a simple delayed component. Maybe using a simple timeout would be enough to describe async rendering

await new Promise(resolve => setTimeout(resolve, 2000))

The current example is complicated to understand

I can change the example if you prefer; however, note that a simplified example wouldn’t demonstrate how to prevent recursion (an infinite loop whereby a block component imports itself).

This seems like a common pattern that may prevent questions down the line, but I’m happy to simplify the example if you don’t think it’s needed?

@adamdehaven
Copy link
Contributor Author

@farnabaz I've updated the playground to include a Simple Async Example (the default) and an Advanced Async Example (to demonstrate how to bypass infinite loops due to recursion).

image

Let me know if you'd prefer any other changes

@farnabaz
Copy link
Collaborator

@adamdehaven I've pushed an update for the resolve component helper. Could you confirm its behavior in your project?

@adamdehaven
Copy link
Contributor Author

@farnabaz yep it looks like everything still works (I tested in the playground pages I added in this PR).

It looks like the links for the pages I added in the playground header got removed (but the playground itself is still functional at /async-components). I can add the links back if you'd like (or fine to exclude them if you just want the links to be hidden)

Copy link
Collaborator

@farnabaz farnabaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@farnabaz farnabaz merged commit 86e11ab into nuxt-content:main Nov 19, 2024
2 checks passed
@adamdehaven adamdehaven deleted the docs/async-rendering branch November 20, 2024 14:48
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.

2 participants