dgp1130 / rules_prerender

A Bazel rule set for prerending HTML pages.
14 stars 0 forks source link

Out of Order Streaming #78

Open dgp1130 opened 9 months ago

dgp1130 commented 9 months ago

I recently discovered that it is possible to stream HTML out of order (without hacky client-side JS) using declarative shadow DOM: https://techhub.social/@develwithoutacause/111723544374367628

I made a demo which confirms that this is possible and can be automated by transforming input HTML and Promise values into a DSD structure with Promise results deferred to the end of the content, where they can be streamed in any order.

https://github.com/dgp1130/out-of-order-streaming/

I think this is super cool and would be a powerful optimization technique, especially considering that it can be done mostly automatically, if developers author the right structure. Since @rules_prerender uses shadow DOM heavily and implements its own <Template /> Preact component, I think it would potentially be feasible to make this work automatically.

The prototype I did used tagged template literals, so I'm not sure exactly how this would work with VDom. I suspect we do something like:

function MyComponent(): VNode {
  return <div>
    <Template shadowrootmode="open">
      <div>Hello, {lazy(world())}!</div>
    </Template>
  </div>;
}

async function world(): Promise<string> {
  await new Promise<void>((resolve) => {
    setTimeout(() => {
      resolve();
    }, 1_000);
  });

  return 'World';
}

lazy() would just make any suspense stuff easier to work with if possible.

I don't think there's a way to easily correlate the <Template shadowrootmode="open" /> component to the world() Promise, but maybe that's fine. I think we could possibly transform the final VDom for the page (or write a custom renderer) which looks for all lazy values, finds the nearest shadow root ancestor, and transforms them to push the Promise HTML to the end of the document. Definitely need to experiment more to see how this can work.

Needless to say this only really makes sense for true SSR. In SSG, there isn't much value to this kind of streaming. We'll want to explore this once we have a real SSR story.

It might also be worth seeing if it's possible to do all these transformations at compile time. If we can do this statically before request time, then basically the entire document can be streamed immediately without invoking user code at all. Only the slots at the end of the document would need to be filled in via SSR. Not sure how feasible that is, especially if SSR'd HTML would want to take advantage of this trick as well, but it's worth looking into at least.