UNPKG

react-on-rails

Version:

react-on-rails JavaScript for react_on_rails Ruby gem

115 lines (114 loc) 5.31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ReactDOMServer = require("react-dom/server"); const stream_1 = require("stream"); const ComponentRegistry_1 = require("./ComponentRegistry"); const createReactOutput_1 = require("./createReactOutput"); const isServerRenderResult_1 = require("./isServerRenderResult"); const buildConsoleReplay_1 = require("./buildConsoleReplay"); const handleError_1 = require("./handleError"); const serverRenderUtils_1 = require("./serverRenderUtils"); const stringToStream = (str) => { const stream = new stream_1.PassThrough(); stream.write(str); stream.end(); return stream; }; const transformRenderStreamChunksToResultObject = (renderState) => { const consoleHistory = console.history; let previouslyReplayedConsoleMessages = 0; const transformStream = new stream_1.PassThrough({ transform(chunk, _, callback) { const htmlChunk = chunk.toString(); const consoleReplayScript = (0, buildConsoleReplay_1.default)(consoleHistory, previouslyReplayedConsoleMessages); previouslyReplayedConsoleMessages = consoleHistory?.length || 0; const jsonChunk = JSON.stringify((0, serverRenderUtils_1.createResultObject)(htmlChunk, consoleReplayScript, renderState)); this.push(`${jsonChunk}\n`); callback(); } }); let pipedStream = null; const pipeToTransform = (pipeableStream) => { pipeableStream.pipe(transformStream); pipedStream = pipeableStream; }; // We need to wrap the transformStream in a Readable stream to properly handle errors: // 1. If we returned transformStream directly, we couldn't emit errors into it externally // 2. If an error is emitted into the transformStream, it would cause the render to fail // 3. By wrapping in Readable.from(), we can explicitly emit errors into the readableStream without affecting the transformStream // Note: Readable.from can merge multiple chunks into a single chunk, so we need to ensure that we can separate them later const readableStream = stream_1.Readable.from(transformStream); const writeChunk = (chunk) => transformStream.write(chunk); const emitError = (error) => readableStream.emit('error', error); const endStream = () => { transformStream.end(); pipedStream?.abort(); }; return { readableStream, pipeToTransform, writeChunk, emitError, endStream }; }; const streamRenderReactComponent = (reactRenderingResult, options) => { const { name: componentName, throwJsErrors } = options; const renderState = { result: null, hasErrors: false, isShellReady: false }; const { readableStream, pipeToTransform, writeChunk, emitError, endStream } = transformRenderStreamChunksToResultObject(renderState); const renderingStream = ReactDOMServer.renderToPipeableStream(reactRenderingResult, { onShellError(e) { const error = (0, serverRenderUtils_1.convertToError)(e); renderState.hasErrors = true; renderState.error = error; if (throwJsErrors) { emitError(error); } const errorHtml = (0, handleError_1.default)({ e: error, name: componentName, serverSide: true }); writeChunk(errorHtml); endStream(); }, onShellReady() { renderState.isShellReady = true; pipeToTransform(renderingStream); }, onError(e) { if (!renderState.isShellReady) { return; } const error = (0, serverRenderUtils_1.convertToError)(e); if (throwJsErrors) { emitError(error); } renderState.hasErrors = true; renderState.error = error; }, }); return readableStream; }; const streamServerRenderedReactComponent = (options) => { const { name: componentName, domNodeId, trace, props, railsContext, throwJsErrors } = options; try { const componentObj = ComponentRegistry_1.default.get(componentName); (0, serverRenderUtils_1.validateComponent)(componentObj, componentName); const reactRenderingResult = (0, createReactOutput_1.default)({ componentObj, domNodeId, trace, props, railsContext, }); if ((0, isServerRenderResult_1.isServerRenderHash)(reactRenderingResult) || (0, isServerRenderResult_1.isPromise)(reactRenderingResult)) { throw new Error('Server rendering of streams is not supported for server render hashes or promises.'); } return streamRenderReactComponent(reactRenderingResult, options); } catch (e) { if (throwJsErrors) { throw e; } const error = (0, serverRenderUtils_1.convertToError)(e); const htmlResult = (0, handleError_1.default)({ e: error, name: componentName, serverSide: true }); const jsonResult = JSON.stringify((0, serverRenderUtils_1.createResultObject)(htmlResult, (0, buildConsoleReplay_1.default)(), { hasErrors: true, error, result: null })); return stringToStream(jsonResult); } }; exports.default = streamServerRenderedReactComponent;