Skip to content
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

Implement async function component rendering #4571

Conversation

andreafuturi
Copy link

@andreafuturi andreafuturi commented Nov 26, 2024

Implement async function component rendering.

  • src/diff/index.js

    • Add async/await support in the diff function to handle async function components.
    • Check if the component returns a Promise and await it in the diff function.
    • Update the commitRoot function to handle async components.
    • Add async/await support in the diffElementNodes function.
    • Add async/await support in the diffChildren function.
  • src/component.js

    • Add async/await support in the renderComponent function to handle async function components.
    • Check if the component returns a Promise and await it in the renderComponent function.
    • Add async/await support in the process function to handle async function components.
  • src/render.js

    • Add async/await support in the render function to handle async function components.
    • Check if the component returns a Promise and await it in the render function.
    • Add async/await support in the hydrate function to handle async function components.
  • compat/client.js

    • Add async/await support in the render function to handle async function components.
    • Check if the component returns a Promise and await it in the render function.
    • Add async/await support in the hydrateRoot function to handle async function components.
  • compat/client.mjs

    • Add async/await support in the render function to handle async function components.
    • Check if the component returns a Promise and await it in the render function.
    • Add async/await support in the hydrateRoot function to handle async function components.
  • test/browser/asyncFunctionComponent.test.js

    • Add tests to ensure async function components render correctly.
    • Add tests to handle async operations properly in async function components.

For more details, open the Copilot Workspace session.

Implement async function component rendering.

* **src/diff/index.js**
  - Add async/await support in the `diff` function to handle async function components.
  - Check if the component returns a Promise and await it in the `diff` function.
  - Update the `commitRoot` function to handle async components.
  - Add async/await support in the `diffElementNodes` function.
  - Add async/await support in the `diffChildren` function.

* **src/component.js**
  - Add async/await support in the `renderComponent` function to handle async function components.
  - Check if the component returns a Promise and await it in the `renderComponent` function.
  - Add async/await support in the `process` function to handle async function components.

* **src/render.js**
  - Add async/await support in the `render` function to handle async function components.
  - Check if the component returns a Promise and await it in the `render` function.
  - Add async/await support in the `hydrate` function to handle async function components.

* **compat/client.js**
  - Add async/await support in the `render` function to handle async function components.
  - Check if the component returns a Promise and await it in the `render` function.
  - Add async/await support in the `hydrateRoot` function to handle async function components.

* **compat/client.mjs**
  - Add async/await support in the `render` function to handle async function components.
  - Check if the component returns a Promise and await it in the `render` function.
  - Add async/await support in the `hydrateRoot` function to handle async function components.

* **test/browser/asyncFunctionComponent.test.js**
  - Add tests to ensure async function components render correctly.
  - Add tests to handle async operations properly in async function components.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/preactjs/preact?shareId=XXXX-XXXX-XXXX-XXXX).
@rschristian
Copy link
Member

Hey, may I ask what prompted this? Is there something in particular you're after or trying to fix?

@andreafuturi
Copy link
Author

Sure! I was exploring a way to have async components functions in Preact to be able to await blocking functions outside of hooks.

I need to implement server side data fetching in a framework I'm building https://github.com/andreafuturi/Singularity

I found some solutions online but everything is outdated or not working so I decided to see if I can do something myself with the help of AI.

@rschristian
Copy link
Member

Well, a truly async renderer would involve considerably more work than sprinkling async/await in a few places, not to mention, just adding await is a considerable breaking change that has a massive impact on the bundle size. There's pretty much no way we could merge this into Preact X.

Is there a reason why suspense doesn't work for your needs? Suspense, plus renderToStringAsync() from RTS should fit that use case pretty nicely.

@andreafuturi
Copy link
Author

Yes I could have imagined it would need quite some work, but for sure I wasn't considering it could even influences bundle size.

Yes I tried Suspense before with no luck, I might have to try it again. What's this RTS you're talking about?

I can seem to find renderToStringAsync only for React

@rschristian
Copy link
Member

rschristian commented Nov 26, 2024

RTS = render to string; preact-render-to-string, specifically. Docs for rtsAsync.

@andreafuturi
Copy link
Author

Wow thank you! That works now, I have no idea why I have already seen Suspense and lazy documentation but I never noticed there was a renderToStringAsync export in preact-render-to-string. I always used the normal rendertoString function and that for sure was the problem! Thanks again :)

@JoviDeCroock
Copy link
Member

@andreafuturi do take the pitfalls lined out in #4442 in account when you do suspended hydration

@rschristian
Copy link
Member

rtsAsync is fairly new tbf! But you can utilize it for data fetching and the like beyond just lazy for sure.

I realize our docs are quite lacking here so I'm working on getting some proper SSR demos built next, hopefully it'll help people in the future. Sorry for the inconvenience, we're trying to get better at docs/showing off all the things you can take advantage of.

@andreafuturi
Copy link
Author

Aaah ok I see, no worries at all! I completely understand, thanks for the amazing work 🙏🏻 Keep it up!

@andreafuturi
Copy link
Author

andreafuturi commented Dec 10, 2024

Hi guys does the new signal apis work there?
I tried a very basic example in this codepen but it's giving this error: Unhandled Promise Rejection: TypeError: undefined is not an object (evaluating 'o.__H')

@rschristian @JoviDeCroock

@JoviDeCroock
Copy link
Member

This is a very common exception if you search for it in our issues and is mainly caused by duplicate versions of preact in your bundle

@rschristian
Copy link
Member

rschristian commented Dec 10, 2024

We'd recommend using import maps to solve this for no-build situations: https://preactjs.com/guide/v10/no-build-workflows#preact-with-hooks-signals-and-htm

<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Preact SSR Test</title>
        <script type="importmap">
            {
              "imports": {
                "preact": "https://esm.sh/preact@10.23.1",
                "preact/": "https://esm.sh/preact@10.23.1/",
                "@preact/signals": "https://esm.sh/@preact/signals@1.3.0?external=preact",
                "preact-render-to-string": "https://esm.sh/preact-render-to-string?external=preact"
              }
            }
        </script>
    </head>
    <body>
        <noscript>Enable JavaScript to run this example.</noscript>
    </body>
</html>
    import { h } from 'preact';
    import { renderToString } from 'preact-render-to-string';
    import { useSignal } from '@preact/signals';

  
    // Define a simple component using `useSignal`
    function Counter() {
      const count = useSignal(20); // Signal created with useSignal
      return h('div', null, `Count: ${count.value}`);
    }

    // Render the component to string asynchronously
    (async () => {
      // Wrap the component in a fragment or root node
      const App = () => h(Counter);

      const html = renderToString(h(App));
      console.log(html); // Outputs: <div>Count: 20</div>

      // Display the rendered HTML in the browser
      document.body.innerHTML = `<pre>${html}</pre>`;
    })();

Just a note while you're here: renderToString there is sync. import { renderToStringAsync } from 'preact-render-to-string' is what you want for suspense/async.

@andreafuturi
Copy link
Author

about the async version of the method, yes I probably changed that while verifying what the problem was exactly and accidentally updated the codepen.

For the imports, I already had an importMap in Deno which looked like this:

   "preact": "https://esm.sh/preact@10.25.1",
    "preact/compat": "https://esm.sh/preact@10.25.1/compat",
    "preact/hooks": "https://esm.sh/preact@10.25.1/hooks",
     "preact-render-to-string": "https://esm.sh/preact-render-to-string@6.5.11",

But I managed to fix the error by aliasing them to npm versions

    "preact": "npm:preact",
    "preact/compat": "npm:preact/compat",
    "preact/hooks": "npm:preact/hooks",
     "preact-render-to-string": "npm:preact-render-to-string@6.5.11",

thank you very much for the hint! :)

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.

3 participants