import React from 'react'; import ReactDOM from 'react-dom/server'; import StaticHtml from './static-html.js'; const reactTypeof = Symbol.for('react.element'); function errorIsComingFromPreactComponent(err) { return ( err.message && (err.message.startsWith("Cannot read property '__H'") || err.message.includes("(reading '__H')")) ); } async function check(Component, props, children) { // Note: there are packages that do some unholy things to create "components". // Checking the $$typeof property catches most of these patterns. if (typeof Component === 'object') { const $$typeof = Component['$$typeof']; return $$typeof && $$typeof.toString().slice('Symbol('.length).startsWith('react'); } if (typeof Component !== 'function') return false; if (Component.prototype != null && typeof Component.prototype.render === 'function') { return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component); } let error = null; let isReactComponent = false; function Tester(...args) { try { const vnode = Component(...args); if (vnode && vnode['$$typeof'] === reactTypeof) { isReactComponent = true; } } catch (err) { if (!errorIsComingFromPreactComponent(err)) { error = err; } } return React.createElement('div'); } await renderToStaticMarkup(Tester, props, children, {}); if (error) { throw error; } return isReactComponent; } async function getNodeWritable() { let nodeStreamBuiltinModuleName = 'stream'; let { Writable } = await import(nodeStreamBuiltinModuleName); return Writable; } async function renderToStaticMarkup(Component, props, children, metadata) { delete props['class']; const vnode = React.createElement(Component, { ...props, children: children != null ? React.createElement(StaticHtml, { value: children }) : undefined, }); let html; if (metadata && metadata.hydrate) { html = ReactDOM.renderToString(vnode); if('renderToReadableStream' in ReactDOM) { html = await renderToReadableStreamAsync(vnode); } else { html = await renderToPipeableStreamAsync(vnode); } } else { if('renderToReadableStream' in ReactDOM) { html = await renderToReadableStreamAsync(vnode); } else { html = await renderToStaticNodeStreamAsync(vnode); } } return { html }; } async function renderToPipeableStreamAsync(vnode) { const Writable = await getNodeWritable(); let html = ''; return new Promise((resolve, reject) => { let error = undefined; let stream = ReactDOM.renderToPipeableStream(vnode, { onError(err) { error = err; reject(error); }, onAllReady() { stream.pipe(new Writable({ write(chunk, _encoding, callback) { html += chunk.toString('utf-8'); callback(); }, destroy() { resolve(html); } })); } }); }); } async function renderToStaticNodeStreamAsync(vnode) { const Writable = await getNodeWritable(); let html = ''; return new Promise((resolve) => { let stream = ReactDOM.renderToStaticNodeStream(vnode); stream.pipe(new Writable({ write(chunk, _encoding, callback) { html += chunk.toString('utf-8'); callback(); }, destroy() { resolve(html); } })); }); } async function renderToReadableStreamAsync(vnode) { const decoder = new TextDecoder(); const stream = await ReactDOM.renderToReadableStream(vnode); let html = ''; for await(const chunk of stream) { html += decoder.decode(chunk); } return html; } export default { check, renderToStaticMarkup, };