Skip to content

Commit 6387c1d

Browse files
authored
Merge pull request #40 from adifiore/master
Modular source
2 parents 5eadc2b + 2adfc64 commit 6387c1d

33 files changed

+3807
-3590
lines changed

index.js

-3,587
This file was deleted.

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
{
22
"name": "react-component-caching",
33
"version": "1.1.0",
4-
"description":
5-
"React Component Caching is a component-level caching library for faster server-side rendering with React 16",
4+
"description": "React Component Caching is a component-level caching library for faster server-side rendering with React 16",
65
"main": "index.js",
76
"scripts": {
8-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"webpack": "./node_modules/.bin/webpack -w"
98
},
109
"keywords": [
1110
"ssr",
@@ -24,5 +23,8 @@
2423
"object-assign": "^4.1.1",
2524
"prop-types": "^15.6.1",
2625
"react": "^16.2.0"
26+
},
27+
"devDependencies": {
28+
"webpack": "^3.5.5"
2729
}
2830
}

src/server/ComponentCache.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import lru from 'lru-cache';
2+
3+
export default class ComponentCache {
4+
constructor(config = {}) {
5+
6+
if (Number.isInteger(config)) {
7+
config = {
8+
max:config,
9+
};
10+
}
11+
12+
this.storage = lru({
13+
max: config.max || 1000000000,
14+
length: (n, key) => {
15+
return n.length + key.length;
16+
},
17+
});
18+
}
19+
20+
get(cacheKey, cb) {
21+
let reply = this.storage.get(cacheKey);
22+
cb(null, reply);
23+
}
24+
25+
set(cacheKey, html) {
26+
this.storage.set(cacheKey, html);
27+
}
28+
}

src/server/DOMMarkupOperations.js

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
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+
import {
9+
ATTRIBUTE_NAME_CHAR,
10+
ATTRIBUTE_NAME_START_CHAR,
11+
ID_ATTRIBUTE_NAME,
12+
ROOT_ATTRIBUTE_NAME,
13+
getPropertyInfo,
14+
shouldAttributeAcceptBooleanValue,
15+
shouldSetAttribute,
16+
} from '../shared/DOMProperty';
17+
import quoteAttributeValueForBrowser from './quoteAttributeValueForBrowser';
18+
import warning from 'fbjs/lib/warning';
19+
20+
// isAttributeNameSafe() is currently duplicated in DOMPropertyOperations.
21+
// TODO: Find a better place for this.
22+
var VALID_ATTRIBUTE_NAME_REGEX = new RegExp(
23+
'^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$',
24+
);
25+
var illegalAttributeNameCache = {};
26+
var validatedAttributeNameCache = {};
27+
function isAttributeNameSafe(attributeName) {
28+
if (validatedAttributeNameCache.hasOwnProperty(attributeName)) {
29+
return true;
30+
}
31+
if (illegalAttributeNameCache.hasOwnProperty(attributeName)) {
32+
return false;
33+
}
34+
if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
35+
validatedAttributeNameCache[attributeName] = true;
36+
return true;
37+
}
38+
illegalAttributeNameCache[attributeName] = true;
39+
if (__DEV__) {
40+
warning(false, 'Invalid attribute name: `%s`', attributeName);
41+
}
42+
return false;
43+
}
44+
45+
// shouldIgnoreValue() is currently duplicated in DOMPropertyOperations.
46+
// TODO: Find a better place for this.
47+
function shouldIgnoreValue(propertyInfo, value) {
48+
return (
49+
value == null ||
50+
(propertyInfo.hasBooleanValue && !value) ||
51+
(propertyInfo.hasNumericValue && isNaN(value)) ||
52+
(propertyInfo.hasPositiveNumericValue && value < 1) ||
53+
(propertyInfo.hasOverloadedBooleanValue && value === false)
54+
);
55+
}
56+
57+
/**
58+
* Operations for dealing with DOM properties.
59+
*/
60+
61+
/**
62+
* Creates markup for the ID property.
63+
*
64+
* @param {string} id Unescaped ID.
65+
* @return {string} Markup string.
66+
*/
67+
export function createMarkupForID(id) {
68+
return ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id);
69+
}
70+
71+
export function createMarkupForRoot() {
72+
return ROOT_ATTRIBUTE_NAME + '=""';
73+
}
74+
75+
/**
76+
* Creates markup for a property.
77+
*
78+
* @param {string} name
79+
* @param {*} value
80+
* @return {?string} Markup string, or null if the property was invalid.
81+
*/
82+
export function createMarkupForProperty(name, value) {
83+
var propertyInfo = getPropertyInfo(name);
84+
if (propertyInfo) {
85+
if (shouldIgnoreValue(propertyInfo, value)) {
86+
return '';
87+
}
88+
var attributeName = propertyInfo.attributeName;
89+
if (
90+
propertyInfo.hasBooleanValue ||
91+
(propertyInfo.hasOverloadedBooleanValue && value === true)
92+
) {
93+
return attributeName + '=""';
94+
} else if (
95+
typeof value !== 'boolean' ||
96+
shouldAttributeAcceptBooleanValue(name)
97+
) {
98+
return attributeName + '=' + quoteAttributeValueForBrowser(value);
99+
}
100+
} else if (shouldSetAttribute(name, value)) {
101+
if (value == null) {
102+
return '';
103+
}
104+
return name + '=' + quoteAttributeValueForBrowser(value);
105+
}
106+
return null;
107+
}
108+
109+
/**
110+
* Creates markup for a custom property.
111+
*
112+
* @param {string} name
113+
* @param {*} value
114+
* @return {string} Markup string, or empty string if the property was invalid.
115+
*/
116+
export function createMarkupForCustomAttribute(name, value) {
117+
if (!isAttributeNameSafe(name) || value == null) {
118+
return '';
119+
}
120+
return name + '=' + quoteAttributeValueForBrowser(value);
121+
}
+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
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+
import {Readable} from 'stream';
9+
import { Transform } from 'stream';
10+
11+
import ReactPartialRenderer from './ReactPartialRenderer';
12+
13+
// This is a Readable Node.js stream which wraps the ReactDOMPartialRenderer.
14+
class ReactMarkupReadableStream extends Readable {
15+
constructor(element, makeStaticMarkup, cache, streamingStart, memLife) {
16+
// Calls the stream.Readable(options) constructor. Consider exposing built-in
17+
// features like highWaterMark in the future.
18+
super({});
19+
this.cache = cache;
20+
this.streamingStart = streamingStart;
21+
this.memLife = memLife;
22+
this.partialRenderer = new ReactPartialRenderer(element, makeStaticMarkup);
23+
}
24+
25+
async _read(size) {
26+
try {
27+
let readOutput = await this.partialRenderer.read(size,
28+
this.cache,
29+
true,
30+
this.streamingStart,
31+
this.memLife);
32+
this.push(readOutput);
33+
} catch (err) {
34+
this.emit('error', err);
35+
}
36+
}
37+
}
38+
39+
function createCacheStream(cache, streamingStart, memLife = 0) {
40+
const bufferedChunks = [];
41+
return new Transform({
42+
// transform() is called with each chunk of data
43+
transform(data, enc, cb) {
44+
// We store the chunk of data (which is a Buffer) in memory
45+
bufferedChunks.push(data);
46+
// Then pass the data unchanged onwards to the next stream
47+
cb(null, data);
48+
},
49+
50+
// flush() is called when everything is done
51+
flush(cb) {
52+
// We concatenate all the buffered chunks of HTML to get the full HTML, then cache it at "key"
53+
let html = bufferedChunks.join("");
54+
delete streamingStart.sliceStartCount;
55+
56+
for (let component in streamingStart) {
57+
let tagStack = [];
58+
let tagStart;
59+
let tagEnd;
60+
61+
do {
62+
if (!tagStart) {
63+
tagStart = streamingStart[component];
64+
} else {
65+
tagStart = (html[tagEnd] === '<') ? tagEnd : html.indexOf('<', tagEnd);
66+
}
67+
tagEnd = html.indexOf('>', tagStart) + 1;
68+
// Skip stack logic for void/self-closing elements and HTML comments
69+
if (html[tagEnd - 2] !== '/' && html[tagStart + 1] !== '!') {
70+
// Push opening tags onto stack; pop closing tags off of stack
71+
if (html[tagStart + 1] !== '/') {
72+
tagStack.push(html.slice(tagStart, tagEnd));
73+
} else {
74+
tagStack.pop();
75+
}
76+
}
77+
} while (tagStack.length !== 0);
78+
// cache component by slicing 'html'
79+
if (memLife) {
80+
cache.set(component, html.slice(streamingStart[component], tagEnd), memLife, (err) => {
81+
if (err) {
82+
console.log(err);
83+
}
84+
});
85+
} else {
86+
cache.set(component, html.slice(streamingStart[component], tagEnd));
87+
}
88+
}
89+
cb();
90+
},
91+
});
92+
}
93+
/**
94+
* Render a ReactElement to its initial HTML. This should only be used on the
95+
* server.
96+
* See https://reactjs.org/docs/react-dom-stream.html#rendertonodestream
97+
*/
98+
function originalRenderToNodeStream(element, cache, streamingStart, memLife=0) {
99+
return new ReactMarkupReadableStream(element, false, cache, streamingStart, memLife);
100+
}
101+
102+
export function renderToNodeStream(element, cache, res) {
103+
104+
const htmlStart =
105+
'<html><head><title>Page</title></head><body><div id="react-root">';
106+
107+
const htmlEnd = '</div></body></html>';
108+
109+
const streamingStart = {
110+
sliceStartCount: htmlStart.length,
111+
};
112+
113+
const cacheStream = createCacheStream(cache, streamingStart);
114+
cacheStream.pipe(res);
115+
cacheStream.write(htmlStart);
116+
117+
const stream = originalRenderToNodeStream(element, cache, streamingStart);
118+
stream.pipe(cacheStream, { end: false });
119+
stream.on("end", () => {
120+
cacheStream.end(htmlEnd);
121+
});
122+
123+
}
124+
125+
/**
126+
* Similar to renderToNodeStream, except this doesn't create extra DOM attributes
127+
* such as data-react-id that React uses internally.
128+
* See https://reactjs.org/docs/react-dom-stream.html#rendertostaticnodestream
129+
*/
130+
function originalRenderToStaticNodeStream(element, cache, streamingStart, memLife=0) {
131+
return new ReactMarkupReadableStream(element, true, cache, streamingStart, memLife);
132+
}
133+
134+
export function renderToStaticNodeStream(element, cache, res) {
135+
const htmlStart =
136+
'<html><head><title>Page</title></head><body><div id="react-root">';
137+
138+
const htmlEnd = '</div></body></html>';
139+
140+
const streamingStart = {
141+
sliceStartCount: htmlStart.length,
142+
};
143+
144+
const cacheStream = createCacheStream(cache, streamingStart);
145+
cacheStream.pipe(res);
146+
cacheStream.write(htmlStart);
147+
148+
const stream = originalRenderToStaticNodeStream(element, cache, streamingStart);
149+
stream.pipe(cacheStream, { end: false });
150+
stream.on("end", () => {
151+
cacheStream.end(htmlEnd);
152+
});
153+
}

src/server/ReactDOMServerBrowser.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
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+
import '../shared/ReactDOMInjection';
9+
import ReactVersion from 'shared/ReactVersion';
10+
import invariant from 'fbjs/lib/invariant';
11+
12+
import {renderToString, renderToStaticMarkup} from './ReactDOMStringRenderer';
13+
14+
function renderToNodeStream() {
15+
invariant(
16+
false,
17+
'ReactDOMServer.renderToNodeStream(): The streaming API is not available ' +
18+
'in the browser. Use ReactDOMServer.renderToString() instead.',
19+
);
20+
}
21+
22+
function renderToStaticNodeStream() {
23+
invariant(
24+
false,
25+
'ReactDOMServer.renderToStaticNodeStream(): The streaming API is not available ' +
26+
'in the browser. Use ReactDOMServer.renderToStaticMarkup() instead.',
27+
);
28+
}
29+
30+
// Note: when changing this, also consider https://github.com/facebook/react/issues/11526
31+
export default {
32+
renderToString,
33+
renderToStaticMarkup,
34+
renderToNodeStream,
35+
renderToStaticNodeStream,
36+
version: ReactVersion,
37+
};

src/server/ReactDOMServerNode.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
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+
import '../shared/ReactDOMInjection';
9+
import ReactVersion from 'shared/ReactVersion';
10+
11+
import {renderToString, renderToStaticMarkup} from './ReactDOMStringRenderer';
12+
import {
13+
renderToNodeStream,
14+
renderToStaticNodeStream,
15+
} from './ReactDOMNodeStreamRenderer';
16+
17+
import ComponentCache from './ComponentCache';
18+
19+
20+
// Note: when changing this, also consider https://github.com/facebook/react/issues/11526
21+
export default {
22+
renderToString,
23+
renderToStaticMarkup,
24+
renderToNodeStream,
25+
renderToStaticNodeStream,
26+
ComponentCache,
27+
version: ReactVersion,
28+
};

0 commit comments

Comments
 (0)