import { JSX } from "solid-js"; import { renderToStream, renderToString, renderToStringAsync } from "solid-js/web"; import { internalFetch } from "../api/internalFetch"; import { FetchEvent, FETCH_EVENT, PageEvent } from "../server/types"; export function renderSync( fn: (context: PageEvent) => JSX.Element, options?: { nonce?: string; renderId?: string; } ) { return () => (event: FetchEvent) => { let pageEvent = createPageEvent(event); let markup = renderToString(() => fn(pageEvent), options); if (pageEvent.routerContext.url) { return Response.redirect(new URL(pageEvent.routerContext.url, pageEvent.request.url), 302); } if (import.meta.env.START_ISLANDS_ROUTER && pageEvent.routerContext.replaceOutletId) { markup = `${pageEvent.routerContext.replaceOutletId}:${ pageEvent.routerContext.newOutletId }=${markup.slice( markup.indexOf(``) + ``.length + ``.length, markup.lastIndexOf(``) - ``.length )}`; pageEvent.responseHeaders.set("Content-Type", "text/plain"); } return new Response(markup, { status: pageEvent.getStatusCode(), headers: pageEvent.responseHeaders }); } } export function renderAsync( fn: (context: PageEvent) => JSX.Element, options?: { timeoutMs?: number; nonce?: string; renderId?: string; } ) { return () => async (event: FetchEvent) => { let pageEvent = createPageEvent(event); let markup = await renderToStringAsync(() => fn(pageEvent), options); if (pageEvent.routerContext.url) { return Response.redirect(new URL(pageEvent.routerContext.url, pageEvent.request.url), 302); } if (import.meta.env.START_ISLANDS_ROUTER && pageEvent.routerContext.replaceOutletId) { markup = `${pageEvent.routerContext.replaceOutletId}:${ pageEvent.routerContext.newOutletId }=${markup.slice( markup.indexOf(``) + ``.length + ``.length, markup.lastIndexOf(``) - ``.length )}`; pageEvent.responseHeaders.set("Content-Type", "text/plain"); } return new Response(markup, { status: pageEvent.getStatusCode(), headers: pageEvent.responseHeaders }); }; } function handleRedirect(context) { return ({ write }) => { if (context.routerContext.url) write(``); }; } function createPageEvent(event: FetchEvent) { let responseHeaders = new Headers({ "Content-Type": "text/html" }); const prevPath = event.request.headers.get("x-solid-referrer"); let statusCode = 200; function setStatusCode(code: number) { statusCode = code; } function getStatusCode() { return statusCode; } const pageEvent: PageEvent = Object.freeze({ request: event.request, prevUrl: prevPath, routerContext: {}, tags: [], env: event.env, $type: FETCH_EVENT, responseHeaders, setStatusCode: setStatusCode, getStatusCode: getStatusCode, fetch: internalFetch }); return pageEvent; } export function renderStream( fn: (context: PageEvent) => JSX.Element, baseOptions: { nonce?: string; renderId?: string; onCompleteShell?: (info: { write: (v: string) => void }) => void; onCompleteAll?: (info: { write: (v: string) => void }) => void; } = {} ) { return () => (event: FetchEvent) => { let pageEvent = createPageEvent(event); const options = { ...baseOptions }; if (options.onCompleteAll) { const og = options.onCompleteAll; options.onCompleteAll = options => { handleRedirect(pageEvent)(options); og(options); }; } else options.onCompleteAll = handleRedirect(pageEvent); const { readable, writable } = new TransformStream(); const stream = renderToStream(() => fn(pageEvent), options); if (pageEvent.routerContext.url) { return Response.redirect(new URL(pageEvent.routerContext.url, pageEvent.request.url), 302); } if (pageEvent.routerContext.replaceOutletId) { const writer = writable.getWriter(); const encoder = new TextEncoder(); writer.write( encoder.encode( `${pageEvent.routerContext.replaceOutletId}:${pageEvent.routerContext.newOutletId}=` ) ); writer.releaseLock(); pageEvent.responseHeaders.set("Content-Type", "text/plain"); } stream.pipeTo(writable); return new Response(readable, { status: pageEvent.getStatusCode(), headers: pageEvent.responseHeaders }); }; }