├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── index.js
├── lib
└── style-parser.js
├── package-lock.json
├── package.json
└── test
├── test-style-parser.js
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '4'
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jun
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-render-html [](https://travis-ci.org/utatti/react-render-html)
2 |
3 | Render HTML as React element, possibly replacing dangerouslySetInnerHTML
4 |
5 | ## Deprecation
6 |
7 | This library is no more maintained. Please use other libraries instead.
8 |
9 | * [htmr](https://github.com/pveyes/htmr)
10 | * [html-to-react](https://github.com/aknuds1/html-to-react)
11 | * [react-html-parser](https://github.com/wrakky/react-html-parser)
12 | * [html-react-parser](https://github.com/remarkablemark/html-react-parser)
13 |
14 | ## How it works
15 |
16 | It renders a provided HTML string into a React element.
17 |
18 | ```js
19 | import renderHTML from 'react-render-html';
20 |
21 | renderHTML("GitHub")
22 | // => React Element
23 | // GitHub
24 | ```
25 |
26 | It may be used in the `render` method in a React component:
27 |
28 | ```js
29 | let App = React.createClass({
30 | render() {
31 | return (
32 |
33 | {renderHTML(someHTML)}
34 |
35 | );
36 | }
37 | });
38 | ```
39 |
40 | Or just by itself
41 | ```js
42 | ReactDOM.render(renderHTML(someHTML), document.getElementById('app'));
43 | ```
44 |
45 | If a provided HTML contains several top-level nodes, the function will return
46 | an array of React elements.
47 |
48 | ```js
49 | renderHTML('helloworld');
50 | // => [React Element hello, React Element world]
51 | ```
52 |
53 | ## Pros and cons
54 |
55 | ### Pros
56 |
57 | - Can make use of React's reconciliation for plain HTML too
58 | - Fully compatible with JSX
59 |
60 | ### Cons
61 |
62 | - It uses [parse5](https://github.com/inikulin/parse5) to parse HTML, which can
63 | result in large bundle size
64 | - Can result in slower rendering speed, mainly for parsing
65 |
66 | ## Install
67 |
68 | Install with NPM:
69 |
70 | ```
71 | npm i --save react-render-html
72 | ```
73 |
74 | Import with CommonJS or whatever:
75 |
76 | ```js
77 | const renderHTML = require('react-render-html');
78 |
79 | import renderHTML from 'react-render-html';
80 | ```
81 |
82 | ## A bug!
83 |
84 | When a bug is found, please report them in [Issues](https://github.com/utatti/react-render-html/issues).
85 |
86 | Also, any form of contribution(especially a PR) will absolutely be welcomed :beers:
87 |
88 | ## License
89 |
90 | [MIT](LICENSE)
91 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var htmlParser = require('parse5');
4 | var React = require('react');
5 | var convertAttr = require('react-attr-converter');
6 | var styleParser = require('./lib/style-parser');
7 |
8 | function renderNode(node, key) {
9 | if (node.nodeName === '#text') {
10 | return node.value;
11 | }
12 |
13 | if (node.nodeName === '#comment') {
14 | return node.value;
15 | }
16 |
17 | var attr = node.attrs.reduce(function (result, attr) {
18 | var name = convertAttr(attr.name);
19 | result[name] = name === 'style' ? styleParser(attr.value) : attr.value;
20 | return result;
21 | }, {key: key});
22 |
23 | if (node.childNodes.length === 0) {
24 | return React.createElement(node.tagName, attr);
25 | }
26 |
27 | if (node.nodeName === 'script') {
28 | attr.dangerouslySetInnerHTML = {__html: node.childNodes[0].value};
29 | return React.createElement('script', attr);
30 | }
31 |
32 | var children = node.childNodes.map(renderNode);
33 | return React.createElement(node.tagName, attr, children);
34 | }
35 |
36 | function renderHTML(html) {
37 | var htmlAST = htmlParser.parseFragment(html);
38 |
39 | if (htmlAST.childNodes.length === 0) {
40 | return null;
41 | }
42 |
43 | var result = htmlAST.childNodes.map(renderNode);
44 |
45 | return result.length === 1 ? result[0] : result;
46 | }
47 |
48 | module.exports = renderHTML;
49 |
--------------------------------------------------------------------------------
/lib/style-parser.js:
--------------------------------------------------------------------------------
1 | function hyphenToCamelcase(str) {
2 | var result = '';
3 | var upper = false;
4 |
5 | for (var i = 0; i < str.length; i++) {
6 | var c = str[i];
7 |
8 | if (c === '-') {
9 | upper = true;
10 | continue;
11 | }
12 |
13 | if (upper) {
14 | c = c.toUpperCase();
15 | upper = false;
16 | }
17 |
18 | result += c;
19 | }
20 |
21 | return result;
22 | }
23 |
24 | function convertKey(key) {
25 | var res = hyphenToCamelcase(key);
26 |
27 | if (key.indexOf('-ms-') === 0) {
28 | res = res[0].toLowerCase() + res.slice(1);
29 | }
30 |
31 | return res;
32 | }
33 |
34 | module.exports = function (styleStr) {
35 | return styleStr
36 | .split(';')
37 | .reduce(function (res, token) {
38 | if (token.slice(0, 7) === 'base64,') {
39 | res[res.length - 1] += ';' + token;
40 | } else {
41 | res.push(token);
42 | }
43 | return res;
44 | }, [])
45 | .reduce(function (obj, str) {
46 | var tokens = str.split(':');
47 | var key = tokens[0].trim();
48 | if (key) {
49 | var value = tokens.slice(1).join(':').trim();
50 | obj[convertKey(key)] = value;
51 | }
52 | return obj;
53 | }, {});
54 | };
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-render-html",
3 | "version": "0.6.0",
4 | "description": "Render HTML as React element, possibly replacing dangerouslySetInnerHTML",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "xo && ava"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/utatti/react-render-html.git"
12 | },
13 | "keywords": [
14 | "react",
15 | "html"
16 | ],
17 | "author": "Jun ",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/utatti/react-render-html/issues"
21 | },
22 | "homepage": "https://github.com/utatti/react-render-html#readme",
23 | "devDependencies": {
24 | "ava": "^0.20.0",
25 | "react": "^16.1.0",
26 | "react-dom": "^16.1.0",
27 | "xo": "^0.18.2"
28 | },
29 | "xo": {
30 | "space": true,
31 | "esnext": false
32 | },
33 | "dependencies": {
34 | "parse5": "^3.0.2",
35 | "react-attr-converter": "^0.3.1"
36 | },
37 | "peerDependencies": {
38 | "react": "^16.1.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/test-style-parser.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import styleParser from '../lib/style-parser';
3 |
4 | test('single rule without semi colon', t => {
5 | t.deepEqual(styleParser('background-image: url(temp.png) no-repeat center'), {
6 | backgroundImage: 'url(temp.png) no-repeat center'
7 | });
8 | });
9 |
10 | test('single rule with semi colon', t => {
11 | t.deepEqual(styleParser('background-image: url(temp.png) no-repeat center;'), {
12 | backgroundImage: 'url(temp.png) no-repeat center'
13 | });
14 | });
15 |
16 | test('multiple rules without semi colon', t => {
17 | t.deepEqual(styleParser('background-image: url(temp.png) no-repeat center;max-width: 300px'), {
18 | backgroundImage: 'url(temp.png) no-repeat center',
19 | maxWidth: '300px'
20 | });
21 | });
22 |
23 | test('multiple rules with semi colon', t => {
24 | t.deepEqual(styleParser('background-image: url(temp.png) no-repeat center;width:200px;max-width: 300px;'), {
25 | backgroundImage: 'url(temp.png) no-repeat center',
26 | width: '200px',
27 | maxWidth: '300px'
28 | });
29 | });
30 |
31 | test('vender prefix', t => {
32 | t.deepEqual(styleParser('-webkit-transition: all; -moz-transition: all; -o-transition: all; -ms-transition: all'), {
33 | WebkitTransition: 'all',
34 | MozTransition: 'all',
35 | OTransition: 'all',
36 | msTransition: 'all'
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import ReactDOMServer from 'react-dom/server';
3 | import renderHTML from '../index';
4 |
5 | const renderTest = (t, reactEl, expectedHTML) => {
6 | t.is(ReactDOMServer.renderToStaticMarkup(reactEl), expectedHTML);
7 | };
8 |
9 | const singleElementTest = (t, html) => {
10 | renderTest(t, renderHTML(html), html);
11 | };
12 |
13 | test('returns a single React element rendering a provided HTML', t => {
14 | singleElementTest(t, '' +
15 | '- hihi
' +
16 | 'helloworld
react
' +
17 | '
');
18 | });
19 |
20 | test('returns an array of React elements if several nodes are provided', t => {
21 | const arr = renderHTML('hihi' +
22 | 'helloworld
react
');
23 | t.is(arr.length, 2);
24 | renderTest(t, arr[0], 'hihi');
25 | renderTest(t, arr[1], 'helloworld
react
');
26 | });
27 |
28 | test('parse the style attribute when specified as a string', t => {
29 | singleElementTest(t, '' +
30 | '- hihi
' +
31 | 'helloworld
react
' +
32 | '
');
33 | });
34 |
35 | test('parse comment as undefined', t => {
36 | t.falsy(renderHTML(''));
37 | });
38 |
39 | test('parse base64 background url', t => {
40 | singleElementTest(t, '');
41 | });
42 |
43 | test('parse script tag', t => {
44 | singleElementTest(t, ``);
45 | });
46 |
--------------------------------------------------------------------------------