Skip to content

Commit 72a9e19

Browse files
authored
Merge pull request #21 from adifiore/redis
Templatize compatibility
2 parents 9523fb6 + dadcf7e commit 72a9e19

File tree

11 files changed

+2960
-72
lines changed

11 files changed

+2960
-72
lines changed

README.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# ReactCC
2+
3+
## Overview
4+
ReactCC is a component-level caching library for rendering React components on the server.
5+
- Use any of React's four server-side rendering methods
6+
- Cache simple components or templates
7+
- Choose from three cache implementations (LRU, Redis, or Memcached)
8+
9+
## Installation
10+
Using npm:
11+
```shell
12+
$ npm install reactcc
13+
```
14+
15+
## Usage
16+
### In Node rendering server:
17+
Instantiate a cache and pass it into any rendering method as a second argument. Wherever you would use ReactDOM.renderToString, use ReactCC.renderToString.
18+
```javascript
19+
const ReactCC = require("reactcc");
20+
const cache = ReactCC.ComponentCache();
21+
ReactCC.renderToString(<App />, cache>)
22+
23+
// ...
24+
```
25+
26+
### In React app:
27+
To flag a component for caching, simply add a 'cache' property to it. To create a cache template, add both 'cache' and 'templatized', along with an array of props to templatize.
28+
29+
```javascript
30+
export default class App extends Component {
31+
render() {
32+
return (
33+
<div>
34+
<ComponentNotToBeCached />
35+
<ComponentToCache cache />
36+
<ComponentToTemplatize cache templatized='props to templatize' />
37+
</div>
38+
);
39+
}
40+
}
41+
// ...
42+
```
43+
44+
## Cache Options
45+
ReactCC provides its own cache implementation as well as support for Redis and Memcached. Simply create your preferred cache and pass it into one of the rendering methods.
46+
47+
**ReactCC LRU Cache Example:**
48+
49+
```javascript
50+
const ReactCC = require("reactcc");
51+
52+
const cache = ReactCC.ComponentCache();
53+
54+
ReactCC.renderToString(<App />, cache);
55+
```
56+
57+
**Redis Example:**
58+
59+
```javascript
60+
const ReactCC = require("reactcc");
61+
const redis = require("redis");
62+
63+
const cache = redis.createClient();
64+
65+
ReactCC.renderToString(<App />, cache);
66+
```
67+
68+
**Memcached Example:**
69+
70+
```javascript
71+
const ReactCC = require("reactcc");
72+
const Memcached = require("memcached");
73+
74+
const cache = new Memcached(server location, options);
75+
76+
// Make sure to pass in the lifetime of the data (in seconds) as a number.
77+
ReactCC.renderToString(<App />, cache, 1000);
78+
```
79+
80+
## Templatizing Cached Components
81+
Insert description and implementation here
82+
83+
## API
84+
85+
### ReactCC
86+
ReactCC gives you access to all four of React 16's server-side rendering methods, as well as additional functionality. ReactCC methods are described below.
87+
88+
### ComponentCache
89+
- `size`: (*Optional*) An integer representing the maximum size (in characters) of the cache. Defaults to 1 million.
90+
91+
**Example:**
92+
```javascript
93+
const cache = new ReactCC.ComponentCache();
94+
```
95+
96+
### renderToString
97+
- `component`: The React component being rendered.
98+
- `cache`: The component cache
99+
- `memLife`: (*Only if using Memcached*) A number representing the lifetime (in seconds) of each Memcached entry. Defaults to 0.
100+
101+
**Example:**
102+
```javascript
103+
ReactCC.renderToString(<App />, cache);
104+
```
105+
106+
### renderToStaticMarkup
107+
- `component`: The React component being rendered.
108+
- `cache`: The component cache
109+
- `memLife`: (*Only if using Memcached*) An integer representing the lifetime (in seconds) of each Memcached entry. Defaults to 0.
110+
111+
**Example:**
112+
```javascript
113+
ReactCC.renderToStaticMarkup(<App />, cache);
114+
```
115+
116+
### renderToNodeStream
117+
- `component`: The React component being rendered.
118+
- `cache`: The component cache
119+
- `memLife`: (*Only if using Memcached*) An integer representing the lifetime (in seconds) of each Memcached entry. Defaults to 0.
120+
121+
**Example:**
122+
```javascript
123+
ReactCC.renderToNodeStream(<App />, cache);
124+
```
125+
126+
### renderToStaticNodeStream
127+
- `component`: The React component being rendered.
128+
- `cache`: The component cache
129+
- `memLife`: (*Only if using Memcached*) An integer representing the lifetime (in seconds) of each Memcached entry. Defaults to 0.
130+
131+
**Example:**
132+
```javascript
133+
ReactCC.renderToStaticNodeStream(<App />, cache);
134+
```

SSRtest/ModifiedReact.js

Lines changed: 121 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { lstat } from 'fs';
2+
13
/** @license React v16.2.0
24
* react-dom-server.node.development.js
35
*
@@ -2206,11 +2208,24 @@ var ReactDOMServerRenderer = function () {
22062208

22072209

22082210
ReactDOMServerRenderer.prototype.read = async function read(bytes, cache, memLife) {
2209-
// Promisify the get method, which is asynchronous for Redis
2210-
const getAsync = promisify(cache.get).bind(cache);
2211-
let continueLoop = true;
2212-
// Store starting locations of about-to-be-cached HTML within 'out'
2213-
const start = {};
2211+
/*
2212+
--- Component caching variables ---
2213+
start: Tracks start index in output string and templatization data for cached components
2214+
saveTemplates: Tracks templatized components so props can be restored in output string
2215+
restoreProps: Restores actual props to a templatized component
2216+
getAsync: Convert asynchronous get method into a promise
2217+
*/
2218+
const start = {};
2219+
let saveTemplates = [];
2220+
let loadedTemplates = {};
2221+
const restoreProps = (template, props, lookup) => {
2222+
return template.replace(/\{\{[0-9]+\}\}/g, match => props[lookup[match]]);
2223+
};
2224+
const getAsync = promisify(cache.get).bind(cache);
2225+
2226+
/*
2227+
--- Begin React 16 source code with addition of component caching logic ---
2228+
*/
22142229
if (this.exhausted) {
22152230
return null;
22162231
}
@@ -2237,34 +2252,75 @@ var ReactDOMServerRenderer = function () {
22372252
{
22382253
setCurrentDebugStack(this.stack);
22392254
}
2240-
2241-
<<<<<<< HEAD
2242-
// CACHING LOGIC: EXECUTES IF THE CHILD HAS A 'CACHE' PROP ON IT
2243-
if(child.props && child.props.cache){
2244-
// Create unique cacheKey as a function of component name and props
2245-
const cacheKey = child.type.name + JSON.stringify(child.props);
2246-
2247-
// Check cache (async function)
2248-
const reply = await getAsync(cacheKey);
2249-
// console.log(reply)
2250-
if(reply){
2251-
out += reply;
2255+
2256+
/*
2257+
--- Component caching logic ---
2258+
*/
2259+
if(child.props && child.props.cache) {
2260+
let cacheKey;
2261+
let isTemplate = child.props.templatized ? true : false;
2262+
let lookup;
2263+
let modifiedChild;
2264+
let realProps;
2265+
2266+
if (isTemplate) {
2267+
// Generate templatized version of props to generate appropriate cache key
2268+
let cacheProps = Object.assign({}, child.props);
2269+
let templatizedProps = child.props.templatized;
2270+
lookup = {};
2271+
2272+
if (typeof templatizedProps === 'string') templatizedProps = [templatizedProps];
2273+
2274+
// Generate template placeholders and lookup object for referencing prop names from placeholders
2275+
templatizedProps.forEach((templatizedProp, index) => {
2276+
const placeholder = `{{${index}}}`;
2277+
cacheProps[templatizedProp] = placeholder;
2278+
lookup[placeholder] = templatizedProp; // Move down to next if statement? (Extra work/space if not generating template)
2279+
});
2280+
2281+
cacheKey = child.type.name + JSON.stringify(cacheProps);
2282+
2283+
// Generate modified child with placeholder props to render template
2284+
if (!start[cacheKey]) {
2285+
modifiedChild = Object.assign({}, child);
2286+
modifiedChild.props = cacheProps;
2287+
realProps = child.props;
2288+
}
22522289
} else {
2253-
start[cacheKey] = out.length;
2254-
out += this.render(child, frame.context, frame.domNamespace);
2255-
=======
2256-
// IF THE CHILD HAS A CACHEKEY PROPERTY ON IT
2257-
if(child.props && child.props.cache){
2258-
const cacheKey = child.type.name + JSON.stringify(child.props);
2259-
if (!cache.get(cacheKey)){
2260-
start[cacheKey] = out.length;
2261-
out += this.render(child, frame.context, frame.domNamespace);
2290+
// Generate cache key for non-templatized component from its name and props
2291+
cacheKey = child.type.name + JSON.stringify(child.props);
2292+
}
2293+
if(memLife){
2294+
cacheKey = cacheKey.replace(/\s+/g, '|')
2295+
}
2296+
let r;
2297+
let restoredTemplate;
2298+
2299+
if (loadedTemplates[cacheKey]) { // Component found in loaded templates
2300+
restoredTemplate = restoreProps(loadedTemplates[cacheKey], realProps, lookup);
22622301
} else {
2263-
out += cache.get(cacheKey);
2264-
>>>>>>> 4b4c5a2a2344c9fedf609d15a46b33bb783e9fd6
2302+
const reply = await getAsync(cacheKey);
2303+
if (!reply) { // Component not found in cache
2304+
// If templatized component and template hasn't been generated, render a template
2305+
if (!start[cacheKey] && isTemplate) {
2306+
r = this.render(modifiedChild, frame.context, frame.domNamespace);
2307+
start[cacheKey] = { startIndex: out.length, realProps, lookup };
2308+
}
2309+
// Otherwise, render with actual props
2310+
else r = this.render(child, frame.context, frame.domNamespace);
2311+
2312+
// For simple (non-template) caching, save start index of component in output string
2313+
if (!isTemplate) start[cacheKey] = out.length;
2314+
} else { // Component found in cache
2315+
if (isTemplate) {
2316+
restoredTemplate = restoreProps(reply, realProps, lookup);
2317+
loadedTemplates[cacheKey] = reply;
2318+
}
2319+
}
22652320
}
2266-
2267-
} else {
2321+
out += restoredTemplate ? restoredTemplate : r;
2322+
} else {
2323+
// Normal rendering for non-cached components
22682324
out += this.render(child, frame.context, frame.domNamespace);
22692325
}
22702326
{
@@ -2273,36 +2329,56 @@ var ReactDOMServerRenderer = function () {
22732329
}
22742330
}
22752331

2276-
// Add newly marked items to the cache:
2277-
for (let cacheKey in start) {
2332+
/*
2333+
--- After initial render of cacheable components, recover from output string and store in cache ---
2334+
*/
2335+
for (let component in start) {
22782336
let tagStack = [];
22792337
let tagStart;
22802338
let tagEnd;
2339+
let componentStart = (typeof start[component] === 'object') ? start[component].startIndex : start[component];
22812340

22822341
do {
2283-
if (!tagStart) tagStart = start[cacheKey];
2284-
else tagStart = (out[tagEnd] === '<') ? tagEnd : out.indexOf('<', tagEnd)
2342+
if (!tagStart) tagStart = componentStart;
2343+
else tagStart = (out[tagEnd] === '<') ? tagEnd : out.indexOf('<', tagEnd);
22852344
tagEnd = out.indexOf('>', tagStart) + 1;
2286-
// Skip stack logic for void/self-closing elements
2287-
if (out[tagEnd - 2] !== '/') {
2345+
// Skip stack logic for void/self-closing elements and HTML comments
2346+
// Note: Does not account for tags inside HTML comments
2347+
if (out[tagEnd - 2] !== '/' && out[tagStart + 1] !== '!') {
22882348
// Push opening tags onto stack; pop closing tags off of stack
22892349
if (out[tagStart + 1] !== '/') tagStack.push(out.slice(tagStart, tagEnd));
22902350
else tagStack.pop();
22912351
}
22922352
} while (tagStack.length !== 0);
2293-
2294-
// cache component by slicing 'out'
2295-
<<<<<<< HEAD
2353+
// Cache component by slicing 'out'
2354+
const cachedComponent = out.slice(componentStart, tagEnd);
2355+
if (typeof start[component] === 'object') {
2356+
saveTemplates.push(start[component]);
2357+
start[component].endIndex = tagEnd;
2358+
}
22962359
if (memLife) {
2297-
cache.set(cacheKey, out.slice(start[cacheKey], tagEnd), memLife, (err) => {
2360+
cache.set(component, cachedComponent, memLife, (err) => {
22982361
if(err) console.log(err)
22992362
});
23002363
} else {
2301-
cache.set(cacheKey, out.slice(start[cacheKey], tagEnd));
2364+
cache.set(component, cachedComponent);
23022365
}
2303-
=======
2304-
cache.set(component, out.slice(start[component], tagEnd));
2305-
>>>>>>> 4b4c5a2a2344c9fedf609d15a46b33bb783e9fd6
2366+
}
2367+
2368+
// After caching all cacheable components, restore props to templates
2369+
if (saveTemplates) {
2370+
let outCopy = out;
2371+
out = '';
2372+
let bookmark = 0;
2373+
saveTemplates.sort((a, b) => a.startIndex - b.startIndex);
2374+
// Rebuild output string with actual props
2375+
saveTemplates.forEach(savedTemplate => {
2376+
out += outCopy.substring(bookmark, savedTemplate.startIndex);
2377+
bookmark = savedTemplate.endIndex;
2378+
out += restoreProps(outCopy.slice(savedTemplate.startIndex, savedTemplate.endIndex),
2379+
savedTemplate.realProps, savedTemplate.lookup);
2380+
});
2381+
out += outCopy.substring(bookmark, outCopy.length);
23062382
}
23072383
return out;
23082384
};
@@ -2648,16 +2724,10 @@ class ComponentCache {
26482724
}
26492725
});
26502726
}
2651-
<<<<<<< HEAD
26522727

26532728
get(cacheKey, cb) {
26542729
let reply = this.storage.get(cacheKey);
2655-
// return reply;
26562730
cb(null,reply);
2657-
=======
2658-
get(cacheKey, cb) {
2659-
return this.storage.get(cacheKey);
2660-
>>>>>>> 4b4c5a2a2344c9fedf609d15a46b33bb783e9fd6
26612731
}
26622732

26632733
set(cacheKey, html) {
@@ -2688,4 +2758,4 @@ var server_node = ReactDOMServer['default'] ? ReactDOMServer['default'] : ReactD
26882758

26892759
module.exports = server_node;
26902760
})();
2691-
}
2761+
}

0 commit comments

Comments
 (0)