preact-render-to-string 6.0.0-experimental.0 → 6.0.1

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