├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── README.md
├── TODO.md
├── index.js
├── lru-render-cache.js
├── modules
└── lru-render-cache.js
├── package.json
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | dist
4 | build
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v0.5.0
2 |
3 | ### Improved performance
4 |
5 | I found a few perf tweaks that make my TTLB benchmarks run as fast in `react-dom-stream` as they do in `react-dom`.
6 |
7 | ### Added Code Of Conduct
8 |
9 | I realized I'd forgotten to explicitly add a code of conduct. Shame on me!
10 |
11 | ### Component Caching
12 |
13 | I added an experimental feature, component caching, which allows the user to cache component renderings to be shared amongst different calls to `renderToString` and `renderToStaticMarkup`. This has the potential to massively speed up rendering when a server tends to continually generate the same markup snippets, but it can be very dangerous and leak private information if used incorrectly. Please do read the documentation for it in the README and try it out in development, but please DO NOT use in production until it has had more testing.
14 |
15 | ## v0.4.1
16 |
17 | This was just an update to the README file.
18 |
19 | ## v0.4.0
20 |
21 | This version changes the behavior of embedded streams when using `renderToStaticMarkup`. In v0.3.0, streams were sent directly to the output without escaping for the browser. However, I've come to believe this was the wrong decision, as it means that the default behavior is susceptible to cross-site scripting attacks. For this very reason, React automatically escapes Strings and provides `dangerouslySetInnerHTML` as a way for developers to get around the escaping.
22 |
23 | In v0.4.0, any stream that is a child of an element will be browser-encoded, whereas added as the `dangerouslySetInnerHTML.__html` property will be added directly. So if you have the following code in v0.3.0:
24 |
25 | ```javascript
26 | const stream = ReactDOMStream.renderToStaticMarkup(
27 |
28 | {ReactDOMStream.renderToString(Hello, World!)}
29 |
30 | );
31 | ```
32 |
33 | it will need to change to this in v0.4.0:
34 |
35 | ```javascript
36 | const stream = ReactDOMStream.renderToStaticMarkup(
37 | Hello, World!)}} />
38 | );
39 | ```
40 |
41 | If you prefer the v0.3.0 child syntax to `dangerouslySetInnerHTML`, I made a library called `react-raw-html` which passes through children without encoding. So the v0.3.0 example above could also be rewritten in v0.4.0 as:
42 |
43 | ```javascript
44 | import Raw from `react-raw-html`
45 |
46 | const stream = ReactDOMStream.renderToStaticMarkup(
47 |
48 | {ReactDOMStream.renderToString(Hello, World!)}
49 |
50 | );
51 | ```
52 |
53 |
54 | ## v0.3.0
55 |
56 | This version added the ability to embed streams as children in the React element tree when using `renderToStaticMarkup`.
57 |
58 | In v0.3.0, I also removed the v0.1.x API. If you need to convert your code, please see below how to do so.
59 |
60 | ## v0.2.0
61 |
62 | This version's main achievement is changing the API to be more stream-friendly. The 0.1.x API is still supported, but it is deprecated and will cause a console error. In version 0.3.0, I will remove support for the 0.1.x API.
63 |
64 | ### Converting code from the v0.1.x API to the v0.2.x API
65 |
66 | The first difference between v0.1.x's API and v0.2.x's API is how they handle the stream. v0.1.x accepted a Writable stream as an argument to `renderToString` and `renderToStaticMarkup`, but v0.2.x instead returns a Readable stream.
67 |
68 | The second difference is that there is no longer a hash that is returned from `renderToString` and has to be read into the page.
69 |
70 | The third difference is that you no longer need to use `react-dom-stream` to perform the client-side render. Using vanilla `ReactDOM.render` will work just fine.
71 |
72 | So, if your `renderToString` code looks like this in v0.1.x:
73 |
74 | ```javascript
75 | var ReactDOMStream = require("react-dom-stream/server");
76 |
77 | app.get('/', function (req, res) {
78 | // SNIP: write out HTML before the React-rendered piece
79 | ReactDOMStream.renderToString(
, res)
80 | .then(function(hash) {
81 | // SNIP: write the hash out to the page in a script tag
82 | // SNIP: write out more HTML after the React-rendered piece.
83 | res.end();
84 | });
85 | });
86 | ```
87 |
88 | Then it should look like this in v0.2.x:
89 |
90 | ```javascript
91 | var ReactDOMStream = require("react-dom-stream/server");
92 |
93 | app.get('/', function (req, res) {
94 | // SNIP: write out HTML before the React-rendered piece
95 | var stream = ReactDOMStream.renderToString();
96 | stream.pipe(res, {end: false});
97 | stream.on("end", function() {
98 | // SNIP: write out more HTML after the React-rendered piece.
99 | res.end();
100 | });
101 | });
102 | ```
103 |
104 | Or, if you are using `renderToStaticMarkup`, and it looked like this in v0.1.x:
105 |
106 | ```javascript
107 | var ReactDOMStream = require("react-dom-stream/server");
108 |
109 | app.get('/', function (req, res) {
110 | ReactDOMStream.renderToStaticMarkup(, res)
111 | .then(function() {
112 | res.end();
113 | });
114 | });
115 | ```
116 |
117 | It should look like this in v0.2.x:
118 |
119 | ```javascript
120 | var ReactDOMStream = require("react-dom-stream/server");
121 |
122 | app.get('/', function (req, res) {
123 | ReactDOMStream.renderToStaticMarkup().pipe(res);
124 | });
125 | ```
126 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # So you think you'd like to contribute to react-dom-stream?
2 |
3 | Awesome. This is a quick guide to give you a sense of how to set up a build environment and contribute, if you feel like it.
4 |
5 | ## Quick guide: how do I get the build running?
6 |
7 | You need to have node, git, & npm installed. I've only tested this with node 4.2.1.
8 |
9 | ```
10 | git clone https://github.com/aickin/react.git
11 | git clone https://github.com/aickin/react-dom-stream.git
12 | cd react
13 | git checkout streaming-render-0.2
14 | npm install
15 | cd ../react-dom-stream
16 | npm install
17 | ```
18 |
19 | You are now all set; you should have a working build.
20 |
21 | If you want to run the test suite:
22 | ```
23 | npm test # from the react-dom-stream directory
24 | ```
25 |
26 | If you want to run the build after changing code:
27 | ```
28 | npm run build # from the react-dom-stream directory
29 | ```
30 |
31 | ## Where's the `lib` and `dist` directory?
32 |
33 | The first thing you'll notice when you start poking around is that the project depends on `lib/ReactDOMServer`, but there's no lib directory checked in to the `react-dom-stream` repo. What gives?
34 |
35 | The code for server side rendering is actually in a branch in my fork of react, over at . The install instructions will download that repo at `./react`.
36 |
37 | If you want to modify the server side rendering code, you will need to modify the code under `./react/src` and then call `npm run build` **from the react-dom-stream directory**. That will run the react build and copy the results over to `./react-dom-stream/lib` and `./react-dom-stream/dist`. **Do not edit the files under lib or dist; they will not be committed to git**.
38 |
39 | ## What code should I look at?
40 |
41 | To get a sense for what code has been forked from React 0.14, check out [this commit](https://github.com/aickin/react/commit/9159656c53c0335ec6bd56fc7537231a9abeb5d5); it contains most of the interesting changes from upstream.
42 |
43 | ## Testing
44 |
45 | The tests for server-side renders are all in `./react/src/renderers/dom/server/__tests__/ReactServerAsyncRendering-test.js`, which began life as a copy of `ReactServerRendering-test.js`. If you want to add some rendering tests, that'd be awesome.
46 |
47 | To run the tests, call `npm test` **from the react-dom-stream directory**.
48 |
49 | ## Pull Requests
50 |
51 | I am happy to accept pull requests to either project! Please make sure that the tests pass before submitting.
52 |
53 | I do plan to require a CLA for contributors to keep the IP rights clean, but it shouldn't be too onerous, I hope. I'll update with details as I figure them out.
54 |
55 | ## Code of Conduct
56 |
57 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
58 | [code-of-conduct]: http://todogroup.org/opencodeofconduct/#react-dom-stream/xander76@yahoo.com
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-dom-stream
2 |
3 | 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.
4 |
5 | ## Why?
6 |
7 | 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:
8 |
9 | 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.
10 | 2. The server has to allocate memory for the entire HTML string.
11 | 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.
12 |
13 |
14 | This project attempts to fix these three problems by rendering asynchronously to a stream.
15 |
16 | 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](http://www.stevesouders.com/blog/2013/01/31/http-archive-adding-flush/) and [Flushing the Document Early](http://www.stevesouders.com/blog/2009/05/18/flushing-the-document-early/).
17 |
18 | 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](https://github.com/aickin/react-dom-stream-example) repo.
19 |
20 | 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:
21 |
22 |
23 |
24 | ## How?
25 |
26 | First, install `react-dom-stream` into your project:
27 |
28 | ```
29 | npm install --save react-dom-stream react-dom react
30 | ```
31 |
32 | 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`.
33 |
34 | ### Rendering on the server
35 |
36 | To use either of the server-side methods, you need to require `react-dom-stream/server`.
37 |
38 | #### `ReadableStream renderToString(ReactElement element, [Object options])`
39 |
40 | 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):
41 |
42 | ```javascript
43 | import ReactDOMStream from "react-dom-stream/server";
44 |
45 | app.get('/', (req, res) => {
46 | // TODO: write out the html, head, and body tags
47 | var stream = ReactDOMStream.renderToString();
48 | stream.pipe(res, {end: false});
49 | stream.on("end", function() {
50 | // TODO: write out the rest of the page, including the closing body and html tags.
51 | res.end();
52 | });
53 | });
54 | ```
55 |
56 | Or, if you'd like a more terse (but IMO slightly harder to understand) version:
57 |
58 | ```javascript
59 | import ReactDOMStream from "react-dom-stream/server";
60 |
61 | app.get('/', (req, res) => {
62 | // TODO: write out the html, head, and body tags
63 | ReactDOMStream.renderToString().on("end", () =>{
64 | // TODO: write out the rest of the page, including the closing body and html tags.
65 | res.end();
66 | }).pipe(res, {end:false});
67 | });
68 | ```
69 |
70 | If the piping syntax is not to your liking, check out the section below on combining `renderToString` and `renderToStaticMarkup` to render a full page.
71 |
72 | #### `ReadableStream renderToStaticMarkup(ReactElement element, [Object options])`
73 |
74 | 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`.
75 |
76 | In an Express app, it is used like this:
77 |
78 | ```javascript
79 | import ReactDOMStream from "react-dom-stream/server";
80 |
81 | app.get('/', (req, res) => {
82 | ReactDOMStream.renderToStaticMarkup().pipe(res);
83 | });
84 | ```
85 |
86 | 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.
87 |
88 | ### Combining `renderToStaticMarkup` and `renderToString` to serve a page
89 |
90 | 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](https://medium.com/@dan_abramov/two-weird-tricks-that-fix-react-7cf9bbdef375#.7tqgmc4pj). As a result, with vanilla `react-dom/server` many developers use `renderToStaticMarkup` to generate the ``, ``, and `` tags, and then embed the output of `renderToString` into a div inside the body.
91 |
92 | 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:
93 |
94 | ```javascript
95 | import ReactDOMStream from "react-dom-stream/server";
96 |
97 | app.get("/", (req, res) => {
98 | // use renderToStaticMarkup to generate the entire HTML markup, embedding the
99 | // dynamic part under the renderDiv div.
100 | ReactDOMStream.renderToStaticMarkup(
101 |
102 |
103 |
104 | My Cool Page
105 |
106 |
107 | )}}>
108 |
112 |
113 |
114 | ).pipe(res);
115 | });
116 | ```
117 |
118 | If you don't like using `dangerouslySetInnerHTML`, consider using my companion project [`react-raw-html`](https://github.com/aickin/react-raw-html), which doesn't encode children that are Strings or Streams. The previous example would then look like this:
119 |
120 | ```javascript
121 | import ReactDOMStream from "react-dom-stream/server";
122 | import Raw from "react-raw-html";
123 |
124 | app.get("/", (req, res) => {
125 | // use renderToStaticMarkup to generate the entire HTML markup, embedding the
126 | // dynamic part under the renderDiv div.
127 | ReactDOMStream.renderToStaticMarkup(
128 |
129 |
130 |
131 | My Cool Page
132 |
133 |
134 |
135 | {ReactDOMStream.renderToString()}
136 |
137 |
141 |
142 |
143 | ).pipe(res);
144 | });
145 | ```
146 |
147 | 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`.
148 |
149 | ### Experimental feature: Component Caching
150 |
151 | 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.
152 |
153 | 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:
154 |
155 | ```javascript
156 | import ReactDOMStream from "react-dom-stream/server";
157 | import LRURenderCache from "react-dom-stream/lru-render-cache";
158 |
159 | // create a cache that stores 64MB of text.
160 | const myCache = LRURenderCache({max: 64 * 1024 * 1024});
161 |
162 | app.get('/', (req, res) => {
163 | ReactDOMStream.renderToStaticMarkup(, {cache: myCache}).pipe(res);
164 | });
165 | ```
166 |
167 | 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:
168 |
169 | ```javascript
170 | import React from "react"
171 |
172 | export default class CacheableComponent extends React.Component {
173 | render() {
174 | return Hello, ${this.props.name}!;
175 | }
176 |
177 | componentCacheKey() {
178 | // the name prop completely specifies what the rendering will look like.
179 | return this.props.name;
180 | }
181 | }
182 | ```
183 |
184 | Here is an **incorrect** implementation:
185 |
186 | ```javascript
187 | import React from "react"
188 |
189 | export default class BADCacheableComponent extends React.Component {
190 | render() {
191 | return Hello, ${this.props.name}! It is ${new Date()};
192 | }
193 |
194 | // INCORRECT!!
195 | componentCacheKey() {
196 | // the name prop does NOT completely specify the render,
197 | // so this implementation is WRONG.
198 | return this.props.name;
199 | }
200 | }
201 | ```
202 |
203 | 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.
204 |
205 | 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.
206 |
207 | Also, note that the APIs of this feature are liable to change.
208 |
209 | In the future, I hope to add features to caching such as:
210 |
211 | * the ability to write a custom cache implementation.
212 | * comprehensive statistics of cache efficiency (hit & miss rate, time spent looking up entries in the cache, total time saved/spent by the cache, etc.).
213 | * 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.
214 |
215 | Feel free to file issues asking for features you would find interesting.
216 |
217 | ### Reconnecting the markup on the client
218 |
219 | 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.
220 |
221 | ## When should you use `react-dom-stream`?
222 |
223 | 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.
224 |
225 | 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.
226 |
227 | 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.
228 |
229 | ## Who?
230 |
231 | `react-dom-stream` is written by Sasha Aickin ([@xander76](https://twitter.com/xander76)), though let's be real, about 99% of the code is forked from Facebook's React.
232 |
233 | ## Status
234 |
235 | 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.
236 |
237 | This module is forked from Facebook's React project. All extra code and modifications are offered under the Apache 2.0 license.
238 |
239 | ## Something's wrong!
240 |
241 | Please feel free to file any issues at . Thanks!
242 |
243 | ## Upgrading from previous versions
244 |
245 | There were major breaking API changes in v0.2 and v0.4. To learn how to upgrade your client code, please read [CHANGELOG.md](/CHANGELOG.md).
246 |
247 | ## Wait, where's the code?
248 |
249 | Well, this is awkward.
250 |
251 | 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 , which is a fork of React. Specifically, check out [this commit](https://github.com/aickin/react/commit/9159656c53c0335ec6bd56fc7537231a9abeb5d5) to see most of the interesting changes from React 0.14.
252 |
253 |
254 | ## I'd like to contribute!
255 |
256 | Awesome. You are the coolest.
257 |
258 | To get a working build running and learn a little about how the project is set up, please read [CONTRIBUTING.md](CONTRIBUTING.md).
259 |
260 | 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.
261 |
262 | Please note that this project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
263 | [code-of-conduct]: http://todogroup.org/opencodeofconduct/#react-dom-stream/xander76@yahoo.com
264 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | To do:
2 |
3 | * Change the API to return a Stream with a hash Promise property. (issue #2)
4 | ** Test whether streams need to be written to asynchronously to avoid buffering.
5 | * Support streams and Promises as first-class citizens in `renderToStaticMarkup`. (idea in issue #2)
6 | * Determine best practices for using `compression`.
7 | ** Test gzip compression with the ZLIB streaming argument. (issue #5)
8 | ** Test yielding to the event loop as a way to get compression to stream out.
9 | ** Figure out how to rationalize `bufferSize` and `compression`.
10 | * Write some more tests
11 | ** rendering functional components
12 | ** writing out script tags with `renderToStaticMarkup`
13 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var ReactDOM = require("react-dom");
2 |
3 | module.exports = {
4 | render: function (reactElement, domElement, hash, callback) {
5 | if (!domElement) throw new Error("domElement is a required argument to render(). You passed in " + domElement);
6 | if (!domElement.childNodes) throw new Error("domElement must have a childNodes attribute. Did you pass in something other than a DOM node?");
7 | if (domElement.childNodes.length !== 1) throw new Error("domElement must have one and only one child. Did you add some extra nodes in the server rendering process?");
8 | if (!domElement.setAttribute) throw new Error("domElement must have a setAttribute() method. Did you pass in something other than a DOM element?");
9 | if (typeof hash === "undefined") throw new Error("hash is a required argument to render().");
10 |
11 | var renderRoot = domElement.childNodes[0];
12 | renderRoot.setAttribute("data-react-checksum", hash);
13 | return ReactDOM.render(reactElement, domElement, callback);
14 | }
15 | }
--------------------------------------------------------------------------------
/lru-render-cache.js:
--------------------------------------------------------------------------------
1 | module.exports = require("./build/lru-render-cache");
--------------------------------------------------------------------------------
/modules/lru-render-cache.js:
--------------------------------------------------------------------------------
1 | import LRU from "lru-cache"
2 |
3 | class LRURenderCache {
4 | constructor(options = {}) {
5 | if (Number.isInteger(options)) {
6 | options = {
7 | max:options
8 | };
9 | }
10 |
11 | let originalDispose = options.dispose;
12 |
13 | this.lruCache = LRU({
14 | max: options.max || 128 * 1024 * 1024,
15 |
16 | length: (value, key) => {
17 | return value.value.length + (value.key.length * 2);
18 | },
19 |
20 | dispose: (key, value) => {
21 | const componentMap = this._getComponentMap(value.component);
22 | componentMap.delete(value.key);
23 | if (componentMap.size === 0) {
24 | this.map.delete(value.component);
25 | }
26 | originalDispose(key, value);
27 | },
28 | });
29 |
30 | this.map = new Map();
31 | }
32 |
33 | get(component, key) {
34 | const lruCacheKey = this._getComponentMap(component).get(key);
35 | const storedValue = this.lruCache.get(lruCacheKey);
36 | if (typeof storedValue === "object") {
37 | return storedValue.value;
38 | }
39 | return undefined;
40 | }
41 |
42 | set(component, key, value) {
43 | const lruCacheKey = {};
44 | this._getComponentMap(component).set(key, lruCacheKey);
45 | this.lruCache.set(lruCacheKey, {component, key, value});
46 | }
47 |
48 | _getComponentMap(component) {
49 | var componentMap = this.map.get(component);
50 |
51 | if (!componentMap) {
52 | componentMap = new Map();
53 | this.map.set(component, componentMap);
54 | }
55 |
56 | return componentMap;
57 | }
58 | }
59 |
60 | export default options => new LRURenderCache(options);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dom-stream",
3 | "version": "0.5.1",
4 | "description": "A streaming server-side rendering library for React.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "(cd ../react && grunt test)",
8 | "build": "(cd ../react && grunt build) && ./node_modules/babel-cli/bin/babel.js modules --out-dir build",
9 | "postbuild": "rm -rf lib && cp -R ../react/build/packages/react/lib lib && rm -rf dist && cp -R ../react/build/packages/react/dist dist",
10 | "prepublish": "npm run build"
11 | },
12 | "author": "Sasha Aickin",
13 | "license": "Apache-2.0",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/aickin/react-dom-stream.git"
17 | },
18 | "homepage": "https://github.com/aickin/react-dom-stream",
19 | "engines": {
20 | "node": ">=4.0.0"
21 | },
22 | "dependencies": {
23 | "envify": "^3.0.0",
24 | "fbjs": "^0.3.1",
25 | "lru-cache": "^4.0.0"
26 | },
27 | "browserify": {
28 | "transform": [
29 | "envify"
30 | ]
31 | },
32 | "babel": {
33 | "presets": [
34 | "es2015",
35 | "react"
36 | ]
37 | },
38 | "devDependencies": {
39 | "babel-cli": "^6.4.0",
40 | "babel-preset-es2015": "^6.3.13",
41 | "babel-preset-react": "^6.3.13"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // when in production mode, use the minified version; it's much, much faster.
2 | var ReactDOMStream;
3 | if (process.env.NODE_ENV === "production") {
4 | ReactDOMStream = require("./dist/react.min");
5 | module.exports = {
6 | renderToString: ReactDOMStream.renderToString,
7 | renderToStaticMarkup: ReactDOMStream.renderToStaticMarkup
8 | };
9 |
10 | } else {
11 | ReactDOMStream = require("./lib/ReactDOMServer");
12 |
13 | module.exports = {
14 | renderToString: ReactDOMStream.renderToStringStream,
15 | renderToStaticMarkup: ReactDOMStream.renderToStaticMarkupStream
16 | };
17 | }
18 |
--------------------------------------------------------------------------------