1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  */
    
  8. 
    
  9. import {Writable} from 'stream';
    
  10. import * as React from 'react';
    
  11. import {renderToPipeableStream} from 'react-dom/server';
    
  12. import App from '../src/App';
    
  13. import {ABORT_DELAY} from './delays';
    
  14. 
    
  15. // In a real setup, you'd read it from webpack build stats.
    
  16. let assets = {
    
  17.   'main.js': '/main.js',
    
  18.   'main.css': '/main.css',
    
  19. };
    
  20. 
    
  21. function HtmlWritable(options) {
    
  22.   Writable.call(this, options);
    
  23.   this.chunks = [];
    
  24.   this.html = '';
    
  25. }
    
  26. 
    
  27. HtmlWritable.prototype = Object.create(Writable.prototype);
    
  28. HtmlWritable.prototype.getHtml = function getHtml() {
    
  29.   return this.html;
    
  30. };
    
  31. HtmlWritable.prototype._write = function _write(chunk, encoding, callback) {
    
  32.   this.chunks.push(chunk);
    
  33.   callback();
    
  34. };
    
  35. HtmlWritable.prototype._final = function _final(callback) {
    
  36.   this.html = Buffer.concat(this.chunks).toString();
    
  37.   callback();
    
  38. };
    
  39. 
    
  40. module.exports = function render(url, res) {
    
  41.   let writable = new HtmlWritable();
    
  42.   res.socket.on('error', error => {
    
  43.     console.error('Fatal', error);
    
  44.   });
    
  45.   let didError = false;
    
  46.   let didFinish = false;
    
  47. 
    
  48.   writable.on('finish', () => {
    
  49.     // If something errored before we started streaming, we set the error code appropriately.
    
  50.     res.statusCode = didError ? 500 : 200;
    
  51.     res.setHeader('Content-type', 'text/html');
    
  52.     res.send(writable.getHtml());
    
  53.   });
    
  54. 
    
  55.   const {pipe, abort} = renderToPipeableStream(<App assets={assets} />, {
    
  56.     bootstrapScripts: [assets['main.js']],
    
  57.     onAllReady() {
    
  58.       // Full completion.
    
  59.       // You can use this for SSG or crawlers.
    
  60.       didFinish = true;
    
  61.     },
    
  62.     onShellReady() {
    
  63.       // If something errored before we started streaming, we set the error code appropriately.
    
  64.       pipe(writable);
    
  65.     },
    
  66.     onShellError(x) {
    
  67.       // Something errored before we could complete the shell so we emit an alternative shell.
    
  68.       res.statusCode = 500;
    
  69.       res.send('<!doctype><p>Error</p>');
    
  70.     },
    
  71.     onError(x) {
    
  72.       didError = true;
    
  73.       console.error(x);
    
  74.     },
    
  75.   });
    
  76.   // Abandon and switch to client rendering if enough time passes.
    
  77.   // Try lowering this to see the client recover.
    
  78.   setTimeout(() => {
    
  79.     if (!didFinish) {
    
  80.       abort();
    
  81.     }
    
  82.   }, ABORT_DELAY);
    
  83. };