GithubHelp home page GithubHelp logo

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 much faster at sending down the page's first byte than ReactDOM.renderToString, and user perceived performance gains can be significant.

Why?

One difficulty with ReactDOM.renderToString is that it is synchronous, and it can become a performance bottleneck 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 the entire HTML is created, which means that browsers can't start working on painting the page until the renderToString call is finished. 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 these three problems by rendering asynchronously to a stream.

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 in my tests is almost identical to React's methods when testing with zero network latency, but TTLB can be significantly lower than React when real network speed and latency is used. To see more on performance, check out the react-dom-stream-example repo.

To learn more about ways to speed up your React server (including using react-dom-stream), check out my talk from the ReactJS SF meetup in January 2016:

YouTube: Speed Up Your React Server With These 6 Weird Tricks

How?

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

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

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

Rendering on the server

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

ReadableStream renderToString(ReactElement element, [Object options])

This method renders element to a readable stream that is returned from the method. In an Express app, it is used like this (all examples are in ES2015):

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

app.get('/', (req, res) => {
	// TODO: write out the html, head, and body tags
	var stream = ReactDOMStream.renderToString(<Foo prop={value}/>);
	stream.pipe(res, {end: false});
	stream.on("end", function() {
		// TODO: write out the rest of the page, including the closing body and html tags.
		res.end();
	});
});

Or, if you'd like a more terse (but IMO slightly harder to understand) version:

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

app.get('/', (req, res) => {
	// TODO: write out the html, head, and body tags
	ReactDOMStream.renderToString(<Foo prop={value}/>).on("end", () =>{
		// TODO: write out the rest of the page, including the closing body and html tags.
		res.end();
	}).pipe(res, {end:false});
});

If the piping syntax is not to your liking, check out the section below on combining renderToString and renderToStaticMarkup to render a full page.

ReadableStream renderToStaticMarkup(ReactElement element, [Object options])

This method renders element to a readable stream that is returned from the method. 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.

In an Express app, it is used like this:

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

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

As of v0.3.x, renderToStaticMarkup can also accept streams as children in the React element tree; see the next section for how this can be used to render a full page.

Combining renderToStaticMarkup and renderToString to serve a page

If you have used React for server and client rendering before, you probably know that React does not work on the client side if you try to render the entire HTML markup or even if you try to render just the entire HTML body. As a result, with vanilla react-dom/server many developers use renderToStaticMarkup to generate the <html>, <head>, and <body> tags, and then embed the output of renderToString into a div inside the body.

I wanted this pattern to be possible in react-dom-stream, so renderToStaticMarkup accepts readable streams as children in the React element tree the same way that it accepts Strings as children in the tree. This is useful for using renderToStaticMarkup for the page template and renderToString for the dynamic HTML that you want to render with React on the client. Note that, as with Strings, react-dom-stream automatically HTML encodes streams to protect against cross-site scripting attacks, so you need to use dangerouslySetInnerHTML to embed markup. That would look something like this:

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

app.get("/", (req, res) => {
	// use renderToStaticMarkup to generate the entire HTML markup, embedding the
	// dynamic part under the renderDiv div.
	ReactDOMStream.renderToStaticMarkup(
		<html>
			<head>
				<script src="myscript.js"></script>
				<title>My Cool Page</title>
			</head>
			<body>
				<div id="renderDiv" dangerouslySetInnerHTML={{__html: ReactDOMStream.renderToString(<Foo prop={value}/>)}}></div>
				<script>
					{`  // custom javascript for reconnecting on the client side.
						ReactDOM.render(<Foo prop={${value}}/>, document.getElementById("renderDiv"));`}
				</script>
			</body>
		</html>
	).pipe(res);
});

If you don't like using dangerouslySetInnerHTML, consider using my companion project react-raw-html, which doesn't encode children that are Strings or Streams. The previous example would then look like this:

import ReactDOMStream from "react-dom-stream/server";
import Raw from "react-raw-html";

app.get("/", (req, res) => {
	// use renderToStaticMarkup to generate the entire HTML markup, embedding the
	// dynamic part under the renderDiv div.
	ReactDOMStream.renderToStaticMarkup(
		<html>
			<head>
				<script src="myscript.js"></script>
				<title>My Cool Page</title>
			</head>
			<body>
				<Raw.div id="renderDiv">
					{ReactDOMStream.renderToString(<Foo prop={value}/>)}
				</Raw.div>
				<script>
					{`  // custom javascript for reconnecting on the client side.
						ReactDOM.render(<Foo prop={${value}}/>, document.getElementById("renderDiv"));`}
				</script>
			</body>
		</html>
	).pipe(res);
});

Note that renderToString does not accept streams as children in the React element tree, as there would be no way to reconnect to that markup on the client side. If you want to embed a stream on the server side, you want to use renderToStaticMarkup.

Experimental feature: Component Caching

In v0.5.x, I've added an experimental feature: component caching. This feature is based on the insight that a large amount of rendering time on a React server is wasted re-rendering components with the exact same props and state they had in the previous page render. If a component is a pure function of props & state, it should be possible to cache (or memoize) the render results and speed up rendering significantly.

To try this out in v0.5.x, you need to do two things. First, you must instantiate a cache object and pass it to either renderToString or renderToStaticMarkup as the cache attribute on the optional options argument. Currently, there is only one implementation of a cache, LRURenderCache, contained in the module react-dom-stream/lru-render-cache. It takes in an options object that has one attribute, max, which specifies the maximum number of characters in the cache. It looks like this:

import ReactDOMStream from "react-dom-stream/server";
import LRURenderCache from "react-dom-stream/lru-render-cache";

// create a cache that stores 64MB of text.
const myCache = LRURenderCache({max: 64 * 1024 * 1024});

app.get('/', (req, res) => {
	ReactDOMStream.renderToStaticMarkup(<Foo prop={value}/>, {cache: myCache}).pipe(res);
});

Second, you need to have your components opt in to caching by implementing a method called componentCacheKey, which returns a single String representing a useable cache key for that component's current state. The String returned by componentCacheKey must include all inputs to rendering so that it can be used as a cache key for the render. If componentCacheKey leaves out some of the inputs to rendering, it's possible that you will get cache collisions, and you will serve up the wrong content to your users. Here's an example of a correct implementation:

import React from "react"

export default class CacheableComponent extends React.Component {
	render() {
		return <span>Hello, ${this.props.name}!</span>;
	}

	componentCacheKey() {
		// the name prop completely specifies what the rendering will look like.
		return this.props.name;
	}
}

Here is an incorrect implementation:

import React from "react"

export default class BADCacheableComponent extends React.Component {
	render() {
		return <span>Hello, ${this.props.name}! It is ${new Date()}</span>;
	}

	// INCORRECT!!
	componentCacheKey() {
		// the name prop does NOT completely specify the render,
		// so this implementation is WRONG.
		return this.props.name;
	}
}

In this example, the rendering depends on both this.props.name and Date.now(), but componentCacheKey only returns this.props.name. This means that subsequent renderings of the component with the same name will get a cache hit, and the time will therefore be out of date.

Note that this caching feature is powerful, but as of right now it is extremely experimental. I would be very pleased if folks try it out in development and give me feedback, but I strongly believe it should not be used in production until it has been tested more thoroughly. Server-side caching on a component basis has real potential, but mistakes in server-side caching can be extremely costly, as they are often liable to leak private information between users. You have been warned.

Also, note that the APIs of this feature are liable to change.

In the future, I hope to add features to caching such as:

  • the ability to write a custom cache implementation.
  • comprehensive statistics of cache efficiency (hit & miss rate, time spent looking up entries in the cache, total time saved/spent by the cache, etc.).
  • a self-tuning cache that can decide whether or not to cache a component based on that component class's previous hit rate and/or the expense of generating its cache keys.

Feel free to file issues asking for features you would find interesting.

Reconnecting the markup on the client

In previous versions of react-dom-stream, you needed to use a special render library to reconnect to server-generated markup. As of version 0.2.0 and later, this is no longer the case. You can now use the normal ReactDOM.render method, as you would when using ReactDOM to generate server-side markup.

When should you use react-dom-stream?

Currently, react-dom-stream offers a slight tradeoff: for larger pages, it significantly reduces time to first byte while for smaller pages, react-dom-stream can actually be a net negative, albeit a small one. String construction in Node is extremely fast, and streams and asynchronicity add an overhead. In my testing, pages smaller than about 50KB have worse TTFB and TTLB using react-dom-stream. These pages are not generally a performance bottleneck to begin with, though, and on my mid-2014 2.8GHz MBP, the difference in render time between react-dom and react-dom-stream for these small pages is usually less than a millisecond.

For larger pages, the TTFB stays relatively constant as the page gets larger (TTFB stays around 4ms on my laptop), while the TTLB tends to hover at or slightly below the time that react-dom takes. However, even here, there is a wrinkle, because most of my testing has been done against localhost. When I use real world network speeds and latencies, react-dom-stream often beats React significantly in both TTFB and TTLB. This is probably because react-dom-stream's faster time to first byte gets a headstart on filling up the network pipe.

One other operational challenge that react-dom-stream can help with is introducing asynchronicity, which can allow requests for small pages to not get completely blocked by executing requests for large pages.

Who?

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

Status

This project is of beta quality; I don't know if it has been used in production yet, and the API is still firming up. It does, however, pass all of the automated tests that are currently run on react-dom in the main React project plus one or two dozen more that I've written.

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!

Upgrading from previous versions

There were major breaking API changes in v0.2 and v0.4. To learn how to upgrade your client code, please read CHANGELOG.md.

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 and dist, 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.2, 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.

Please note that this project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. [code-of-conduct]: http://todogroup.org/opencodeofconduct/#react-dom-stream/[email protected]

react-dom-stream's People

Contributors

aickin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-dom-stream's Issues

Component caching and react ids

Consider a simple cached component:

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>
  }

  componentCacheKey() {
    return this.props.name;
  }
}

And usage like this:

const people = [{id: 0, name: 'Joe'}, {id: 1, name: 'Mary'}, {id: 2, name: 'Joe'}]

class App extends React.Component {
  render() {
    return <div>
      {people.map(person => <Hello key={person.id} name={person.name} />)}
    </div>
  }
}

If you were to run the above with SSR, React will complain that there was a difference between the client and the server when rendering. The issue is that the caching key only looks at the name prop but the actual output from the cache also contains the react id which includes the key from the first Hello component. When the second Hello component is rendered with the same name but a different key, the id is incorrect.

As an FYI, I know there have been some recent changes to React to remove the id on the clientside, and I believe I recall reading somewhere that they might be able to remove the ids in SSR as well which would address this issue.

React attempted to reuse markup in a container but the checksum was invalid.

Hi guys,
I was making a small POC with the stream SSR, and React seems to be complaining about the checksum while reattaching the DOM.

Just wanted to know if I'm doing something wrong or this is just a version difference between the react-dom-stream fork and react-dom.

This is the warning from React:

app.js:1243 Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
 (client) <div data-reactroot="" data-reactid
 (server) <div data-reactid=".57vkf2fmyo"><h1warning @ app.js:1243ReactMount._mountImageIntoNode @ app.js:12993ReactMount__mountImageIntoNode @ app.js:13855mountComponentIntoNode @ app.js:12649Mixin.perform @ app.js:17286batchedMountComponentIntoNode @ app.js:12663Mixin.perform @ app.js:17286ReactDefaultBatchingStrategy.batchedUpdates @ app.js:10689batchedUpdates @ app.js:15009ReactMount._renderNewRootComponent @ app.js:12816ReactMount__renderNewRootComponent @ app.js:13855ReactMount._renderSubtreeIntoContainer @ app.js:12893ReactMount.render @ app.js:12913React_render @ app.js:138551.react @ app.js:14s @ app.js:1e @ app.js:1(anonymous function) @ app.js:1

This is the code I've used to attach the client in both tests:

ReactDOM.render(<div><h1>{InitData.welcomeText}</h1></div>, document.getElementById("app"));

This is what gets rendered with the original React DOM:

<html>
   <head>
      <title>SSR POC</title>
   </head>
   <body>
      <div id="app">
         <div data-reactroot="" data-reactid="1" data-react-checksum="-503964732">
            <h1 data-reactid="2">Hello World :)</h1>
         </div>
      </div>
      <script>var InitData = {"welcomeText":"Hello World :)"};</script>
      <script src="js/app.js"></script>
   </body>
</html>

The code that renders it:

import ReactDOM from "react-dom/server";
......
    res.end(ReactDOM.renderToStaticMarkup(
        <html>
            <head>
                <title>SSR POC</title>
            </head>
            <body>
                <div id="app" dangerouslySetInnerHTML={{__html: ReactDOM.renderToString(<div><h1>{data.welcomeText}</h1></div>)}}></div>
                <script dangerouslySetInnerHTML={{__html: 'var InitData = '+ JSON.stringify(data) +';'}}>
                </script>
                <script src="js/app.js"></script>
            </body>
        </html>
    ));

This is what gets rendered with react-dom-stream:

<html>
   <head>
      <title>SSR POC</title>
   </head>
   <body>
      <div id="app">
         <div data-reactid=".4wu0ivepkw">
            <h1 data-reactid=".4wu0ivepkw.0">Hello World :)</h1>
         </div>
         <script type="text/javascript" id=".4wu0ivepkw.script">
        if (!document.querySelector) throw new Error("react-dom-stream requires document.querySelector. If using IE8 or IE9, please make sure you are in standards mode by including <!DOCTYPE html>");
        document.querySelector('[data-reactid=".4wu0ivepkw"]').setAttribute("data-react-checksum", 1817648556);
        var s = document.getElementById(".4wu0ivepkw.script");
        s.parentElement.removeChild(s);
         </script>
      </div>
      <script>var InitData = {"welcomeText":"Hello World :)"};</script>
      <script src="js/app.js"></script>
   </body>
</html>

and the code that renders it:

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

    ReactDOMStream.renderToStaticMarkup(
        <html>
            <head>
                <title>SSR POC</title>
            </head>
            <body>
                <div id="app" dangerouslySetInnerHTML={{__html: ReactDOMStream.renderToString(<div><h1>{data.welcomeText}</h1></div>)}}></div>
                <script dangerouslySetInnerHTML={{__html: 'var InitData = '+ JSON.stringify(data) +';'}}>
                </script>
                <script src="js/app.js"></script>
            </body>
        </html>
    ).pipe(res);

Here are my dependencies:

  "dependencies": {
    "express": "^4.13.4",
    "react": "^15.0.1",
    "react-dom": "^15.0.1",
    "react-dom-stream": "^0.5.1",
    "react-raw-html": "^0.4.0"
  }

Let me know if I can help with something and thanks for the great work Sasha,
Sam.

React 15

Thank for making this library! We're running into TTFB latency issues with a sever side rendered React app and we're currently evaluating using react-dom-stream. Now that React 15.0 is in RC, we wanted to see if you were planning to update react-dom-stream to support it? Also, have you spoken with the react team at all about taking your changes upstream?

Fails on function that returns an array

I have a product which would greatly benefit from this, so I have it a shot. Unfortunately it doesn't work.

My root component gets rendered fine, as well as its direct children that are components. But then it has further children generated by a function that returns an array of components โ€” those get dropped on the floor. The root component render returns something like:

<article>
  <h1>{title}</h1>
  {functionThatReturnsAnArrayOfComponents()}
</article>

I get the article with its title but that's it. I don't have time to debug further right now; if I get a change I will investigate later.

How does it work?

Hi, thanks for the great project!

I am trying to find the code that powers renderToString to understand how you are making the virtual node into a stream, but it seems like the bulk of the code is omitted from the repository (lib and dist folders, referenced in

renderToString: ReactDOMStream.renderToString,
). Do you know where I could find it?

Cheers,
Olly

problem: how to send 500 if a component throws an exception

I'm not sure I fully understand this yet, but if I do, then by the time react-dom-stream gets to a component that throws an error, a status code of 200 has already been written to the response header and it's impossible to send a 500 if an error occurs.

Is this an intractable problem? Maybe you know how other streaming servers deal with this, I don't.

RenderStream.maxStackDepth too low

I am rendering a rather large application and the RenderStream.maxStackDepth limit of 500 it too high.

RangeError: Maximum call stack size exceeded
  at ReactCompositeComponentMixin._renderValidatedComponent (/node_modules/react-dom-stream/lib/ReactCompositeComponent.js:746:32)
  at wrapper [as _renderValidatedComponent] (/node_modules/react-dom-stream/lib/ReactPerf.js:66:21)
  at ReactCompositeComponentMixin.mountComponentAsync (/node_modules/react-dom-stream/lib/ReactCompositeComponent.js:343:30)
  at Object.ReactReconciler.mountComponentAsync (/node_modules/react-dom-stream/lib/ReactReconciler.js:44:35)
  at ReactDOMComponent.ReactMultiChild.Mixin.renderChildren (/node_modules/react-dom-stream/lib/ReactMultiChild.js:272:23)
  at /node_modules/react-dom-stream/lib/ReactMultiChild.js:274:17
  at /node_modules/react-dom-stream/lib/ReactReconciler.js:49:7
  at /node_modules/react-dom-stream/lib/ReactCompositeComponent.js:368:7
  at /node_modules/react-dom-stream/lib/ReactReconciler.js:49:7
  at /node_modules/react-dom-stream/lib/ReactDOMComponent.js:608:9
  at componentInstance.mountComponentAsync._this2.done (/node_modules/react-dom-stream/lib/ReactServerAsyncRendering.js:177:15)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  at /node_modules/react-dom-stream/lib/ReactDOMComponent.js:594:7
  at /node_modules/react-dom-stream/lib/ReactMultiChild.js:276:11
  at /node_modules/react-dom-stream/lib/ReactReconciler.js:49:7
  at /node_modules/react-dom-stream/lib/ReactCompositeComponent.js:368:7
  at /node_modules/react-dom-stream/lib/ReactReconciler.js:49:7
  at /node_modules/react-dom-stream/lib/ReactDOMComponent.js:608:9
  at componentInstance.mountComponentAsync._this2.done (/node_modules/react-dom-stream/lib/ReactServerAsyncRendering.js:177:15)
  at _createContentMarkupAsync.closeTag (/node_modules/react-dom-stream/lib/ReactDOMComponent.js:582:9)
  ... (And on for thousands of lines)

I am able to successfully render the application if I manually change the RenderStream.maxStackDepth to something like 300, but this option is not configurable through the external API.

Is there a larger issue I am missing that is causing this error? Is it possible to expose some sort of configuration API so this number can be modified externally?

Two copies of React

Any way we could "cleanly" overload the exports of 'react-dom/server' server side ?

I just checked a possible case where I would be using react-dom/server and react-dom-stream/server side by side and of course they are not using the same source files ...

Shouldn't react-dom-stream be used on with React as a peerDependency and actually instead of requiring something you copied into ./lib/, for example
var ReactDefaultBatchingStrategy = require('./ReactDefaultBatchingStrategy');
you would actually require the real stuff, that is
var ReactDefaultBatchingStrategy = require('react/lib/ReactDefaultBatchingStrategy');

Moving to react 15.0

I see work in progress. Do you wanted an contributor for improve this awesome project?

Client side must use render provided by this library

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

Any reason for this ? The markup generation strategy should have no impact on its reuse once client side code is being executed no ?

License?

Hey, love this project; very interested in using it. Hope to see the behavior merged into the official React project at some point. I didn't see a license anywhere -- what is it released under? Could you add that info to the repo?

HTML Entities

So I'm trying to stream my response and I'm getting special characters on the screen.

For instance, in my component I have &nbsp; which is translated to ร‚ when streaming the response. If I use the native React renderToString it works fine

un-rebuildable package!

the build script is not present in the package
any way to get ride of minified react??
or update it to version 15???

React warning - Replacing React-rendered children with a new root component

Got the following warning when I started using react-dom-stream.

warning.js?0260:45 Warning: render(...): Replacing React-rendered children with a new root component. If you intended to update the children of this node, you should instead have the existing children update their state and render the new components instead of calling ReactDOM.render.

How i'm using it:

...
ReactDOMStream.renderToString(
      <HtmlDocument
        lang={ req.locale }
        state={ state }
        ApplicationComponent={ Application }
        webpackStats={ webpackStats }
        currentUser={ currentUser }
        title={ "test" }
        req={ req }
      />,
      res
    ).then((hash) => {
      res.write(`<script>window.__REACT_DOM_STREAM_HASH__=${hash}</script>`);
      res.end();
    });

...

This is really huge HTML page (24.1KB). However, I do not see it the individual html fragments of being streamed down when I see view source.

Am I missing something?

Gzip will cause react-dom-stream to not stream content

image

Tried this out on a test site, I don't see the content being streamed down properly cuz if it did, you would see main-xxx.css start downloading before for-him/ finish downloading.

Am I missing some kind of proper encoding?

You are manually calling a React.PropTypes validation function

After upgrading react to 15.3.0, it shows the following warning message via the server side rendering:

Warning: You are manually calling a React.PropTypes validation function for the head prop on Html. This is deprecated and will not work in the next major version. You may be seeing this warning due to a third-party PropTypes library. See https://fb.me/react-warning-dont-call-proptypes for details.
Warning: You are manually calling a React.PropTypes validation function for the content prop on Html. This is deprecated and will not work in the next major version. You may be seeing this warning due to a third-party PropTypes library. See https://fb.me/react-warning-dont-call-proptypes for details.

I think it may call PropTypes directly within the call path of renderToStaticMarkup
The issue can be reproduced by the following code:

function renderComponent({ componentHtml, head }) {
  return renderToStaticMarkup(                            
    <Html                                                        
      content={componentHtml}             
      head={head}                      
    />
  );                                    
}   

Could you kindly help this issue.
Thanks.

Rendering issue with empty span tags.

So I have a page-wrapper component that wraps page content in all the boilerplate code of my website to make things easier, and one of the things it processes is error/alert messages from the server to display at the top of the page content.

When there are no messages to display, it just renders as an empty span. However for some reason [email protected] is rendering <span></span> as <span< span>, which mangles parts of the page.

Screenshot of bugged rendering as displayed in the chrome devtools
bugged

Screenshot of the correct rendering as displayed in the chrome devtools (plus the React hashes since it rerendered it already)
correct

I didn't notice it for a while since the normal React re-renders it correctly on the client immediately.

how to use in koajs v2

when I write this code in koajs v2

ReactDOMStream.renderToString(<Foo prop={value}/>).pipe(ctx.res)

then error comes

events.js:154
      throw er; // Unhandled 'error' event
      ^

Error: write after end
    at ServerResponse.OutgoingMessage.write (_http_outgoing.js:422:15)
    at Adler32Stream.ondata (_stream_readable.js:529:20)
    at emitOne (events.js:90:13)
    at Adler32Stream.emit (events.js:182:7)
    at readableAddChunk (_stream_readable.js:147:16)
    at Adler32Stream.Readable.push (_stream_readable.js:111:10)
    at Adler32Stream.Transform.push (_stream_transform.js:127:32)
    at Adler32Stream._transform (/Users/ipconfig/dev/react-universal-starter/node_modules/react-dom-stream/lib/ReactServerAsyncRendering.js:61:12)
    at Adler32Stream.Transform._read (_stream_transform.js:166:10)

help me!!!

Hangs on `<div dangerouslySetInnerHTML={{__html: undefined}} />`

We had a situation where the html trying to set ended up being undefined. I would expect this to throw a warning but still render like react-dom does. Instead it just hangs. This was really tricky to track down but we eventually traced it back to react-dom-stream acting differently than react-dom when html happens to be undefined.

Preventing errors from crashing node

I'm running into an issue where errors during the React render are crashing the node process. Here is a simple repro:

var React = require('react')
var ReactDOMStream = require('react-dom-stream/server')

var Component = React.createClass({
  render: function() {
    null.doSomething() // throws
    return React.createElement('span')
  }
})

var stream = ReactDOMStream.renderToString(React.createElement(Component))

// this should theoretically prevent node from crashing
stream.on('error', function(err) {
  console.log(err)
})

stream.on('readable', function() {
  var data
  while (null !== (data = this.read())) {
    console.log("Data:", data.toString('utf-8'))
  }
});

// application should keep running
setTimeout(null, 10000)

My understanding is that unhandled errors in streams will crash node, but even if I add an error handler as shown above the process still crashes. Am I missing something?

Dynamic meta tags / titles

Not sure if this is the best place to report this issue.

I am actually using react-helmet to support a declarative way of writing head and title tags. It is really useful in the traditional way to rendering on the server side.

The trouble is that the information for these head / title tags are only available after a tree of components have finished renderToString-ed.

Since we are streaming components down, the head / title tags will always be undefined. Wondering if there's a way to avoid this issue or use a completely different design to write the head / meta tags.

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.