@elliottsj/react-render-html
Advanced tools
Comparing version 0.2.0 to 0.2.1
@@ -9,2 +9,5 @@ 'use strict'; | ||
exports.applyMiddleware = applyMiddleware; | ||
exports.default = renderHTML; | ||
var _parse = require('parse5'); | ||
@@ -22,2 +25,6 @@ | ||
var _reactStyling = require('react-styling'); | ||
var _reactStyling2 = _interopRequireDefault(_reactStyling); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -29,17 +36,43 @@ | ||
function baseRenderNode() { | ||
var compose = function compose() { | ||
for (var _len = arguments.length, fns = Array(_len), _key = 0; _key < _len; _key++) { | ||
fns[_key] = arguments[_key]; | ||
} | ||
return function (arg) { | ||
return fns.reduceRight(function (prev, fn) { | ||
return fn(prev); | ||
}, arg); | ||
}; | ||
}; | ||
function baseRenderNode(renderNode) { | ||
return function (node, key) { | ||
if (node.nodeName === '#text') { | ||
return node.value; | ||
} | ||
var props = _extends({ | ||
key: key | ||
}, node.attrs.reduce(function (attrs, attr) { | ||
var name = (0, _reactAttrConverter2.default)(attr.name); | ||
return _extends({}, attrs, _defineProperty({}, name, name === 'style' ? (0, _reactStyling2.default)(attr.value) : attr.value)); | ||
}, {})); | ||
var children = node.childNodes.map(renderNode); | ||
return _react2.default.createElement.apply(_react2.default, [node.tagName, props].concat(_toConsumableArray(children))); | ||
}; | ||
} | ||
function applyMiddleware() { | ||
for (var _len2 = arguments.length, transformers = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | ||
transformers[_key2] = arguments[_key2]; | ||
} | ||
return function (renderNode) { | ||
return function (node, key) { | ||
if (node.nodeName === '#text') { | ||
return node.value; | ||
} | ||
var props = _extends({ | ||
key: key | ||
}, node.attrs.reduce(function (attrs, attr) { | ||
return _extends({}, attrs, _defineProperty({}, (0, _reactAttrConverter2.default)(attr.name), attr.value)); | ||
}, {})); | ||
var children = node.childNodes.map(renderNode); | ||
return _react2.default.createElement.apply(_react2.default, [node.tagName, props].concat(_toConsumableArray(children))); | ||
var chain = transformers.map(function (transformer) { | ||
return transformer(renderNode); | ||
}); | ||
return compose.apply(undefined, _toConsumableArray(chain))(node, key); | ||
}; | ||
@@ -50,5 +83,7 @@ }; | ||
function renderHTML(html) { | ||
var renderNode = arguments.length <= 1 || arguments[1] === undefined ? function (next) { | ||
return function () { | ||
return next.apply(undefined, arguments); | ||
var renderNode = arguments.length <= 1 || arguments[1] === undefined ? function () { | ||
return function (next) { | ||
return function () { | ||
return next.apply(undefined, arguments); | ||
}; | ||
}; | ||
@@ -64,3 +99,3 @@ } : arguments[1]; | ||
var finalRenderNode = function finalRenderNode(node, key) { | ||
return renderNode(baseRenderNode())(finalRenderNode)(node, key); | ||
return renderNode(finalRenderNode)(baseRenderNode(finalRenderNode))(node, key); | ||
}; | ||
@@ -70,4 +105,2 @@ var result = htmlAST.childNodes.map(finalRenderNode); | ||
return result.length === 1 ? result[0] : result; | ||
} | ||
exports.default = renderHTML; | ||
} |
{ | ||
"name": "@elliottsj/react-render-html", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "No more dangerouslySetInnerHTML, render HTML as React element.", | ||
@@ -32,2 +32,3 @@ "main": "lib/index.js", | ||
"babel-preset-stage-2": "^6.5.0", | ||
"react": "^15.1.0", | ||
"react-dom": "^15.1.0", | ||
@@ -42,3 +43,4 @@ "xo": "^0.15.1" | ||
"parse5": "^2.0.2", | ||
"react-attr-converter": "0.1.0" | ||
"react-attr-converter": "0.1.0", | ||
"react-styling": "^1.5.0" | ||
}, | ||
@@ -45,0 +47,0 @@ "peerDependencies": { |
@@ -44,23 +44,28 @@ # react-render-html [](https://travis-ci.org/noraesae/react-render-html) | ||
### Custom renderers | ||
### Custom renderers ("Middleware") | ||
Pass a function as the 2nd argument to `renderHTML` to customize how nodes are rendered: | ||
```js | ||
function renderNode(next) { /* ... */ } | ||
function middleware(renderNode) { /* ... */ } | ||
renderHTML('<li>hello</li><li>world</li>', renderNode); | ||
renderHTML('<li>hello</li><li>world</li>', middleware); | ||
``` | ||
The function should have the signature | ||
The middleware function should have the signature | ||
```js | ||
renderNode(next: renderNode) => (renderNode: renderNode) => (node: ASTNode.<Element>, key: String) => ReactElement | ||
type Middleware = (renderNode: NodeRenderer) => (next: NodeRenderer) => NodeRenderer | ||
type NodeRenderer = (node: ASTNode.<Element>, key: String) => ReactElement | ||
``` | ||
Where `next` is the next renderer in the chain, `renderNode` is the entire chain, and `node` and `key` correspond to the current node being rendered. | ||
Where `next` is the next renderer in the middleware chain, `renderNode` is the entire chain, and `node` and `key` correspond to the current node being rendered. | ||
For example, to replace the `href` attribute of all `<a>` elements, use: | ||
```js | ||
function replaceHref(next) { | ||
return next => renderNode => (node, key) => { | ||
const element = next(renderNode)(node, key); | ||
import renderHTML, { | ||
applyMiddleware | ||
} from 'react-render-html'; | ||
function replaceHref(renderNode) { | ||
return next => (node, key) => { | ||
const element = next(node, key); | ||
if (node.tagName === 'a') { | ||
@@ -80,6 +85,6 @@ return React.cloneElement(element, { | ||
Custom renders are composable: simply call `next` from within a renderer to get the resulting ReactElement from subsequent renders, and call `renderNode` if you need to render a node from scratch (e.g. child nodes). For example: | ||
Custom renders are composable: using `applyMiddleware`, simply call `next` from within a renderer to get the resulting ReactElement from subsequent renderers, and call `renderNode` if you need to render a node from scratch using the entire renderer chain (e.g. child nodes). For example: | ||
```js | ||
const replaceHref = next => renderNode => (node, key) => { | ||
const element = next(renderNode)(node, key); | ||
const replaceHref = renderNode => next => (node, key) => { | ||
const element = next(node, key); | ||
if (node.tagName === 'a') { | ||
@@ -92,10 +97,10 @@ return React.cloneElement(element, { | ||
}; | ||
const replacePs = next => renderNode => (node, key) => { | ||
const replacePs = renderNode => next => (node, key) => { | ||
if (node.tagName === 'p') { | ||
return React.createElement(node.tagName, {}, 'Redacted'); | ||
} | ||
return next(renderNode)(node, key); | ||
return next(node, key); | ||
}; | ||
const addLi = next => renderNode => (node, key) => { | ||
const element = next(renderNode)(node, key); | ||
const addLi = renderNode => next => (node, key) => { | ||
const element = next(node, key); | ||
if (node.tagName === 'ul') { | ||
@@ -116,3 +121,3 @@ return React.cloneElement( | ||
'</ul>', | ||
compose(replaceHref, replacePs, addLi) | ||
applyMiddleware(replaceHref, replacePs, addLi) | ||
); | ||
@@ -136,6 +141,10 @@ | ||
```js | ||
const renderHTML = require('react-render-html'); | ||
const { | ||
default: renderHTML, | ||
applyMiddleware | ||
} = require('react-render-html'); | ||
import renderHTML from 'react-render-html'; | ||
import * as renderHTML from 'react-render-html'; // both of them work | ||
// OR | ||
import renderHTML, { applyMiddleware } from 'react-render-html'; | ||
``` | ||
@@ -142,0 +151,0 @@ |
import parse5 from 'parse5'; | ||
import React from 'react'; | ||
import convertAttr from 'react-attr-converter'; | ||
import styler from 'react-styling'; | ||
function baseRenderNode() { | ||
return renderNode => (node, key) => { | ||
const compose = (...fns) => arg => fns.reduceRight((prev, fn) => fn(prev), arg); | ||
function baseRenderNode(renderNode) { | ||
return (node, key) => { | ||
if (node.nodeName === '#text') { | ||
@@ -13,6 +16,9 @@ return node.value; | ||
key, | ||
...node.attrs.reduce((attrs, attr) => ({ | ||
...attrs, | ||
[convertAttr(attr.name)]: attr.value | ||
}), {}) | ||
...node.attrs.reduce((attrs, attr) => { | ||
const name = convertAttr(attr.name); | ||
return { | ||
...attrs, | ||
[name]: name === 'style' ? styler(attr.value) : attr.value | ||
}; | ||
}, {}) | ||
}; | ||
@@ -25,3 +31,10 @@ | ||
function renderHTML(html, renderNode = next => (...args) => next(...args)) { | ||
export function applyMiddleware(...transformers) { | ||
return renderNode => (node, key) => { | ||
const chain = transformers.map(transformer => transformer(renderNode)); | ||
return compose(...chain)(node, key); | ||
}; | ||
} | ||
export default function renderHTML(html, renderNode = () => next => (...args) => next(...args)) { | ||
const htmlAST = parse5.parseFragment(html); | ||
@@ -33,3 +46,4 @@ | ||
const finalRenderNode = (node, key) => renderNode(baseRenderNode())(finalRenderNode)(node, key); | ||
const finalRenderNode = | ||
(node, key) => renderNode(finalRenderNode)(baseRenderNode(finalRenderNode))(node, key); | ||
const result = htmlAST.childNodes.map(finalRenderNode); | ||
@@ -39,3 +53,1 @@ | ||
} | ||
export default renderHTML; |
import test from 'ava'; | ||
import React from 'react'; | ||
import * as ReactDOMServer from 'react-dom/server'; | ||
import renderHTML from '../lib/index'; | ||
import renderHTML, { | ||
applyMiddleware | ||
} from '../lib/index'; | ||
const compose = (...fns) => arg => fns.reduceRight((prev, fn) => fn(prev), arg); | ||
const renderTest = (t, reactEl, expectedHTML) => { | ||
@@ -31,5 +31,12 @@ t.is(ReactDOMServer.renderToStaticMarkup(reactEl), expectedHTML); | ||
test('parse the style attribute when specified as a string', t => { | ||
singleElementTest(t, '<ul>' + | ||
'<li style="font-weight:bold;color:green;"><a class="hello" href="https://github.com">hihi</a></li>' + | ||
'<li style="font-style:italic;"><p><b>hello</b>world</p><p>react</p></li>' + | ||
'</ul>'); | ||
}); | ||
test('uses the given `renderNode` function', t => { | ||
const replaceHref = next => renderNode => (node, key) => { | ||
const element = next(renderNode)(node, key); | ||
const replaceHref = () => next => (node, key) => { | ||
const element = next(node, key); | ||
if (node.tagName === 'a') { | ||
@@ -61,4 +68,4 @@ return React.cloneElement(element, { | ||
test('can compose `renderNode` functions', t => { | ||
const replaceHref = next => renderNode => (node, key) => { | ||
const element = next(renderNode)(node, key); | ||
const replaceHref = () => next => (node, key) => { | ||
const element = next(node, key); | ||
if (node.tagName === 'a') { | ||
@@ -71,10 +78,10 @@ return React.cloneElement(element, { | ||
}; | ||
const replacePs = next => renderNode => (node, key) => { | ||
const replacePs = () => next => (node, key) => { | ||
if (node.tagName === 'p') { | ||
return React.createElement(node.tagName, {}, 'Redacted'); | ||
} | ||
return next(renderNode)(node, key); | ||
return next(node, key); | ||
}; | ||
const addLi = next => renderNode => (node, key) => { | ||
const element = next(renderNode)(node, key); | ||
const addLi = renderNode => next => (node, key) => { | ||
const element = next(node, key); | ||
if (node.tagName === 'ul') { | ||
@@ -95,3 +102,3 @@ return React.cloneElement( | ||
'</ul>', | ||
compose(replaceHref, replacePs, addLi) | ||
applyMiddleware(replaceHref, replacePs, addLi) | ||
); | ||
@@ -98,0 +105,0 @@ |
14864
215
156
4
7
0
+ Addedreact-styling@^1.5.0
+ Addedbabel-runtime@6.26.0(transitive)
+ Addedcore-js@2.6.12(transitive)
+ Addedreact-styling@1.6.4(transitive)
+ Addedregenerator-runtime@0.11.1(transitive)
+ Addedstyle-builder@1.0.13(transitive)