Skip to content

Commit 5da5e5b

Browse files
authored
Merge pull request #11 from mnaga/master
Added support for templating objects and arrays recursively
2 parents 6f24819 + 8eaf53c commit 5da5e5b

File tree

2 files changed

+100
-28
lines changed

2 files changed

+100
-28
lines changed

lib/index.js

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ const get = require("lodash/get");
44
const set = require("lodash/set");
55
const wrap = require("lodash/wrap");
66
const template = require("lodash/template");
7+
const mapKeys = require("lodash/mapKeys");
8+
const isArray = require("lodash/isArray");
79
const isObject = require("lodash/isObject");
10+
const isPlainObject = require("lodash/isPlainObject");
811
const Module = require("module");
912
const InstantiateReactComponent = require("react-dom/lib/instantiateReactComponent");
1013
const escapeTextContentForBrowser = require("react-dom/lib/escapeTextContentForBrowser");
@@ -82,12 +85,50 @@ class InstantiateReactComponentOptimizer {
8285
}
8386
}
8487

85-
/* eslint-disable max-params, no-console*/
88+
/* eslint-disable max-params, no-console, prefer-template*/
89+
90+
const restoreAttr = (origProps, lookupPropTable, propKey, arrNotaKey, propValue) => {
91+
if (isPlainObject(propValue)) {
92+
mapKeys(propValue, (value, key) => {
93+
restoreAttr(origProps, lookupPropTable, `${propKey}.${key}`,
94+
`${propKey}["${key}"]`, value);
95+
});
96+
} else if (isArray(propValue)) {
97+
for (let i = 0; i < propValue.length; i++) {
98+
restoreAttr(origProps, lookupPropTable, `${propKey}.${i}`, `${propKey}[${i}]`,
99+
propValue[i]);
100+
}
101+
} else {
102+
const _attrKey = propKey.replace(/_/gi, "__").replace(/\./gi, "_");
103+
set(origProps, arrNotaKey, lookupPropTable[_attrKey]);
104+
}
105+
};
106+
107+
const templatizeAttr = (origProps, lookupPropTable, propKey, arrNotaKey,
108+
propValue, addlCacheForArr) => {
109+
if (isPlainObject(propValue)) {
110+
mapKeys(propValue, (value, key) => {
111+
templatizeAttr(origProps, lookupPropTable, `${propKey}.${key}`, `${propKey}["${key}"]`,
112+
value, addlCacheForArr);
113+
});
114+
} else if (isArray(propValue)) {
115+
addlCacheForArr.push(propKey + "_" + propValue.length);
116+
for (let i = 0; i < propValue.length; i++) {
117+
templatizeAttr(origProps, lookupPropTable, `${propKey}.${i}`, `${propKey}[${i}]`,
118+
propValue[i], addlCacheForArr);
119+
}
120+
} else {
121+
const _attrKey = propKey.replace(/_/gi, "__").replace(/\./gi, "_");
122+
lookupPropTable[_attrKey] = escapeTextContentForBrowser(propValue);
123+
set(origProps, arrNotaKey, "${" + _attrKey + "}");
124+
}
125+
};
126+
86127
const restorePropsAndProcessTemplate = (compiled, templateAttrs, templateAttrValues, curEl) => {
87128
templateAttrs.forEach((attrKey) => {
88-
const _attrKey = attrKey.replace(".", "__");
89-
set(curEl.props, attrKey, templateAttrValues[_attrKey]);
90-
templateAttrValues[_attrKey] = escapeTextContentForBrowser(templateAttrValues[_attrKey]);
129+
const value = get(curEl.props, attrKey);
130+
restoreAttr({a: curEl.props}, templateAttrValues, `a.${attrKey}`, `a.${attrKey}`,
131+
value);
91132
});
92133
return compiled(templateAttrValues);
93134
};
@@ -99,19 +140,6 @@ class InstantiateReactComponentOptimizer {
99140
self.componentsToCache.indexOf(curEl.type.name) > EMPTY_ID);
100141
};
101142

102-
/* eslint-disable max-params */
103-
const templatizeProps = function (attrKey, templateAttrValues, curEl, cmpName) {
104-
const _attrKey = attrKey.replace(".", "__");
105-
templateAttrValues[_attrKey] = get(curEl.props, attrKey);
106-
if (isObject(templateAttrValues[_attrKey])) {
107-
throw new Error(
108-
`Cannot templatize Object at ${attrKey} for component ${cmpName}`
109-
);
110-
}
111-
/* eslint-disable prefer-template */
112-
set(curEl.props, attrKey, "${" + _attrKey + "}");
113-
};
114-
115143
const WrappedInstantiateReactComponent = wrap(InstantiateReactComponent,
116144
function (instantiate) {
117145
const component = instantiate.apply(
@@ -132,13 +160,16 @@ class InstantiateReactComponentOptimizer {
132160
if (generatedKey === null) {
133161
return mount.apply(component, [].slice.call(arguments, 1));
134162
}
135-
const cacheKey = `${cmpName}:${generatedKey}`;
163+
const addlCacheForArr = [];
136164
const rootID = arguments[1];
137165
const templateAttrs = self.config.components[cmpName].templateAttrs || [];
138166
const templateAttrValues = {};
139167
templateAttrs.forEach((attrKey) => {
140-
templatizeProps(attrKey, templateAttrValues, curEl, cmpName);
168+
const value = get(curEl.props, attrKey);
169+
templatizeAttr({a: curEl.props}, templateAttrValues, `a.${attrKey}`,
170+
`a.${attrKey}`, value, addlCacheForArr);
141171
});
172+
const cacheKey = `${cmpName}:${generatedKey}:${addlCacheForArr}`;
142173
const cachedObj = self.lruCache.get(cacheKey);
143174
if (cachedObj) {
144175
eventCallback({type: "cache", event: "hit", cmpName: cmpName});

test/spec/index.spec.js

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -344,15 +344,15 @@ describe("react-component-cache", function () {
344344
/*eslint-enable camelcase*/
345345
});
346346

347-
it("should should throw error when templateAttr is function or object", () => {
347+
it("should accept object attributes for templating", () => {
348348
clearRequireCache();
349349

350350
let renderCount = 0;
351351
/* eslint-disable max-params, no-console*/
352352
reactComponentCache({
353353
components: {
354354
"HelloWorld": {
355-
templateAttrs: ["text"]
355+
templateAttrs: ["data"]
356356
}
357357
}
358358
});
@@ -362,16 +362,57 @@ describe("react-component-cache", function () {
362362
class HelloWorld extends React.Component {
363363
render() {
364364
renderCount++;
365-
return React.DOM.div(null, this.props.text);
365+
return React.DOM.div(null, this.props.data.text);
366366
}
367367
}
368-
const renderFn = () => {
369-
ReactDomServer.renderToString(React.createFactory(HelloWorld)({text: {a: "Hello World X!"}}));
370-
};
371-
expect(renderFn).to.throw(
372-
/Cannot templatize Object at text for component HelloWorld/);
373368

374-
expect(renderCount).to.equal(0);
369+
// Cache Miss
370+
let props = {data: { text: "Hello World X!"}};
371+
expect(ReactDomServer.renderToString(React.createFactory(HelloWorld)(props))).to.contains("Hello World X!");
372+
expect(props.data.text).to.equal("Hello World X!");
373+
expect(renderCount).to.equal(1);
374+
375+
// Cache Hit
376+
props = {data: { text: "Hello World Y!"}};
377+
expect(ReactDomServer.renderToString(React.createFactory(HelloWorld)(props))).to.contains("Hello World Y!");
378+
expect(props.data.text).to.equal("Hello World Y!");
379+
expect(renderCount).to.equal(1);
380+
});
381+
382+
it("should accept array ref for templating", () => {
383+
clearRequireCache();
384+
385+
let renderCount = 0;
386+
/* eslint-disable max-params, no-console*/
387+
reactComponentCache({
388+
components: {
389+
"HelloWorld": {
390+
templateAttrs: ["data"]
391+
}
392+
}
393+
});
394+
/* eslint-enable max-params, no-console*/
395+
const React = require("react");
396+
const ReactDomServer = require("react-dom/server");
397+
class HelloWorld extends React.Component {
398+
render() {
399+
renderCount++;
400+
const str = `${this.props.data[0]} ${this.props.data[1]}`;
401+
return React.DOM.div(null, str);
402+
}
403+
}
404+
405+
// Cache Miss
406+
let props = {data: ["Hello World", "X!"]};
407+
expect(ReactDomServer.renderToString(React.createFactory(HelloWorld)(props))).to.contains("Hello World X!");
408+
expect(props.data.length).to.equal(2);
409+
expect(renderCount).to.equal(1);
410+
411+
// Cache Hit
412+
props = {data: ["Hello World", "Y!"]};
413+
expect(ReactDomServer.renderToString(React.createFactory(HelloWorld)(props))).to.contains("Hello World Y!");
414+
expect(props.data.length).to.equal(2);
415+
expect(renderCount).to.equal(1);
375416
});
376417

377418
it("should accept a custom cache implementation", () => {

0 commit comments

Comments
 (0)