react-on-rails
Version:
react-on-rails JavaScript for react_on_rails Ruby gem
115 lines (114 loc) • 5.31 kB
JavaScript
;
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;