GithubHelp home page GithubHelp logo

serendipious / react-dom-stream Goto Github PK

View Code? Open in Web Editor NEW

This project forked from aickin/react-dom-stream

0.0 2.0 0.0 80 KB

A streaming server-side rendering library for React.

JavaScript 100.00%

react-dom-stream's Introduction

react-dom-stream

This is a React renderer for generating markup on a NodeJS server, but unlike the built-in ReactDOM.renderToString, this module renders to a stream. Streams make this library as much as 47% faster in sending down a full page than ReactDOM.renderToString, and user perceived performance gains can be even greater.

Why?

One difficulty with ReactDOM.renderToString is that it is synchronous, and it can become a performance challenge in server-side rendering of React sites. This is especially true of pages with larger HTML payloads, because ReactDOM.renderToString's runtime tends to scale more or less linearly with the number of virtual DOM nodes. This leads to three problems:

  1. The server cannot send out any part of the response until all the HTML is created, which means that browsers can't start working on painting the page until the renderToString call is done. With larger pages, this can introduce a latency of hundreds of milliseconds.
  2. The server has to allocate memory for the entire HTML string.
  3. One call to ReactDOM.renderToString can dominate the CPU and starve out other requests. This is particularly troublesome on servers that serve a mix of small and large pages.

This project attempts to fix the first two problems by rendering directly to a stream, and I hope to work on the third problem as the project develops.

When web servers stream out their content, browsers can render pages for users before the entire response is finished. To learn more about streaming HTML to browsers, see HTTP Archive: adding flush and Flushing the Document Early.

My preliminary tests have found that this renderer keeps the TTFB nearly constant as the size of a page gets larger. TTLB can be slightly longer than React's methods (~3%) when testing with zero network latency, but TTLB is as much as 47% less than React when real network speeds are used. In a real world test on Heroku, for example, I found that compared to React TTFB was 65% less and TTLB was 37% less for a 108KB page. To see more on performance, check out the react-dom-stream-example repo.

How?

First, install react-dom-stream into your project:

npm install --save react-dom-stream react-dom react

There are three public methods in this project: renderToString, renderToStaticMarkup, and render, and they are intended as nearly drop-in replacements for the corresponding methods in react-dom.

Rendering on the server

To use either of the server-side methods, you need to require react-dom-stream/server.

Promise(Number) renderToString(ReactElement element, Stream stream, [Object options])

This method renders element to stream. The Promise that it returns resolves when the method is done writing to the stream, and the Promise resolves to a hash that must be passed to react-dom-stream's render on the client side (see below.)

In an Express app, it is used like this:

var ReactDOMStream = require("react-dom-stream/server");

app.get('/', function (req, res) {
	ReactDOMStream.renderToString(<Foo prop={value}/>, res)
		.then(function(hash) {
			// TODO: write the hash out to the page in a script tag
			res.end();
		});
});

Or, if you are using async/await from ES7, you can use it like this:

import ReactDOMStream from "react-dom-stream/server";

app.get('/', async function (req, res) {
	var hash = await ReactDOMStream.renderToString(<Foo prop={value}/>, res);
	// TODO: write the hash out to the page in a script tag
	res.end();
});

options is an optional object with tunable options for rendering. Right now, it only supports bufferSize, which is a number of bytes to buffer before writing to the stream. It defaults to 10,000, although I reserve the right to change the default as we learn more about the performance of this library.

// override the buffer to be just 1000 bytes.
var hash = await ReactDOMStream.renderToString(<Foo prop={value}/>, res, {bufferSize: 1000});

You may ask yourself: why go to the trouble of including a buffer, when the whole point of this project is to stream? Well, a naive implementation of React server streaming involves a lot of very small writes: every open tag, close tag, and tag content becomes a separate write. Preliminary performance tests indicate that streaming lots of small (<100B) writes to the output buffer can create enough overhead to overwhelm any performance gains from streaming. This is especially true when looking at TTLB. Coalescing writes into a limited, several kilobyte buffer adds a very small amount to TTFB, but in exchange TTLB comes way, way down.

Promise renderToStaticMarkup(ReactElement element, Stream stream, [Object options])

This method renders element to stream. Like ReactDOM.renderToStaticMarkup, it is only good for static pages where you don't intend to use React to render on the client side, and in exchange it generates smaller sized markup than renderToString. The Promise that it returns resolves when the method is done writing to the stream.

In an Express app, it is used like this:

var ReactDOMStream = require("react-dom-stream/server");

app.get('/', function (req, res) {
	ReactDOMStream.renderToStaticMarkup(<Foo prop={value}/>, res)
		.then(function() {
			res.end();
		});
});

Or, if you are using async/await from ES7, you can use it like this:

import ReactDOMStream from "react-dom-stream/server";

app.get('/', async function (req, res) {
	await ReactDOMStream.renderToStaticMarkup(<Foo prop={value}/>, res);
	res.end();
});

options is an optional object, and has one member, bufferSize. See renderToString above for more info.

Rendering on the client

To use the client-side method, you need to require react-dom-stream.

ReactComponent render(ReactElement element, DOMElement container, Number hash, [function callback])

If you generate server markup with this project, you cannot use the standard ReactDOM.render; you must use the render method in react-dom-stream. The only difference between react-dom's version and this one is that this render also takes in the hash returned from renderToString:

var ReactDOMStream = require("react-dom-stream");

var hash = 1234567890; // returned from renderToString's promise and read out into the page
ReactDOMStream.render(<Foo prop={value}/>, document.getElementById("bar"), hash);

All other client-side methods on react and react-dom (like createElement or unmountComponentAtNode) can be used in concert with this render method.

Who?

react-dom-stream is written by Sasha Aickin (@xander76), though let's be real, most of the code is forked from Facebook's React.

Status

This project is of alpha quality; it has not been used in production yet. It does, however, pass all of the automated tests that are currently run on react-dom in the main React project.

This module is forked from Facebook's React project. All extra code and modifications are offered under the Apache 2.0 license.

Something's wrong!

Please feel free to file any issues at https://github.com/aickin/react-dom-stream. Thanks!

Wait, where's the code?

Well, this is awkward.

You may have noticed that all of the server-side rendering code is in a directory called lib, which is not checked in to the react-dom-stream repo. That's because most of the interesting code is over at https://github.com/aickin/react/tree/streaming-render-0.14, which is a fork of React. Specifically, check out this commit to see most of the interesting changes from React 0.14.

I'd like to contribute!

Awesome. You are the coolest.

To get a working build running and learn a little about how the project is set up, please read CONTRIBUTING.md.

If you'd like to send PRs to either repo, please feel free! I'll require a CLA before pulling code to keep rights clean, but we can figure that out when we get there.

react-dom-stream's People

Contributors

aickin avatar

Watchers

James Cloos avatar Ankit Kuwadekar avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.