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

POC: feat: enable to write jsx to streaming api #1913

Closed
wants to merge 3 commits into from

Conversation

sor4chi
Copy link
Contributor

@sor4chi sor4chi commented Jan 7, 2024

Author should do the followings, if applicable

  • Add tests
  • Run tests
  • yarn denoify to generate files for Deno

If JSX can be delivered via streamingAPI, as in the tweet below, it would be a nice match with the shadowrootmode of web components and allow components to be changed dynamically from the server without client JS.

https://x.com/shou_study/status/1743996737696764069?s=20

Demo:

Code

import { Hono } from "hono";
import { stream } from "hono/streaming";

const app = new Hono();

app.get("/", (c) => {
  return stream(c, async (stream) => {
    await stream.write(
      <html lang="en">
        <body>
          <template shadowrootmode="open">
            <slot name="component1">
              <p>Loading...</p>
            </slot>
          </template>
        </body>
      </html>
    );
    await stream.sleep(1000);
    await stream.write(
      <div slot="component1">
        <h1>Hello Hono!</h1>
      </div>
    );
  });
});

export default app;

2024-01-08.3.22.52.mov

@sor4chi sor4chi marked this pull request as ready for review January 7, 2024 18:34
@sor4chi sor4chi changed the title feat: enable to write jsx to streaming api POC: feat: enable to write jsx to streaming api Jan 7, 2024
@sor4chi sor4chi force-pushed the feat/stream-can-send-jsx branch 2 times, most recently from ddba75d to d5a8136 Compare January 8, 2024 07:07
@sor4chi sor4chi force-pushed the feat/stream-can-send-jsx branch from d5a8136 to 3ef835b Compare January 8, 2024 08:08
@yusukebe
Copy link
Member

yusukebe commented Jan 9, 2024

Thanks @sor4chi

I think there are some features that don't work, such as Suspense, although basically, it's not bad.

@usualoma What do you think this feature?

@usualoma
Copy link
Member

usualoma commented Jan 9, 2024

@sor4chi @yusukebe
I think … since a readableStream can be passed to pipe(), it is a good idea to pass the result of renderToReadableStream() to pipe() if you want to export JSX with the streaming API.
Therefore, while I think it is an interesting experiment, I do not think this PR change is necessary.

import { jsx } from '../jsx'
import { renderToReadableStream } from '../jsx/streaming'
import { StreamingApi } from './stream'

describe('StreamingApi', () => {
  it('pipe(no promised hono/jsx)', async () => {
    const Component1 = () => <span>foo</span>
    const Component2 = () => <span>bar</span>
    const { readable, writable } = new TransformStream()
    const api = new StreamingApi(writable, readable)
    const reader = api.responseReadable.getReader()
    await api.pipe(renderToReadableStream(<Component1 />))
    expect((await reader.read()).value).toEqual(new TextEncoder().encode('<span>foo</span>'))
    await api.pipe(renderToReadableStream(<Component2 />))
    expect((await reader.read()).value).toEqual(new TextEncoder().encode('<span>bar</span>'))
  })

  it('pipe(promised hono/jsx)', async () => {
    const Component1 = async () => {
      await new Promise((resolve) => setTimeout(resolve, 100))
      return <span>foo</span>
    }
    const Component2 = async () => {
      await new Promise((resolve) => setTimeout(resolve, 100))
      return <span>bar</span>
    }
    const { readable, writable } = new TransformStream()
    const api = new StreamingApi(writable, readable)
    const reader = api.responseReadable.getReader()
    await api.pipe(renderToReadableStream(<Component1 />))
    expect((await reader.read()).value).toEqual(new TextEncoder().encode('<span>foo</span>'))
    await api.pipe(renderToReadableStream(<Component2 />))
    expect((await reader.read()).value).toEqual(new TextEncoder().encode('<span>bar</span>'))
  })
})

@yusukebe
Copy link
Member

yusukebe commented Jan 9, 2024

@usualoma

Like this?

const app = new Hono()

const HTML = () => (
  <html lang='en'>
    <body>
      <template shadowrootmode='open'>
        <slot name='component1'>
          <p>Loading...</p>
        </slot>
      </template>
    </body>
  </html>
)

const Component = () => (
  <div slot='component1'>
    <h1>Hello Hono!</h1>
  </div>
)

app.get('/', (c) => {
  return stream(c, async (stream) => {
    await stream.pipe(renderToReadableStream(<HTML />))
    await stream.sleep(1000)
    await stream.pipe(renderToReadableStream(<Component />))
  })
})

@usualoma
Copy link
Member

usualoma commented Jan 9, 2024

@yusukebe
Yes, I have not actually tried running it, but I think that is fine.

@yusukebe
Copy link
Member

yusukebe commented Jan 9, 2024

@usualoma

Thanks! This code is working as @sor4chi expects.

@sor4chi
Copy link
Contributor Author

sor4chi commented Jan 9, 2024

Hi, @yusukebe, @usualoma
Thank you for reviewing.
Surely, we can convert it to renderToReadableStream and PIPE it, we can handle it without extending it. I didn’t notice it.

@sor4chi sor4chi closed this Jan 9, 2024
@yusukebe
Copy link
Member

yusukebe commented Jan 9, 2024

@sor4chi

How about writing this thing as a "snippet" on our website?

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