diff --git a/demo/plugins/ssr/ssr-environment.js b/demo/plugins/ssr/ssr-environment.js index 10c349c4e..023dbec93 100644 --- a/demo/plugins/ssr/ssr-environment.js +++ b/demo/plugins/ssr/ssr-environment.js @@ -103,10 +103,10 @@ function finish({ url, res }, result) { } res.setHeader('Link', scripts.map(script => `<${script.url}>;rel=preload;as=script;crossorigin`).join(', ')); - for (const script of scripts) { - // head += ``; - body += ``; - } + // for (const script of scripts) { + // // head += ``; + // body += ``; + // } if (/<\/head>/i.test(result)) result = result.replace(/(<\/head>)/i, head + '$1'); else result = head + result; diff --git a/demo/public/index.tsx b/demo/public/app.tsx similarity index 100% rename from demo/public/index.tsx rename to demo/public/app.tsx diff --git a/demo/public/document.tsx b/demo/public/document.tsx new file mode 100644 index 000000000..3431e1d08 --- /dev/null +++ b/demo/public/document.tsx @@ -0,0 +1,58 @@ +import { App } from './app'; +import { Hydratable } from './hydrateable'; +import { HydrationContextProvider, useHydrationRegistrations } from './with-hydration'; + +function importAlias(componentId: string): string { + return `Component_${componentId}`; +} + +function HydrationScripts() { + const components = useHydrationRegistrations(); + + if (!components || components.length === 0) { + return null; + } + + return ( + - - - - - - - - - diff --git a/demo/public/pages/about/index.js b/demo/public/pages/about/index.js index d940e00a1..284a9239e 100644 --- a/demo/public/pages/about/index.js +++ b/demo/public/pages/about/index.js @@ -1,3 +1,4 @@ +import { Hydratable } from '../../hydrateable2'; import styles from './style.module.css'; const About = ({ query }) => ( @@ -5,6 +6,7 @@ const About = ({ query }) => (

About

My name is Jason.

{JSON.stringify(query)}
+ ); diff --git a/demo/public/ssr.js b/demo/public/ssr.js index 12e0044c1..23ddb5436 100644 --- a/demo/public/ssr.js +++ b/demo/public/ssr.js @@ -1,31 +1,9 @@ -import { promises as fs } from 'fs'; import renderToString from 'preact-render-to-string'; +import prepass from 'preact-ssr-prepass'; -async function prepass(vnode, maxDepth = 20, maxTime = 5000) { - let attempts = 0; - const start = Date.now(); - while (++attempts < maxDepth && Date.now() - start < maxTime) { - try { - return renderToString(vnode); - } catch (e) { - if (e && e.then) { - await e; - continue; - } - throw e; - } - } -} - -export async function ssr({ url }) { - const { App } = await import('./index.tsx'); - let body = await prepass(, 20, 5000); - const html = await fs.readFile('./public/index.html', 'utf-8'); - // body = html.replace(/(
)<\/div>/, '$1' + body + '
'); - if (/]*?)?>/.test(html)) { - body = html.replace(/(]*?)?)>/, '$1 ssr>' + body); - } else { - body = html + body; - } - return body; +export async function ssr(req) { + const { Document } = await import('./document.tsx'); + const vnode = ; + await prepass(vnode); + return renderToString(vnode, {}, { pretty: true }); } diff --git a/demo/public/with-hydration.tsx b/demo/public/with-hydration.tsx new file mode 100644 index 000000000..7252032a9 --- /dev/null +++ b/demo/public/with-hydration.tsx @@ -0,0 +1,82 @@ +import { h, createContext } from 'preact'; +import { useContext, useMemo } from 'preact/hooks'; + +let id = 0; +function uuid() { + return String(id++); +} + +interface ComponentRegistration { + script: string; + specifier: string; + componentId: string; +} + +let hydrationComponentIdCounter = 0; + +interface HydrationContext { + registerComponent(component: ComponentRegistration): void; + getComponents(): ComponentRegistration[]; +} + +export function HydrationContextProvider({ req, children }) { + const hydrationContextValue = useMemo(() => { + const components: ComponentRegistration[] = []; + + return { + registerComponent(comp: ComponentRegistration) { + if (components.find(c => c.componentId === comp.componentId)) { + return; + } + + components.push(comp); + }, + getComponents() { + return components; + } + }; + }, [req]); + + return {children}; +} + +const hydrationContext = createContext(null); + +export function useHydrationRegistrations() { + return useContext(hydrationContext)?.getComponents(); +} + +export function withHydration({ specifier, importUrl }: { specifier: string; importUrl: string }) { + const componentId = String(hydrationComponentIdCounter++); + + return function (Component) { + return function HydrateableComponent(props) { + if (typeof document !== 'undefined') { + return ; + } + + const ctx = useContext(hydrationContext); + + ctx?.registerComponent?.({ + script: importUrl.replace('http://0.0.0.0:8080', ''), + specifier, + componentId + }); + + const instanceId = uuid(); + + return ( + <> +