node-web-streams-helper.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.encodeText = encodeText;
  6. exports.decodeText = decodeText;
  7. exports.readableStreamTee = readableStreamTee;
  8. exports.chainStreams = chainStreams;
  9. exports.streamFromArray = streamFromArray;
  10. exports.streamToString = streamToString;
  11. exports.createBufferedTransformStream = createBufferedTransformStream;
  12. exports.createFlushEffectStream = createFlushEffectStream;
  13. exports.renderToInitialStream = renderToInitialStream;
  14. exports.createHeadInjectionTransformStream = createHeadInjectionTransformStream;
  15. exports.createDeferredSuffixStream = createDeferredSuffixStream;
  16. exports.createInlineDataStream = createInlineDataStream;
  17. exports.createSuffixStream = createSuffixStream;
  18. exports.continueFromInitialStream = continueFromInitialStream;
  19. var _nonNullable = require("../lib/non-nullable");
  20. function encodeText(input) {
  21. return new TextEncoder().encode(input);
  22. }
  23. function decodeText(input, textDecoder) {
  24. return textDecoder ? textDecoder.decode(input, {
  25. stream: true
  26. }) : new TextDecoder().decode(input);
  27. }
  28. function readableStreamTee(readable) {
  29. const transformStream = new TransformStream();
  30. const transformStream2 = new TransformStream();
  31. const writer = transformStream.writable.getWriter();
  32. const writer2 = transformStream2.writable.getWriter();
  33. const reader = readable.getReader();
  34. function read() {
  35. reader.read().then(({ done , value })=>{
  36. if (done) {
  37. writer.close();
  38. writer2.close();
  39. return;
  40. }
  41. writer.write(value);
  42. writer2.write(value);
  43. read();
  44. });
  45. }
  46. read();
  47. return [
  48. transformStream.readable,
  49. transformStream2.readable
  50. ];
  51. }
  52. function chainStreams(streams) {
  53. const { readable , writable } = new TransformStream();
  54. let promise = Promise.resolve();
  55. for(let i = 0; i < streams.length; ++i){
  56. promise = promise.then(()=>streams[i].pipeTo(writable, {
  57. preventClose: i + 1 < streams.length
  58. }));
  59. }
  60. return readable;
  61. }
  62. function streamFromArray(strings) {
  63. // Note: we use a TransformStream here instead of instantiating a ReadableStream
  64. // because the built-in ReadableStream polyfill runs strings through TextEncoder.
  65. const { readable , writable } = new TransformStream();
  66. const writer = writable.getWriter();
  67. strings.forEach((str)=>writer.write(encodeText(str)));
  68. writer.close();
  69. return readable;
  70. }
  71. async function streamToString(stream) {
  72. const reader = stream.getReader();
  73. const textDecoder = new TextDecoder();
  74. let bufferedString = "";
  75. while(true){
  76. const { done , value } = await reader.read();
  77. if (done) {
  78. return bufferedString;
  79. }
  80. bufferedString += decodeText(value, textDecoder);
  81. }
  82. }
  83. function createBufferedTransformStream(transform = (v)=>v) {
  84. let bufferedString = "";
  85. let pendingFlush = null;
  86. const flushBuffer = (controller)=>{
  87. if (!pendingFlush) {
  88. pendingFlush = new Promise((resolve)=>{
  89. setTimeout(async ()=>{
  90. const buffered = await transform(bufferedString);
  91. controller.enqueue(encodeText(buffered));
  92. bufferedString = "";
  93. pendingFlush = null;
  94. resolve();
  95. }, 0);
  96. });
  97. }
  98. return pendingFlush;
  99. };
  100. const textDecoder = new TextDecoder();
  101. return new TransformStream({
  102. transform (chunk, controller) {
  103. bufferedString += decodeText(chunk, textDecoder);
  104. flushBuffer(controller);
  105. },
  106. flush () {
  107. if (pendingFlush) {
  108. return pendingFlush;
  109. }
  110. }
  111. });
  112. }
  113. function createFlushEffectStream(handleFlushEffect) {
  114. return new TransformStream({
  115. transform (chunk, controller) {
  116. const flushedChunk = encodeText(handleFlushEffect());
  117. controller.enqueue(flushedChunk);
  118. controller.enqueue(chunk);
  119. }
  120. });
  121. }
  122. function renderToInitialStream({ ReactDOMServer , element , streamOptions }) {
  123. return ReactDOMServer.renderToReadableStream(element, streamOptions);
  124. }
  125. function createHeadInjectionTransformStream(inject) {
  126. let injected = false;
  127. return new TransformStream({
  128. transform (chunk, controller) {
  129. const content = decodeText(chunk);
  130. let index;
  131. if (!injected && (index = content.indexOf("</head")) !== -1) {
  132. injected = true;
  133. const injectedContent = content.slice(0, index) + inject() + content.slice(index);
  134. controller.enqueue(encodeText(injectedContent));
  135. } else {
  136. controller.enqueue(chunk);
  137. }
  138. }
  139. });
  140. }
  141. function createDeferredSuffixStream(suffix) {
  142. let suffixFlushed = false;
  143. let suffixFlushTask = null;
  144. return new TransformStream({
  145. transform (chunk, controller) {
  146. controller.enqueue(chunk);
  147. if (!suffixFlushed && suffix) {
  148. suffixFlushed = true;
  149. suffixFlushTask = new Promise((res)=>{
  150. // NOTE: streaming flush
  151. // Enqueue suffix part before the major chunks are enqueued so that
  152. // suffix won't be flushed too early to interrupt the data stream
  153. setTimeout(()=>{
  154. controller.enqueue(encodeText(suffix));
  155. res();
  156. });
  157. });
  158. }
  159. },
  160. flush (controller) {
  161. if (suffixFlushTask) return suffixFlushTask;
  162. if (!suffixFlushed && suffix) {
  163. suffixFlushed = true;
  164. controller.enqueue(encodeText(suffix));
  165. }
  166. }
  167. });
  168. }
  169. function createInlineDataStream(dataStream) {
  170. let dataStreamFinished = null;
  171. return new TransformStream({
  172. transform (chunk, controller) {
  173. controller.enqueue(chunk);
  174. if (!dataStreamFinished) {
  175. const dataStreamReader = dataStream.getReader();
  176. // NOTE: streaming flush
  177. // We are buffering here for the inlined data stream because the
  178. // "shell" stream might be chunkenized again by the underlying stream
  179. // implementation, e.g. with a specific high-water mark. To ensure it's
  180. // the safe timing to pipe the data stream, this extra tick is
  181. // necessary.
  182. dataStreamFinished = new Promise((res)=>setTimeout(async ()=>{
  183. try {
  184. while(true){
  185. const { done , value } = await dataStreamReader.read();
  186. if (done) {
  187. return res();
  188. }
  189. controller.enqueue(value);
  190. }
  191. } catch (err) {
  192. controller.error(err);
  193. }
  194. res();
  195. }, 0));
  196. }
  197. },
  198. flush () {
  199. if (dataStreamFinished) {
  200. return dataStreamFinished;
  201. }
  202. }
  203. });
  204. }
  205. function createSuffixStream(suffix) {
  206. return new TransformStream({
  207. flush (controller) {
  208. if (suffix) {
  209. controller.enqueue(encodeText(suffix));
  210. }
  211. }
  212. });
  213. }
  214. async function continueFromInitialStream(renderStream, { suffix , dataStream , generateStaticHTML , flushEffectHandler , flushEffectsToHead }) {
  215. const closeTag = "</body></html>";
  216. const suffixUnclosed = suffix ? suffix.split(closeTag)[0] : null;
  217. if (generateStaticHTML) {
  218. await renderStream.allReady;
  219. }
  220. const transforms = [
  221. createBufferedTransformStream(),
  222. flushEffectHandler && !flushEffectsToHead ? createFlushEffectStream(flushEffectHandler) : null,
  223. suffixUnclosed != null ? createDeferredSuffixStream(suffixUnclosed) : null,
  224. dataStream ? createInlineDataStream(dataStream) : null,
  225. suffixUnclosed != null ? createSuffixStream(closeTag) : null,
  226. createHeadInjectionTransformStream(()=>{
  227. // TODO-APP: Inject flush effects to end of head in app layout rendering, to avoid
  228. // hydration errors. Remove this once it's ready to be handled by react itself.
  229. const flushEffectsContent = flushEffectHandler && flushEffectsToHead ? flushEffectHandler() : "";
  230. return flushEffectsContent;
  231. }),
  232. ].filter(_nonNullable.nonNullable);
  233. return transforms.reduce((readable, transform)=>readable.pipeThrough(transform), renderStream);
  234. }
  235. //# sourceMappingURL=node-web-streams-helper.js.map