Skip to content

Commit bd1b774

Browse files
authored
Merge pull request #26 from kodakyellow/nodeStreaming
inserted renderToNodeStream code
2 parents 085f964 + 64785f2 commit bd1b774

File tree

5 files changed

+158
-48
lines changed

5 files changed

+158
-48
lines changed

SSRtest/ModifiedReact.js

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,7 +2204,8 @@ var ReactDOMServerRenderer = function () {
22042204
// TODO: type this more strictly:
22052205

22062206

2207-
ReactDOMServerRenderer.prototype.read = function read(bytes, cache) {
2207+
ReactDOMServerRenderer.prototype.read = function read(bytes, cache, isStreaming,
2208+
streamingStart) {
22082209
/*
22092210
--- Component caching variables ---
22102211
start: Tracks start index in output string and templatization data for cached components
@@ -2304,7 +2305,13 @@ var ReactDOMServerRenderer = function () {
23042305
else r = this.render(child, frame.context, frame.domNamespace);
23052306

23062307
// For simple (non-template) caching, save start index of component in output string
2307-
if (!isTemplate) start[cacheKey] = out.length;
2308+
if (!isTemplate) {
2309+
if (isStreaming) {
2310+
// streamingStart[cacheKey] = out.length;
2311+
streamingStart[cacheKey] = streamingStart.sliceStartCount + out.length;
2312+
console.log("finalcount", streamingStart.finalSliceStart);
2313+
} else start[cacheKey] = out.length;
2314+
}
23082315
} else { // Component found in cache
23092316
if (isTemplate) {
23102317
restoredTemplate = restoreProps(reply, realProps, lookup);
@@ -2326,47 +2333,53 @@ var ReactDOMServerRenderer = function () {
23262333
/*
23272334
--- After initial render of cacheable components, recover from output string and store in cache ---
23282335
*/
2329-
for (let component in start) {
2330-
let tagStack = [];
2331-
let tagStart;
2332-
let tagEnd;
2333-
let componentStart = (typeof start[component] === 'object') ? start[component].startIndex : start[component];
2334-
2335-
do {
2336-
if (!tagStart) tagStart = componentStart;
2337-
else tagStart = (out[tagEnd] === '<') ? tagEnd : out.indexOf('<', tagEnd);
2338-
tagEnd = out.indexOf('>', tagStart) + 1;
2339-
// Skip stack logic for void/self-closing elements and HTML comments
2340-
// Note: Does not account for tags inside HTML comments
2341-
if (out[tagEnd - 2] !== '/' && out[tagStart + 1] !== '!') {
2342-
// Push opening tags onto stack; pop closing tags off of stack
2343-
if (out[tagStart + 1] !== '/') tagStack.push(out.slice(tagStart, tagEnd));
2344-
else tagStack.pop();
2336+
if (!isStreaming) {
2337+
for (let component in start) {
2338+
let tagStack = [];
2339+
let tagStart;
2340+
let tagEnd;
2341+
let componentStart = (typeof start[component] === 'object') ? start[component].startIndex : start[component];
2342+
2343+
do {
2344+
if (!tagStart) tagStart = componentStart;
2345+
else tagStart = (out[tagEnd] === '<') ? tagEnd : out.indexOf('<', tagEnd);
2346+
tagEnd = out.indexOf('>', tagStart) + 1;
2347+
// Skip stack logic for void/self-closing elements and HTML comments
2348+
// Note: Does not account for tags inside HTML comments
2349+
if (out[tagEnd - 2] !== '/' && out[tagStart + 1] !== '!') {
2350+
// Push opening tags onto stack; pop closing tags off of stack
2351+
if (out[tagStart + 1] !== '/') tagStack.push(out.slice(tagStart, tagEnd));
2352+
else tagStack.pop();
2353+
}
2354+
} while (tagStack.length !== 0);
2355+
// Cache component by slicing 'out'
2356+
const cachedComponent = out.slice(componentStart, tagEnd);
2357+
if (typeof start[component] === 'object') {
2358+
saveTemplates.push(start[component]);
2359+
start[component].endIndex = tagEnd;
23452360
}
2346-
} while (tagStack.length !== 0);
2347-
// Cache component by slicing 'out'
2348-
const cachedComponent = out.slice(componentStart, tagEnd);
2349-
if (typeof start[component] === 'object') {
2350-
saveTemplates.push(start[component]);
2351-
start[component].endIndex = tagEnd;
2361+
cache.set(component, cachedComponent);
23522362
}
2353-
cache.set(component, cachedComponent);
2354-
}
23552363

2356-
// After caching all cacheable components, restore props to templates
2357-
if (saveTemplates) {
2358-
let outCopy = out;
2359-
out = '';
2360-
let bookmark = 0;
2361-
saveTemplates.sort((a, b) => a.startIndex - b.startIndex);
2362-
// Rebuild output string with actual props
2363-
saveTemplates.forEach(savedTemplate => {
2364-
out += outCopy.substring(bookmark, savedTemplate.startIndex);
2365-
bookmark = savedTemplate.endIndex;
2366-
out += restoreProps(outCopy.slice(savedTemplate.startIndex, savedTemplate.endIndex),
2367-
savedTemplate.realProps, savedTemplate.lookup);
2368-
});
2369-
out += outCopy.substring(bookmark, outCopy.length);
2364+
// After caching all cacheable components, restore props to templates
2365+
if (saveTemplates) {
2366+
let outCopy = out;
2367+
out = '';
2368+
let bookmark = 0;
2369+
saveTemplates.sort((a, b) => a.startIndex - b.startIndex);
2370+
// Rebuild output string with actual props
2371+
saveTemplates.forEach(savedTemplate => {
2372+
out += outCopy.substring(bookmark, savedTemplate.startIndex);
2373+
bookmark = savedTemplate.endIndex;
2374+
out += restoreProps(outCopy.slice(savedTemplate.startIndex, savedTemplate.endIndex),
2375+
savedTemplate.realProps, savedTemplate.lookup);
2376+
});
2377+
out += outCopy.substring(bookmark, outCopy.length);
2378+
}
2379+
} else {
2380+
// console.log(out.length, streamingStart);
2381+
streamingStart.sliceStartCount += out.length;
2382+
console.log("rolling count", streamingStart["sliceStartCount"]);
23702383
}
23712384
return out;
23722385
};
@@ -2653,21 +2666,29 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function"
26532666
var ReactMarkupReadableStream = function (_Readable) {
26542667
_inherits(ReactMarkupReadableStream, _Readable);
26552668

2656-
function ReactMarkupReadableStream(element, makeStaticMarkup) {
2669+
function ReactMarkupReadableStream(element, makeStaticMarkup, cache, streamingStart) {
26572670
_classCallCheck$1(this, ReactMarkupReadableStream);
26582671

26592672
var _this = _possibleConstructorReturn(this, _Readable.call(this, {}));
26602673
// Calls the stream.Readable(options) constructor. Consider exposing built-in
26612674
// features like highWaterMark in the future.
26622675

2663-
2676+
_this.cache = cache;
2677+
_this.streamingStart = streamingStart;
26642678
_this.partialRenderer = new ReactDOMServerRenderer(element, makeStaticMarkup);
26652679
return _this;
26662680
}
26672681

26682682
ReactMarkupReadableStream.prototype._read = function _read(size) {
26692683
try {
2670-
this.push(this.partialRenderer.read(size));
2684+
this.push(
2685+
this.partialRenderer.read(
2686+
size,
2687+
this.cache,
2688+
true,
2689+
this.streamingStart
2690+
)
2691+
);
26712692
} catch (err) {
26722693
this.emit('error', err);
26732694
}
@@ -2682,8 +2703,13 @@ var ReactMarkupReadableStream = function (_Readable) {
26822703
*/
26832704

26842705

2685-
function renderToNodeStream(element) {
2686-
return new ReactMarkupReadableStream(element, false);
2706+
function renderToNodeStream(element, cache, streamingStart) {
2707+
return new ReactMarkupReadableStream(
2708+
element,
2709+
false,
2710+
cache,
2711+
streamingStart
2712+
);
26872713
}
26882714

26892715
/**

SSRtest/src/server/cacheStream.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Transform } from "stream";
2+
import { create } from "domain";
3+
4+
const createCacheStream = (cache, streamingStart) => {
5+
const bufferedChunks = [];
6+
return new Transform({
7+
// transform() is called with each chunk of data
8+
transform(data, enc, cb) {
9+
// We store the chunk of data (which is a Buffer) in memory
10+
bufferedChunks.push(data);
11+
// Then pass the data unchanged onwards to the next stream
12+
cb(null, data);
13+
},
14+
15+
// flush() is called when everything is done
16+
flush(cb) {
17+
// We concatenate all the buffered chunks of HTML to get the full HTML
18+
// then cache it at "key"
19+
20+
// console.log(cache);
21+
// console.log(bufferedChunks.join());
22+
// cache.set(key, Buffer.concat(bufferedChunks));
23+
// console.log("final", streamingStart.finalSliceStart);
24+
//joinedChunks = bufferedChunks.join("");
25+
let html = bufferedChunks.join("");
26+
delete streamingStart.sliceStartCount;
27+
28+
for (let component in streamingStart) {
29+
let tagStack = [];
30+
let tagStart;
31+
let tagEnd;
32+
33+
do {
34+
if (!tagStart) tagStart = streamingStart[component];
35+
else
36+
tagStart =
37+
html[tagEnd] === "<" ? tagEnd : html.indexOf("<", tagEnd);
38+
tagEnd = html.indexOf(">", tagStart) + 1;
39+
// Skip stack logic for void/self-closing elements
40+
if (html[tagEnd - 2] !== "/") {
41+
// Push opening tags onto stack; pop closing tags off of stack
42+
if (html[tagStart + 1] !== "/")
43+
tagStack.push(html.slice(tagStart, tagEnd));
44+
else tagStack.pop();
45+
}
46+
} while (tagStack.length !== 0);
47+
//console.log(html.slice(streamingStart[component], tagEnd));
48+
// cache component by slicing 'html'
49+
cache.storage.set(component, html.slice(streamingStart[component], tagEnd));
50+
}
51+
//console.log("to be saved:", bufferedChunks.join(""));
52+
//console.log(cache);
53+
cb();
54+
}
55+
});
56+
};
57+
58+
export default createCacheStream;

SSRtest/src/server/index.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,36 @@ import flushChunks from 'webpack-flush-chunks';
77

88
import App from '../shared/App';
99

10+
import createCacheStream from "./cacheStream";
1011
// can pass in max-size, otherwise defaults to 1 million
1112
const cache = new ReactCC.ComponentCache();
13+
// Force NodeStream
14+
15+
const htmlStart =
16+
'<html><head><title>Page</title></head><body><div id="react-root">';
17+
const htmlEnd = "</div></body></html>";
18+
19+
20+
const streamingStart = {
21+
sliceStartCount: htmlStart.length,
22+
};
1223
/**
1324
* @param clientStats Parameter passed by hot server middleware
1425
*/
1526
export default ({ clientStats }) => async (req, res) => {
27+
// Need To Come back To If Statement
28+
if(true){
29+
const cacheStream = createCacheStream(cache, streamingStart);
30+
cacheStream.pipe(res);
31+
cacheStream.write(htmlStart);
32+
33+
const stream = ReactCC.renderToNodeStream(<App />, cache, streamingStart);
34+
stream.pipe(cacheStream, { end: false });
35+
stream.on("end", () => {
36+
cacheStream.end(htmlEnd);
37+
});
38+
}
39+
else{
1640
const app = <App />;
1741
const start_cached = process.hrtime();
1842
// const appString = ReactCC.renderToStaticMarkup(app, cache);
@@ -32,4 +56,6 @@ export default ({ clientStats }) => async (req, res) => {
3256
styles,
3357
cssHash
3458
});
59+
}
60+
3561
};

SSRtest/src/shared/App.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export default class App extends Component {
1818
<div>
1919
<h1>THIS IS AN APP</h1>
2020
<Button cache />
21-
<BlogPost day="Monday" cache templatized="day" />
22-
<BlogPost day="Tuesday" cache templatized="day" />
21+
<BlogPost cache />
22+
<BlogPost cache />
2323

2424
<List />
2525

SSRtest/src/shared/List.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default class List extends Component {
1212
let bunchOfProducts = [];
1313
const templatizedProps = ["name", "description", "price"];
1414
for (let i=0; i<100; i++) {
15-
bunchOfProducts.push(<ProductInfo key={i} name={`Thing ${i}`} description="This product is awesome!" price={i * 10} nonTemplatized="THIS TEXT SHOULD NEVER CHANGE" cache templatized={templatizedProps} />);
15+
bunchOfProducts.push(<ProductInfo key={i} name={`Thing ${i}`} description="This product is awesome!" price={i * 10} nonTemplatized="THIS TEXT SHOULD NEVER CHANGE" cache />);
1616
}
1717
return (
1818
<div>

0 commit comments

Comments
 (0)