1 | import { objectKeys, encodeEntities, indent, isLargeString, styleObjToCss, assign, getNodeProps } from './util';
|
2 |
|
3 | const SHALLOW = { shallow: true };
|
4 |
|
5 |
|
6 | const UNNAMED = [];
|
7 |
|
8 | const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | renderToString.render = renderToString;
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
|
32 |
|
33 |
|
34 |
|
35 | function renderToString(vnode, context, opts, inner, isSvgMode) {
|
36 | if (vnode==null || typeof vnode==='boolean') {
|
37 | return '';
|
38 | }
|
39 |
|
40 | let nodeName = vnode.nodeName,
|
41 | attributes = vnode.attributes,
|
42 | isComponent = false;
|
43 | context = context || {};
|
44 | opts = opts || {};
|
45 |
|
46 | let pretty = opts.pretty,
|
47 | indentChar = typeof pretty==='string' ? pretty : '\t';
|
48 |
|
49 |
|
50 | if (typeof vnode!=='object' && !nodeName) {
|
51 | return encodeEntities(vnode);
|
52 | }
|
53 |
|
54 |
|
55 | if (typeof nodeName==='function') {
|
56 | isComponent = true;
|
57 | if (opts.shallow && (inner || opts.renderRootComponent===false)) {
|
58 | nodeName = getComponentName(nodeName);
|
59 | }
|
60 | else {
|
61 | let props = getNodeProps(vnode),
|
62 | rendered;
|
63 |
|
64 | if (!nodeName.prototype || typeof nodeName.prototype.render!=='function') {
|
65 |
|
66 | rendered = nodeName(props, context);
|
67 | }
|
68 | else {
|
69 |
|
70 | let c = new nodeName(props, context);
|
71 |
|
72 | c._disable = c.__x = true;
|
73 | c.props = props;
|
74 | c.context = context;
|
75 | if (c.componentWillMount) c.componentWillMount();
|
76 | rendered = c.render(c.props, c.state, c.context);
|
77 |
|
78 | if (c.getChildContext) {
|
79 | context = assign(assign({}, context), c.getChildContext());
|
80 | }
|
81 | }
|
82 |
|
83 | return renderToString(rendered, context, opts, opts.shallowHighOrder!==false);
|
84 | }
|
85 | }
|
86 |
|
87 |
|
88 | let s = '', html;
|
89 |
|
90 | if (attributes) {
|
91 | let attrs = objectKeys(attributes);
|
92 |
|
93 |
|
94 | if (opts && opts.sortAttributes===true) attrs.sort();
|
95 |
|
96 | for (let i=0; i<attrs.length; i++) {
|
97 | let name = attrs[i],
|
98 | v = attributes[name];
|
99 | if (name==='children') continue;
|
100 |
|
101 | if (name.match(/[\s\n\\/='"\0<>]/)) continue;
|
102 |
|
103 | if (!(opts && opts.allAttributes) && (name==='key' || name==='ref')) continue;
|
104 |
|
105 | if (name==='className') {
|
106 | if (attributes.class) continue;
|
107 | name = 'class';
|
108 | }
|
109 | else if (isSvgMode && name.match(/^xlink:?./)) {
|
110 | name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
|
111 | }
|
112 |
|
113 | if (name==='style' && v && typeof v==='object') {
|
114 | v = styleObjToCss(v);
|
115 | }
|
116 |
|
117 | let hooked = opts.attributeHook && opts.attributeHook(name, v, context, opts, isComponent);
|
118 | if (hooked || hooked==='') {
|
119 | s += hooked;
|
120 | continue;
|
121 | }
|
122 |
|
123 | if (name==='dangerouslySetInnerHTML') {
|
124 | html = v && v.__html;
|
125 | }
|
126 | else if ((v || v===0 || v==='') && typeof v!=='function') {
|
127 | if (v===true || v==='') {
|
128 | v = name;
|
129 |
|
130 | if (!opts || !opts.xml) {
|
131 | s += ' ' + name;
|
132 | continue;
|
133 | }
|
134 | }
|
135 | s += ` ${name}="${encodeEntities(v)}"`;
|
136 | }
|
137 | }
|
138 | }
|
139 |
|
140 |
|
141 | let sub = s.replace(/^\n\s*/, ' ');
|
142 | if (sub!==s && !~sub.indexOf('\n')) s = sub;
|
143 | else if (pretty && ~s.indexOf('\n')) s += '\n';
|
144 |
|
145 | s = `<${nodeName}${s}>`;
|
146 | if (String(nodeName).match(/[\s\n\\/='"\0<>]/)) throw s;
|
147 |
|
148 | let isVoid = String(nodeName).match(VOID_ELEMENTS);
|
149 | if (isVoid) s = s.replace(/>$/, ' />');
|
150 |
|
151 | let pieces = [];
|
152 | if (html) {
|
153 |
|
154 | if (pretty && isLargeString(html)) {
|
155 | html = '\n' + indentChar + indent(html, indentChar);
|
156 | }
|
157 | s += html;
|
158 | }
|
159 | else if (vnode.children) {
|
160 | let hasLarge = ~s.indexOf('\n');
|
161 | for (let i=0; i<vnode.children.length; i++) {
|
162 | let child = vnode.children[i];
|
163 | if (child!=null && child!==false) {
|
164 | let childSvgMode = nodeName==='svg' ? true : nodeName==='foreignObject' ? false : isSvgMode,
|
165 | ret = renderToString(child, context, opts, true, childSvgMode);
|
166 | if (!hasLarge && pretty && isLargeString(ret)) hasLarge = true;
|
167 | if (ret) pieces.push(ret);
|
168 | }
|
169 | }
|
170 | if (pretty && hasLarge) {
|
171 | for (let i=pieces.length; i--; ) {
|
172 | pieces[i] = '\n' + indentChar + indent(pieces[i], indentChar);
|
173 | }
|
174 | }
|
175 | }
|
176 |
|
177 | if (pieces.length) {
|
178 | s += pieces.join('');
|
179 | }
|
180 | else if (opts && opts.xml) {
|
181 | return s.substring(0, s.length-1) + ' />';
|
182 | }
|
183 |
|
184 | if (!isVoid) {
|
185 | if (pretty && ~s.indexOf('\n')) s += '\n';
|
186 | s += `</${nodeName}>`;
|
187 | }
|
188 |
|
189 | return s;
|
190 | }
|
191 |
|
192 | function getComponentName(component) {
|
193 | return component.displayName || component!==Function && component.name || getFallbackComponentName(component);
|
194 | }
|
195 |
|
196 | function getFallbackComponentName(component) {
|
197 | let str = Function.prototype.toString.call(component),
|
198 | name = (str.match(/^\s*function\s+([^( ]+)/) || '')[1];
|
199 | if (!name) {
|
200 |
|
201 | let index = -1;
|
202 | for (let i=UNNAMED.length; i--; ) {
|
203 | if (UNNAMED[i]===component) {
|
204 | index = i;
|
205 | break;
|
206 | }
|
207 | }
|
208 |
|
209 | if (index<0) {
|
210 | index = UNNAMED.push(component) - 1;
|
211 | }
|
212 | name = `UnnamedComponent${index}`;
|
213 | }
|
214 | return name;
|
215 | }
|
216 | renderToString.shallowRender = shallowRender;
|
217 |
|
218 | export default renderToString;
|
219 |
|
220 | export {
|
221 | renderToString as render,
|
222 | renderToString,
|
223 | shallowRender
|
224 | };
|