"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { cloneTransformStream: null, chainStreams: null, streamFromString: null, streamToString: null, createBufferedTransformStream: null, renderToInitialFizzStream: null, createRootLayoutValidatorStream: null, continueFizzStream: null, continuePostponedFizzStream: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { cloneTransformStream: function() { return cloneTransformStream; }, chainStreams: function() { return chainStreams; }, streamFromString: function() { return streamFromString; }, streamToString: function() { return streamToString; }, createBufferedTransformStream: function() { return createBufferedTransformStream; }, renderToInitialFizzStream: function() { return renderToInitialFizzStream; }, createRootLayoutValidatorStream: function() { return createRootLayoutValidatorStream; }, continueFizzStream: function() { return continueFizzStream; }, continuePostponedFizzStream: function() { return continuePostponedFizzStream; } }); const _tracer = require("../lib/trace/tracer"); const _constants = require("../lib/trace/constants"); const _encodedecode = require("./encode-decode"); const _detachedpromise = require("../../lib/detached-promise"); const _scheduler = require("../../lib/scheduler"); function cloneTransformStream(source) { const sourceReader = source.readable.getReader(); const clone = new TransformStream({ async start (controller) { while(true){ const { done, value } = await sourceReader.read(); if (done) { break; } controller.enqueue(value); } }, // skip all piped chunks transform () {} }); return clone; } function chainStreams(...streams) { const { readable, writable } = new TransformStream(); let promise = Promise.resolve(); for(let i = 0; i < streams.length; ++i){ promise = promise.then(()=>streams[i].pipeTo(writable, { preventClose: i + 1 < streams.length })); } // Catch any errors from the streams and ignore them, they will be handled // by whatever is consuming the readable stream. promise.catch(()=>{}); return readable; } function streamFromString(str) { const encoder = new TextEncoder(); return new ReadableStream({ start (controller) { controller.enqueue(encoder.encode(str)); controller.close(); } }); } async function streamToString(stream) { let buffer = ""; await stream// Decode the streamed chunks to turn them into strings. .pipeThrough((0, _encodedecode.createDecodeTransformStream)()).pipeTo(new WritableStream({ write (chunk) { buffer += chunk; } })); return buffer; } function createBufferedTransformStream() { let buffer = new Uint8Array(); let pending; const flush = (controller)=>{ // If we already have a pending flush, then return early. if (pending) return; const detached = new _detachedpromise.DetachedPromise(); pending = detached; (0, _scheduler.scheduleImmediate)(()=>{ try { controller.enqueue(buffer); buffer = new Uint8Array(); } catch { // If an error occurs while enqueuing it can't be due to this // transformers fault. It's likely due to the controller being // errored due to the stream being cancelled. } finally{ pending = undefined; detached.resolve(); } }); }; return new TransformStream({ transform (chunk, controller) { // Combine the previous buffer with the new chunk. const combined = new Uint8Array(buffer.length + chunk.byteLength); combined.set(buffer); combined.set(chunk, buffer.length); buffer = combined; // Flush the buffer to the controller. flush(controller); }, flush () { if (!pending) return; return pending.promise; } }); } function createInsertedHTMLStream(getServerInsertedHTML) { const encoder = new TextEncoder(); return new TransformStream({ transform: async (chunk, controller)=>{ const html = await getServerInsertedHTML(); if (html) { controller.enqueue(encoder.encode(html)); } controller.enqueue(chunk); } }); } function renderToInitialFizzStream({ ReactDOMServer, element, streamOptions }) { return (0, _tracer.getTracer)().trace(_constants.AppRenderSpan.renderToReadableStream, async ()=>ReactDOMServer.renderToReadableStream(element, streamOptions)); } function createHeadInsertionTransformStream(insert) { let inserted = false; let freezing = false; const encoder = new TextEncoder(); const decoder = new TextDecoder(); return new TransformStream({ async transform (chunk, controller) { // While react is flushing chunks, we don't apply insertions if (freezing) { controller.enqueue(chunk); return; } const insertion = await insert(); if (inserted) { controller.enqueue(encoder.encode(insertion)); controller.enqueue(chunk); freezing = true; } else { const content = decoder.decode(chunk); const index = content.indexOf(""); if (index !== -1) { const insertedHeadContent = content.slice(0, index) + insertion + content.slice(index); controller.enqueue(encoder.encode(insertedHeadContent)); freezing = true; inserted = true; } } if (!inserted) { controller.enqueue(chunk); } else { (0, _scheduler.scheduleImmediate)(()=>{ freezing = false; }); } }, async flush (controller) { // Check before closing if there's anything remaining to insert. const insertion = await insert(); if (insertion) { controller.enqueue(encoder.encode(insertion)); } } }); } // Suffix after main body content - scripts before , // but wait for the major chunks to be enqueued. function createDeferredSuffixStream(suffix) { let flushed = false; let pending; const encoder = new TextEncoder(); const flush = (controller)=>{ const detached = new _detachedpromise.DetachedPromise(); pending = detached; (0, _scheduler.scheduleImmediate)(()=>{ try { controller.enqueue(encoder.encode(suffix)); } catch { // If an error occurs while enqueuing it can't be due to this // transformers fault. It's likely due to the controller being // errored due to the stream being cancelled. } finally{ pending = undefined; detached.resolve(); } }); }; return new TransformStream({ transform (chunk, controller) { controller.enqueue(chunk); // If we've already flushed, we're done. if (flushed) return; // Schedule the flush to happen. flushed = true; flush(controller); }, flush (controller) { if (pending) return pending.promise; if (flushed) return; // Flush now. controller.enqueue(encoder.encode(suffix)); } }); } // Merge two streams into one. Ensure the final transform stream is closed // when both are finished. function createMergedTransformStream(stream) { let started = false; let pending = null; const start = (controller)=>{ const reader = stream.getReader(); // NOTE: streaming flush // We are buffering here for the inlined data stream because the // "shell" stream might be chunkenized again by the underlying stream // implementation, e.g. with a specific high-water mark. To ensure it's // the safe timing to pipe the data stream, this extra tick is // necessary. const detached = new _detachedpromise.DetachedPromise(); pending = detached; // We use `setTimeout/setImmediate` here to ensure that it's inserted after // flushing the shell. Note that this implementation might get stale if impl // details of Fizz change in the future. (0, _scheduler.scheduleImmediate)(async ()=>{ try { while(true){ const { done, value } = await reader.read(); if (done) return; controller.enqueue(value); } } catch (err) { controller.error(err); } finally{ detached.resolve(); } }); }; return new TransformStream({ transform (chunk, controller) { controller.enqueue(chunk); // Start the streaming if it hasn't already been started yet. if (started) return; started = true; start(controller); }, flush () { // If the data stream promise is defined, then return it as its completion // will be the completion of the stream. if (!pending) return; if (!started) return; return pending.promise; } }); } /** * This transform stream moves the suffix to the end of the stream, so results * like `` will be transformed to * ``. */ function createMoveSuffixStream(suffix) { let foundSuffix = false; const encoder = new TextEncoder(); const decoder = new TextDecoder(); return new TransformStream({ transform (chunk, controller) { if (foundSuffix) { return controller.enqueue(chunk); } const buf = decoder.decode(chunk); const index = buf.indexOf(suffix); if (index > -1) { foundSuffix = true; // If the whole chunk is the suffix, then don't write anything, it will // be written in the flush. if (buf.length === suffix.length) { return; } // Write out the part before the suffix. const before = buf.slice(0, index); chunk = encoder.encode(before); controller.enqueue(chunk); // In the case where the suffix is in the middle of the chunk, we need // to split the chunk into two parts. if (buf.length > suffix.length + index) { // Write out the part after the suffix. const after = buf.slice(index + suffix.length); chunk = encoder.encode(after); controller.enqueue(chunk); } } else { controller.enqueue(chunk); } }, flush (controller) { // Even if we didn't find the suffix, the HTML is not valid if we don't // add it, so insert it at the end. controller.enqueue(encoder.encode(suffix)); } }); } function createRootLayoutValidatorStream(assetPrefix = "", getTree) { let foundHtml = false; let foundBody = false; const encoder = new TextEncoder(); const decoder = new TextDecoder(); let content = ""; return new TransformStream({ async transform (chunk, controller) { // Peek into the streamed chunk to see if the tags are present. if (!foundHtml || !foundBody) { content += decoder.decode(chunk, { stream: true }); if (!foundHtml && content.includes(" 0) { controller.enqueue(encoder.encode(``)); } } }); } function chainTransformers(readable, transformers) { let stream = readable; for (const transformer of transformers){ if (!transformer) continue; stream = stream.pipeThrough(transformer); } return stream; } async function continueFizzStream(renderStream, { suffix, inlinedDataStream, isStaticGeneration, getServerInsertedHTML, serverInsertedHTMLToHead, validateRootLayout }) { const closeTag = ""; // Suffix itself might contain close tags at the end, so we need to split it. const suffixUnclosed = suffix ? suffix.split(closeTag, 1)[0] : null; // If we're generating static HTML and there's an `allReady` promise on the // stream, we need to wait for it to resolve before continuing. if (isStaticGeneration && "allReady" in renderStream) { await renderStream.allReady; } return chainTransformers(renderStream, [ // Buffer everything to avoid flushing too frequently createBufferedTransformStream(), // Insert generated tags to head getServerInsertedHTML && !serverInsertedHTMLToHead ? createInsertedHTMLStream(getServerInsertedHTML) : null, // Insert suffix content suffixUnclosed != null && suffixUnclosed.length > 0 ? createDeferredSuffixStream(suffixUnclosed) : null, // Insert the inlined data (Flight data, form state, etc.) stream into the HTML inlinedDataStream ? createMergedTransformStream(inlinedDataStream) : null, // Close tags should always be deferred to the end createMoveSuffixStream(closeTag), // Special head insertions // TODO-APP: Insert server side html to end of head in app layout rendering, to avoid // hydration errors. Remove this once it's ready to be handled by react itself. getServerInsertedHTML && serverInsertedHTMLToHead ? createHeadInsertionTransformStream(getServerInsertedHTML) : null, validateRootLayout ? createRootLayoutValidatorStream(validateRootLayout.assetPrefix, validateRootLayout.getTree) : null ]); } async function continuePostponedFizzStream(renderStream, { inlinedDataStream, isStaticGeneration, getServerInsertedHTML, serverInsertedHTMLToHead }) { const closeTag = ""; // If we're generating static HTML and there's an `allReady` promise on the // stream, we need to wait for it to resolve before continuing. if (isStaticGeneration && "allReady" in renderStream) { await renderStream.allReady; } return chainTransformers(renderStream, [ // Buffer everything to avoid flushing too frequently createBufferedTransformStream(), // Insert generated tags to head getServerInsertedHTML && !serverInsertedHTMLToHead ? createInsertedHTMLStream(getServerInsertedHTML) : null, // Insert the inlined data (Flight data, form state, etc.) stream into the HTML inlinedDataStream ? createMergedTransformStream(inlinedDataStream) : null, // Close tags should always be deferred to the end createMoveSuffixStream(closeTag) ]); } //# sourceMappingURL=node-web-streams-helper.js.map