preact-render-to-string 5.2.1 → 5.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,462 +1,396 @@
1
- import {
2
- encodeEntities,
3
- indent,
4
- isLargeString,
5
- styleObjToCss,
6
- assign,
7
- getChildren
8
- } from './util';
9
- import { options, Fragment } from 'preact';
10
-
11
- /** @typedef {import('preact').VNode} VNode */
12
-
13
- const SHALLOW = { shallow: true };
14
-
15
- // components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
16
- const UNNAMED = [];
17
-
18
- const VOID_ELEMENTS =
19
- /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
20
-
21
- const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
22
-
23
- function markAsDirty() {
24
- this.__d = true;
25
- }
26
-
27
- /** Render Preact JSX + Components to an HTML string.
28
- * @name render
29
- * @function
30
- * @param {VNode} vnode JSX VNode to render.
31
- * @param {Object} [context={}] Optionally pass an initial context object through the render path.
32
- * @param {Object} [options={}] Rendering options
33
- * @param {Boolean} [options.shallow=false] If `true`, renders nested Components as HTML elements (`<Foo a="b" />`).
34
- * @param {Boolean} [options.xml=false] If `true`, uses self-closing tags for elements without children.
35
- * @param {Boolean} [options.pretty=false] If `true`, adds whitespace for readability
36
- * @param {RegExp|undefined} [options.voidElements] RegeEx that matches elements that are considered void (self-closing)
37
- */
38
- renderToString.render = renderToString;
39
-
40
- /** Only render elements, leaving Components inline as `<ComponentName ... />`.
41
- * This method is just a convenience alias for `render(vnode, context, { shallow:true })`
42
- * @name shallow
43
- * @function
44
- * @param {VNode} vnode JSX VNode to render.
45
- * @param {Object} [context={}] Optionally pass an initial context object through the render path.
46
- */
47
- let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
48
-
49
- const EMPTY_ARR = [];
50
- function renderToString(vnode, context, opts) {
51
- context = context || {};
52
- opts = opts || {};
53
-
54
- // Performance optimization: `renderToString` is synchronous and we
55
- // therefore don't execute any effects. To do that we pass an empty
56
- // array to `options._commit` (`__c`). But we can go one step further
57
- // and avoid a lot of dirty checks and allocations by setting
58
- // `options._skipEffects` (`__s`) too.
59
- const previousSkipEffects = options.__s;
60
- options.__s = true;
61
-
62
- const res = _renderToString(vnode, context, opts);
63
-
64
- // options._commit, we don't schedule any effects in this library right now,
65
- // so we can pass an empty queue to this hook.
66
- if (options.__c) options.__c(vnode, EMPTY_ARR);
67
- EMPTY_ARR.length = 0;
68
- options.__s = previousSkipEffects;
69
- return res;
70
- }
71
-
72
- /** The default export is an alias of `render()`. */
73
- function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
74
- if (vnode == null || typeof vnode === 'boolean') {
75
- return '';
76
- }
77
-
78
- // #text nodes
79
- if (typeof vnode !== 'object') {
80
- return encodeEntities(vnode);
81
- }
82
-
83
- let pretty = opts.pretty,
84
- indentChar = pretty && typeof pretty === 'string' ? pretty : '\t';
85
-
86
- if (Array.isArray(vnode)) {
87
- let rendered = '';
88
- for (let i = 0; i < vnode.length; i++) {
89
- if (pretty && i > 0) rendered = rendered + '\n';
90
- rendered =
91
- rendered +
92
- _renderToString(vnode[i], context, opts, inner, isSvgMode, selectValue);
93
- }
94
- return rendered;
95
- }
96
-
97
- let nodeName = vnode.type,
98
- props = vnode.props,
99
- isComponent = false;
100
-
101
- // components
102
- if (typeof nodeName === 'function') {
103
- isComponent = true;
104
- if (opts.shallow && (inner || opts.renderRootComponent === false)) {
105
- nodeName = getComponentName(nodeName);
106
- } else if (nodeName === Fragment) {
107
- const children = [];
108
- getChildren(children, vnode.props.children);
109
- return _renderToString(
110
- children,
111
- context,
112
- opts,
113
- opts.shallowHighOrder !== false,
114
- isSvgMode,
115
- selectValue
116
- );
117
- } else {
118
- let rendered;
119
-
120
- let c = (vnode.__c = {
121
- __v: vnode,
122
- context,
123
- props: vnode.props,
124
- // silently drop state updates
125
- setState: markAsDirty,
126
- forceUpdate: markAsDirty,
127
- __d: true,
128
- // hooks
129
- __h: []
130
- });
131
-
132
- // options._diff
133
- if (options.__b) options.__b(vnode);
134
-
135
- // options._render
136
- let renderHook = options.__r;
137
-
138
- if (
139
- !nodeName.prototype ||
140
- typeof nodeName.prototype.render !== 'function'
141
- ) {
142
- // Necessary for createContext api. Setting this property will pass
143
- // the context value as `this.context` just for this component.
144
- let cxType = nodeName.contextType;
145
- let provider = cxType && context[cxType.__c];
146
- let cctx =
147
- cxType != null
148
- ? provider
149
- ? provider.props.value
150
- : cxType.__
151
- : context;
152
-
153
- // If a hook invokes setState() to invalidate the component during rendering,
154
- // re-render it up to 25 times to allow "settling" of memoized states.
155
- // Note:
156
- // This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
157
- // https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
158
- let count = 0;
159
- while (c.__d && count++ < 25) {
160
- c.__d = false;
161
-
162
- if (renderHook) renderHook(vnode);
163
-
164
- // stateless functional components
165
- rendered = nodeName.call(vnode.__c, props, cctx);
166
- }
167
- } else {
168
- // class-based components
169
- let cxType = nodeName.contextType;
170
- let provider = cxType && context[cxType.__c];
171
- let cctx =
172
- cxType != null
173
- ? provider
174
- ? provider.props.value
175
- : cxType.__
176
- : context;
177
-
178
- // c = new nodeName(props, context);
179
- c = vnode.__c = new nodeName(props, cctx);
180
- c.__v = vnode;
181
- // turn off stateful re-rendering:
182
- c._dirty = c.__d = true;
183
- c.props = props;
184
- if (c.state == null) c.state = {};
185
-
186
- if (c._nextState == null && c.__s == null) {
187
- c._nextState = c.__s = c.state;
188
- }
189
-
190
- c.context = cctx;
191
- if (nodeName.getDerivedStateFromProps)
192
- c.state = assign(
193
- assign({}, c.state),
194
- nodeName.getDerivedStateFromProps(c.props, c.state)
195
- );
196
- else if (c.componentWillMount) {
197
- c.componentWillMount();
198
-
199
- // If the user called setState in cWM we need to flush pending,
200
- // state updates. This is the same behaviour in React.
201
- c.state =
202
- c._nextState !== c.state
203
- ? c._nextState
204
- : c.__s !== c.state
205
- ? c.__s
206
- : c.state;
207
- }
208
-
209
- if (renderHook) renderHook(vnode);
210
-
211
- rendered = c.render(c.props, c.state, c.context);
212
- }
213
-
214
- if (c.getChildContext) {
215
- context = assign(assign({}, context), c.getChildContext());
216
- }
217
-
218
- if (options.diffed) options.diffed(vnode);
219
- return _renderToString(
220
- rendered,
221
- context,
222
- opts,
223
- opts.shallowHighOrder !== false,
224
- isSvgMode,
225
- selectValue
226
- );
227
- }
228
- }
229
-
230
- // render JSX to HTML
231
- let s = '<' + nodeName,
232
- propChildren,
233
- html;
234
-
235
- if (props) {
236
- let attrs = Object.keys(props);
237
-
238
- // allow sorting lexicographically for more determinism (useful for tests, such as via preact-jsx-chai)
239
- if (opts && opts.sortAttributes === true) attrs.sort();
240
-
241
- for (let i = 0; i < attrs.length; i++) {
242
- let name = attrs[i],
243
- v = props[name];
244
- if (name === 'children') {
245
- propChildren = v;
246
- continue;
247
- }
248
-
249
- if (UNSAFE_NAME.test(name)) continue;
250
-
251
- if (
252
- !(opts && opts.allAttributes) &&
253
- (name === 'key' ||
254
- name === 'ref' ||
255
- name === '__self' ||
256
- name === '__source')
257
- )
258
- continue;
259
-
260
- if (name === 'defaultValue') {
261
- name = 'value';
262
- } else if (name === 'defaultChecked') {
263
- name = 'checked';
264
- } else if (name === 'defaultSelected') {
265
- name = 'selected';
266
- } else if (name === 'className') {
267
- if (typeof props.class !== 'undefined') continue;
268
- name = 'class';
269
- } else if (isSvgMode && /^xlink:?./.test(name)) {
270
- name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
271
- }
272
-
273
- if (name === 'htmlFor') {
274
- if (props.for) continue;
275
- name = 'for';
276
- }
277
-
278
- if (name === 'style' && v && typeof v === 'object') {
279
- v = styleObjToCss(v);
280
- }
281
-
282
- // always use string values instead of booleans for aria attributes
283
- // also see https://github.com/preactjs/preact/pull/2347/files
284
- if (name[0] === 'a' && name['1'] === 'r' && typeof v === 'boolean') {
285
- v = String(v);
286
- }
287
-
288
- let hooked =
289
- opts.attributeHook &&
290
- opts.attributeHook(name, v, context, opts, isComponent);
291
- if (hooked || hooked === '') {
292
- s = s + hooked;
293
- continue;
294
- }
295
-
296
- if (name === 'dangerouslySetInnerHTML') {
297
- html = v && v.__html;
298
- } else if (nodeName === 'textarea' && name === 'value') {
299
- // <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
300
- propChildren = v;
301
- } else if ((v || v === 0 || v === '') && typeof v !== 'function') {
302
- if (v === true || v === '') {
303
- v = name;
304
- // in non-xml mode, allow boolean attributes
305
- if (!opts || !opts.xml) {
306
- s = s + ' ' + name;
307
- continue;
308
- }
309
- }
310
-
311
- if (name === 'value') {
312
- if (nodeName === 'select') {
313
- selectValue = v;
314
- continue;
315
- } else if (
316
- // If we're looking at an <option> and it's the currently selected one
317
- nodeName === 'option' &&
318
- selectValue == v &&
319
- // and the <option> doesn't already have a selected attribute on it
320
- typeof props.selected === 'undefined'
321
- ) {
322
- s = s + ` selected`;
323
- }
324
- }
325
- s = s + ` ${name}="${encodeEntities(v)}"`;
326
- }
327
- }
328
- }
329
-
330
- // account for >1 multiline attribute
331
- if (pretty) {
332
- let sub = s.replace(/\n\s*/, ' ');
333
- if (sub !== s && !~sub.indexOf('\n')) s = sub;
334
- else if (pretty && ~s.indexOf('\n')) s = s + '\n';
335
- }
336
-
337
- s = s + '>';
338
-
339
- if (UNSAFE_NAME.test(nodeName))
340
- throw new Error(`${nodeName} is not a valid HTML tag name in ${s}`);
341
-
342
- let isVoid =
343
- VOID_ELEMENTS.test(nodeName) ||
344
- (opts.voidElements && opts.voidElements.test(nodeName));
345
- let pieces = [];
346
-
347
- let children;
348
- if (html) {
349
- // if multiline, indent.
350
- if (pretty && isLargeString(html)) {
351
- html = '\n' + indentChar + indent(html, indentChar);
352
- }
353
- s = s + html;
354
- } else if (
355
- propChildren != null &&
356
- getChildren((children = []), propChildren).length
357
- ) {
358
- let hasLarge = pretty && ~s.indexOf('\n');
359
- let lastWasText = false;
360
-
361
- for (let i = 0; i < children.length; i++) {
362
- let child = children[i];
363
-
364
- if (child != null && child !== false) {
365
- let childSvgMode =
366
- nodeName === 'svg'
367
- ? true
368
- : nodeName === 'foreignObject'
369
- ? false
370
- : isSvgMode,
371
- ret = _renderToString(
372
- child,
373
- context,
374
- opts,
375
- true,
376
- childSvgMode,
377
- selectValue
378
- );
379
-
380
- if (pretty && !hasLarge && isLargeString(ret)) hasLarge = true;
381
-
382
- // Skip if we received an empty string
383
- if (ret) {
384
- if (pretty) {
385
- let isText = ret.length > 0 && ret[0] != '<';
386
-
387
- // We merge adjacent text nodes, otherwise each piece would be printed
388
- // on a new line.
389
- if (lastWasText && isText) {
390
- pieces[pieces.length - 1] += ret;
391
- } else {
392
- pieces.push(ret);
393
- }
394
-
395
- lastWasText = isText;
396
- } else {
397
- pieces.push(ret);
398
- }
399
- }
400
- }
401
- }
402
- if (pretty && hasLarge) {
403
- for (let i = pieces.length; i--; ) {
404
- pieces[i] = '\n' + indentChar + indent(pieces[i], indentChar);
405
- }
406
- }
407
- }
408
-
409
- if (pieces.length || html) {
410
- s = s + pieces.join('');
411
- } else if (opts && opts.xml) {
412
- return s.substring(0, s.length - 1) + ' />';
413
- }
414
-
415
- if (isVoid && !children && !html) {
416
- s = s.replace(/>$/, ' />');
417
- } else {
418
- if (pretty && ~s.indexOf('\n')) s = s + '\n';
419
- s = s + `</${nodeName}>`;
420
- }
421
-
422
- return s;
423
- }
424
-
425
- function getComponentName(component) {
426
- return (
427
- component.displayName ||
428
- (component !== Function && component.name) ||
429
- getFallbackComponentName(component)
430
- );
431
- }
432
-
433
- function getFallbackComponentName(component) {
434
- let str = Function.prototype.toString.call(component),
435
- name = (str.match(/^\s*function\s+([^( ]+)/) || '')[1];
436
- if (!name) {
437
- // search for an existing indexed name for the given component:
438
- let index = -1;
439
- for (let i = UNNAMED.length; i--; ) {
440
- if (UNNAMED[i] === component) {
441
- index = i;
442
- break;
443
- }
444
- }
445
- // not found, create a new indexed name:
446
- if (index < 0) {
447
- index = UNNAMED.push(component) - 1;
448
- }
449
- name = `UnnamedComponent${index}`;
450
- }
451
- return name;
452
- }
453
- renderToString.shallowRender = shallowRender;
454
-
455
- export default renderToString;
456
-
457
- export {
458
- renderToString as render,
459
- renderToString as renderToStaticMarkup,
460
- renderToString,
461
- shallowRender
462
- };
1
+ import {
2
+ encodeEntities,
3
+ styleObjToCss,
4
+ getContext,
5
+ createComponent,
6
+ UNSAFE_NAME,
7
+ XLINK,
8
+ VOID_ELEMENTS
9
+ } from './util';
10
+ import { options, h, Fragment } from 'preact';
11
+ import { _renderToStringPretty } from './pretty';
12
+ import {
13
+ COMMIT,
14
+ COMPONENT,
15
+ DIFF,
16
+ DIFFED,
17
+ DIRTY,
18
+ NEXT_STATE,
19
+ PARENT,
20
+ RENDER,
21
+ SKIP_EFFECTS,
22
+ VNODE,
23
+ CHILDREN
24
+ } from './constants';
25
+
26
+ /** @typedef {import('preact').VNode} VNode */
27
+
28
+ const SHALLOW = { shallow: true };
29
+
30
+ /** Render Preact JSX + Components to an HTML string.
31
+ * @name render
32
+ * @function
33
+ * @param {VNode} vnode JSX VNode to render.
34
+ * @param {Object} [context={}] Optionally pass an initial context object through the render path.
35
+ * @param {Object} [options={}] Rendering options
36
+ * @param {Boolean} [options.shallow=false] If `true`, renders nested Components as HTML elements (`<Foo a="b" />`).
37
+ * @param {Boolean} [options.xml=false] If `true`, uses self-closing tags for elements without children.
38
+ * @param {Boolean} [options.pretty=false] If `true`, adds whitespace for readability
39
+ * @param {RegExp|undefined} [options.voidElements] RegeEx that matches elements that are considered void (self-closing)
40
+ */
41
+ renderToString.render = renderToString;
42
+
43
+ /** Only render elements, leaving Components inline as `<ComponentName ... />`.
44
+ * This method is just a convenience alias for `render(vnode, context, { shallow:true })`
45
+ * @name shallow
46
+ * @function
47
+ * @param {VNode} vnode JSX VNode to render.
48
+ * @param {Object} [context={}] Optionally pass an initial context object through the render path.
49
+ */
50
+ let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
51
+
52
+ const EMPTY_ARR = [];
53
+ function renderToString(vnode, context, opts) {
54
+ context = context || {};
55
+
56
+ // Performance optimization: `renderToString` is synchronous and we
57
+ // therefore don't execute any effects. To do that we pass an empty
58
+ // array to `options._commit` (`__c`). But we can go one step further
59
+ // and avoid a lot of dirty checks and allocations by setting
60
+ // `options._skipEffects` (`__s`) too.
61
+ const previousSkipEffects = options[SKIP_EFFECTS];
62
+ options[SKIP_EFFECTS] = true;
63
+
64
+ const parent = h(Fragment, null);
65
+ parent[CHILDREN] = [vnode];
66
+
67
+ let res;
68
+ if (
69
+ opts &&
70
+ (opts.pretty ||
71
+ opts.voidElements ||
72
+ opts.sortAttributes ||
73
+ opts.shallow ||
74
+ opts.allAttributes ||
75
+ opts.xml ||
76
+ opts.attributeHook)
77
+ ) {
78
+ res = _renderToStringPretty(vnode, context, opts);
79
+ } else {
80
+ res = _renderToString(vnode, context, false, undefined, parent);
81
+ }
82
+
83
+ // options._commit, we don't schedule any effects in this library right now,
84
+ // so we can pass an empty queue to this hook.
85
+ if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
86
+ options[SKIP_EFFECTS] = previousSkipEffects;
87
+ EMPTY_ARR.length = 0;
88
+
89
+ return res;
90
+ }
91
+
92
+ function renderFunctionComponent(vnode, context) {
93
+ let rendered,
94
+ c = createComponent(vnode, context),
95
+ cctx = getContext(vnode.type, context);
96
+
97
+ vnode[COMPONENT] = c;
98
+
99
+ // If a hook invokes setState() to invalidate the component during rendering,
100
+ // re-render it up to 25 times to allow "settling" of memoized states.
101
+ // Note:
102
+ // This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
103
+ // https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
104
+ let renderHook = options[RENDER];
105
+ let count = 0;
106
+ while (c[DIRTY] && count++ < 25) {
107
+ c[DIRTY] = false;
108
+
109
+ if (renderHook) renderHook(vnode);
110
+
111
+ // stateless functional components
112
+ rendered = vnode.type.call(c, vnode.props, cctx);
113
+ }
114
+
115
+ return rendered;
116
+ }
117
+
118
+ function renderClassComponent(vnode, context) {
119
+ let nodeName = vnode.type,
120
+ cctx = getContext(nodeName, context);
121
+
122
+ // c = new nodeName(props, context);
123
+ let c = new nodeName(vnode.props, cctx);
124
+ vnode[COMPONENT] = c;
125
+ c[VNODE] = vnode;
126
+ // turn off stateful re-rendering:
127
+ c[DIRTY] = true;
128
+ c.props = vnode.props;
129
+ if (c.state == null) c.state = {};
130
+
131
+ if (c[NEXT_STATE] == null) {
132
+ c[NEXT_STATE] = c.state;
133
+ }
134
+
135
+ c.context = cctx;
136
+ if (nodeName.getDerivedStateFromProps) {
137
+ c.state = assign(
138
+ {},
139
+ c.state,
140
+ nodeName.getDerivedStateFromProps(c.props, c.state)
141
+ );
142
+ } else if (c.componentWillMount) {
143
+ c.componentWillMount();
144
+
145
+ // If the user called setState in cWM we need to flush pending,
146
+ // state updates. This is the same behaviour in React.
147
+ c.state = c[NEXT_STATE] !== c.state ? c[NEXT_STATE] : c.state;
148
+ }
149
+
150
+ let renderHook = options[RENDER];
151
+ if (renderHook) renderHook(vnode);
152
+
153
+ return c.render(c.props, c.state, c.context);
154
+ }
155
+
156
+ function normalizePropName(name, isSvgMode) {
157
+ if (name === 'className') {
158
+ return 'class';
159
+ } else if (name === 'htmlFor') {
160
+ return 'for';
161
+ } else if (name === 'defaultValue') {
162
+ return 'value';
163
+ } else if (name === 'defaultChecked') {
164
+ return 'checked';
165
+ } else if (name === 'defaultSelected') {
166
+ return 'selected';
167
+ } else if (isSvgMode && XLINK.test(name)) {
168
+ return name.toLowerCase().replace(/^xlink:?/, 'xlink:');
169
+ }
170
+
171
+ return name;
172
+ }
173
+
174
+ function normalizePropValue(name, v) {
175
+ if (name === 'style' && v != null && typeof v === 'object') {
176
+ return styleObjToCss(v);
177
+ } else if (name[0] === 'a' && name[1] === 'r' && typeof v === 'boolean') {
178
+ // always use string values instead of booleans for aria attributes
179
+ // also see https://github.com/preactjs/preact/pull/2347/files
180
+ return String(v);
181
+ }
182
+
183
+ return v;
184
+ }
185
+
186
+ const isArray = Array.isArray;
187
+ const assign = Object.assign;
188
+
189
+ /** The default export is an alias of `render()`. */
190
+ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
191
+ // Ignore non-rendered VNodes/values
192
+ if (vnode == null || vnode === true || vnode === false || vnode === '') {
193
+ return '';
194
+ }
195
+
196
+ // Text VNodes: escape as HTML
197
+ if (typeof vnode !== 'object') {
198
+ return encodeEntities(vnode);
199
+ }
200
+
201
+ // Recurse into children / Arrays
202
+ if (isArray(vnode)) {
203
+ let rendered = '';
204
+ parent[CHILDREN] = vnode;
205
+ for (let i = 0; i < vnode.length; i++) {
206
+ rendered =
207
+ rendered +
208
+ _renderToString(vnode[i], context, isSvgMode, selectValue, parent);
209
+ }
210
+ return rendered;
211
+ }
212
+
213
+ vnode[PARENT] = parent;
214
+ if (options[DIFF]) options[DIFF](vnode);
215
+
216
+ let type = vnode.type,
217
+ props = vnode.props;
218
+
219
+ // Invoke rendering on Components
220
+ const isComponent = typeof type === 'function';
221
+ if (isComponent) {
222
+ let rendered;
223
+ if (type === Fragment) {
224
+ rendered = props.children;
225
+ } else {
226
+ if (type.prototype && typeof type.prototype.render === 'function') {
227
+ rendered = renderClassComponent(vnode, context);
228
+ } else {
229
+ rendered = renderFunctionComponent(vnode, context);
230
+ }
231
+
232
+ let component = vnode[COMPONENT];
233
+ if (component.getChildContext) {
234
+ context = assign({}, context, component.getChildContext());
235
+ }
236
+ }
237
+
238
+ // Recurse into children before invoking the after-diff hook
239
+ const str = _renderToString(
240
+ rendered,
241
+ context,
242
+ isSvgMode,
243
+ selectValue,
244
+ vnode
245
+ );
246
+
247
+ if (options[DIFFED]) options[DIFFED](vnode);
248
+ vnode[PARENT] = undefined;
249
+
250
+ if (options.unmount) options.unmount(vnode);
251
+
252
+ return str;
253
+ }
254
+
255
+ // Serialize Element VNodes to HTML
256
+ let s = '<',
257
+ children,
258
+ html;
259
+
260
+ s = s + type;
261
+
262
+ if (props) {
263
+ children = props.children;
264
+ for (let name in props) {
265
+ let v = props[name];
266
+
267
+ if (
268
+ name === 'key' ||
269
+ name === 'ref' ||
270
+ name === '__self' ||
271
+ name === '__source' ||
272
+ name === 'children' ||
273
+ (name === 'className' && 'class' in props) ||
274
+ (name === 'htmlFor' && 'for' in props)
275
+ ) {
276
+ continue;
277
+ }
278
+
279
+ if (UNSAFE_NAME.test(name)) continue;
280
+
281
+ name = normalizePropName(name, isSvgMode);
282
+ v = normalizePropValue(name, v);
283
+
284
+ if (name === 'dangerouslySetInnerHTML') {
285
+ html = v && v.__html;
286
+ } else if (type === 'textarea' && name === 'value') {
287
+ // <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
288
+ children = v;
289
+ } else if ((v || v === 0 || v === '') && typeof v !== 'function') {
290
+ if (v === true || v === '') {
291
+ v = name;
292
+ s = s + ' ' + name;
293
+ continue;
294
+ }
295
+
296
+ if (name === 'value') {
297
+ if (type === 'select') {
298
+ selectValue = v;
299
+ continue;
300
+ } else if (
301
+ // If we're looking at an <option> and it's the currently selected one
302
+ type === 'option' &&
303
+ selectValue == v &&
304
+ // and the <option> doesn't already have a selected attribute on it
305
+ !('selected' in props)
306
+ ) {
307
+ s = s + ' selected';
308
+ }
309
+ }
310
+ s = s + ' ' + name + '="' + encodeEntities(v) + '"';
311
+ }
312
+ }
313
+ }
314
+
315
+ let startElement = s;
316
+ s = s + '>';
317
+
318
+ if (UNSAFE_NAME.test(type)) {
319
+ throw new Error(`${type} is not a valid HTML tag name in ${s}`);
320
+ }
321
+
322
+ let pieces = '';
323
+ let hasChildren = false;
324
+
325
+ if (html) {
326
+ pieces = pieces + html;
327
+ hasChildren = true;
328
+ } else if (typeof children === 'string') {
329
+ pieces = pieces + encodeEntities(children);
330
+ hasChildren = true;
331
+ } else if (isArray(children)) {
332
+ vnode[CHILDREN] = children;
333
+ for (let i = 0; i < children.length; i++) {
334
+ let child = children[i];
335
+ if (child != null && child !== false) {
336
+ let childSvgMode =
337
+ type === 'svg' || (type !== 'foreignObject' && isSvgMode);
338
+ let ret = _renderToString(
339
+ child,
340
+ context,
341
+ childSvgMode,
342
+ selectValue,
343
+ vnode
344
+ );
345
+
346
+ // Skip if we received an empty string
347
+ if (ret) {
348
+ pieces = pieces + ret;
349
+ hasChildren = true;
350
+ }
351
+ }
352
+ }
353
+ } else if (children != null && children !== false && children !== true) {
354
+ vnode[CHILDREN] = [children];
355
+ let childSvgMode =
356
+ type === 'svg' || (type !== 'foreignObject' && isSvgMode);
357
+ let ret = _renderToString(
358
+ children,
359
+ context,
360
+ childSvgMode,
361
+ selectValue,
362
+ vnode
363
+ );
364
+
365
+ // Skip if we received an empty string
366
+ if (ret) {
367
+ pieces = pieces + ret;
368
+ hasChildren = true;
369
+ }
370
+ }
371
+
372
+ if (options[DIFFED]) options[DIFFED](vnode);
373
+ vnode[PARENT] = undefined;
374
+ if (options.unmount) options.unmount(vnode);
375
+
376
+ if (hasChildren) {
377
+ s = s + pieces;
378
+ } else if (VOID_ELEMENTS.test(type)) {
379
+ return startElement + ' />';
380
+ }
381
+
382
+ return s + '</' + type + '>';
383
+ }
384
+
385
+ /** The default export is an alias of `render()`. */
386
+
387
+ renderToString.shallowRender = shallowRender;
388
+
389
+ export default renderToString;
390
+
391
+ export {
392
+ renderToString as render,
393
+ renderToString as renderToStaticMarkup,
394
+ renderToString,
395
+ shallowRender
396
+ };