UNPKG

3.6 kBJavaScriptView Raw
1var React = require('react');
2var attributesToProps = require('./attributes-to-props');
3var utilities = require('./utilities');
4
5var setStyleProp = utilities.setStyleProp;
6
7/**
8 * Converts DOM nodes to JSX element(s).
9 *
10 * @param {DomElement[]} nodes - DOM nodes.
11 * @param {object} [options={}] - Options.
12 * @param {Function} [options.replace] - Replacer.
13 * @param {object} [options.library] - Library (React/Preact/etc.).
14 * @return {string|JSX.Element|JSX.Element[]}
15 */
16function domToReact(nodes, options) {
17 options = options || {};
18
19 var library = options.library || React;
20 var cloneElement = library.cloneElement;
21 var createElement = library.createElement;
22 var isValidElement = library.isValidElement;
23
24 var result = [];
25 var node;
26 var hasReplace = typeof options.replace === 'function';
27 var replaceElement;
28 var props;
29 var children;
30 var data;
31 var trim = options.trim;
32
33 for (var i = 0, len = nodes.length; i < len; i++) {
34 node = nodes[i];
35
36 // replace with custom React element (if present)
37 if (hasReplace) {
38 replaceElement = options.replace(node);
39
40 if (isValidElement(replaceElement)) {
41 // set "key" prop for sibling elements
42 // https://fb.me/react-warning-keys
43 if (len > 1) {
44 replaceElement = cloneElement(replaceElement, {
45 key: replaceElement.key || i
46 });
47 }
48 result.push(replaceElement);
49 continue;
50 }
51 }
52
53 if (node.type === 'text') {
54 // if trim option is enabled, skip whitespace text nodes
55 if (trim) {
56 data = node.data.trim();
57 if (data) {
58 result.push(node.data);
59 }
60 } else {
61 result.push(node.data);
62 }
63 continue;
64 }
65
66 props = node.attribs;
67 if (skipAttributesToProps(node)) {
68 setStyleProp(props.style, props);
69 } else if (props) {
70 props = attributesToProps(props);
71 }
72
73 children = null;
74
75 switch (node.type) {
76 case 'script':
77 case 'style':
78 // prevent text in <script> or <style> from being escaped
79 // https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
80 if (node.children[0]) {
81 props.dangerouslySetInnerHTML = {
82 __html: node.children[0].data
83 };
84 }
85 break;
86
87 case 'tag':
88 // setting textarea value in children is an antipattern in React
89 // https://reactjs.org/docs/forms.html#the-textarea-tag
90 if (node.name === 'textarea' && node.children[0]) {
91 props.defaultValue = node.children[0].data;
92 } else if (node.children && node.children.length) {
93 // continue recursion of creating React elements (if applicable)
94 children = domToReact(node.children, options);
95 }
96 break;
97
98 // skip all other cases (e.g., comment)
99 default:
100 continue;
101 }
102
103 // set "key" prop for sibling elements
104 // https://fb.me/react-warning-keys
105 if (len > 1) {
106 props.key = i;
107 }
108
109 result.push(createElement(node.name, props, children));
110 }
111
112 return result.length === 1 ? result[0] : result;
113}
114
115/**
116 * Determines whether DOM element attributes should be transformed to props.
117 * Web Components should not have their attributes transformed except for `style`.
118 *
119 * @param {DomElement} node
120 * @return {boolean}
121 */
122function skipAttributesToProps(node) {
123 return (
124 utilities.PRESERVE_CUSTOM_ATTRIBUTES &&
125 node.type === 'tag' &&
126 utilities.isCustomComponent(node.name, node.attribs)
127 );
128}
129
130module.exports = domToReact;