preact-render-to-string 5.2.2 → 5.2.3

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,28 +1,30 @@
1
1
  import {
2
2
  encodeEntities,
3
- indent,
4
- isLargeString,
5
3
  styleObjToCss,
6
- getChildren
4
+ getContext,
5
+ createComponent,
6
+ UNSAFE_NAME,
7
+ XLINK,
8
+ VOID_ELEMENTS
7
9
  } from './util';
8
10
  import { options, Fragment } from 'preact';
11
+ import { _renderToStringPretty } from './pretty';
12
+ import {
13
+ COMMIT,
14
+ COMPONENT,
15
+ DIFF,
16
+ DIFFED,
17
+ DIRTY,
18
+ NEXT_STATE,
19
+ RENDER,
20
+ SKIP_EFFECTS,
21
+ VNODE
22
+ } from './constants';
9
23
 
10
24
  /** @typedef {import('preact').VNode} VNode */
11
25
 
12
26
  const SHALLOW = { shallow: true };
13
27
 
14
- // components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
15
- const UNNAMED = [];
16
-
17
- const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
18
-
19
- const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
20
- const XLINK = /^xlink:?./;
21
-
22
- function markAsDirty() {
23
- this.__d = true;
24
- }
25
-
26
28
  /** Render Preact JSX + Components to an HTML string.
27
29
  * @name render
28
30
  * @function
@@ -48,705 +50,310 @@ let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
48
50
  const EMPTY_ARR = [];
49
51
  function renderToString(vnode, context, opts) {
50
52
  context = context || {};
51
- opts = opts || {};
52
53
 
53
54
  // Performance optimization: `renderToString` is synchronous and we
54
55
  // therefore don't execute any effects. To do that we pass an empty
55
56
  // array to `options._commit` (`__c`). But we can go one step further
56
57
  // and avoid a lot of dirty checks and allocations by setting
57
58
  // `options._skipEffects` (`__s`) too.
58
- const previousSkipEffects = options.__s;
59
- options.__s = true;
59
+ const previousSkipEffects = options[SKIP_EFFECTS];
60
+ options[SKIP_EFFECTS] = true;
60
61
 
61
62
  let res;
62
- if (opts.pretty || opts.sortAttributes) {
63
+ if (
64
+ opts &&
65
+ (opts.pretty ||
66
+ opts.voidElements ||
67
+ opts.sortAttributes ||
68
+ opts.shallow ||
69
+ opts.allAttributes ||
70
+ opts.xml ||
71
+ opts.attributeHook)
72
+ ) {
63
73
  res = _renderToStringPretty(vnode, context, opts);
64
74
  } else {
65
- res = _renderToString(vnode, context, opts);
75
+ res = _renderToString(vnode, context, false, undefined);
66
76
  }
67
77
 
68
78
  // options._commit, we don't schedule any effects in this library right now,
69
79
  // so we can pass an empty queue to this hook.
70
- if (options.__c) options.__c(vnode, EMPTY_ARR);
80
+ if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
81
+ options[SKIP_EFFECTS] = previousSkipEffects;
71
82
  EMPTY_ARR.length = 0;
72
- options.__s = previousSkipEffects;
73
83
  return res;
74
84
  }
75
85
 
76
- function createComponent(vnode, context) {
77
- return {
78
- __v: vnode,
79
- context,
80
- props: vnode.props,
81
- // silently drop state updates
82
- setState: markAsDirty,
83
- forceUpdate: markAsDirty,
84
- __d: true,
85
- // hooks
86
- __h: []
87
- };
88
- }
86
+ function renderFunctionComponent(vnode, context) {
87
+ let rendered,
88
+ c = createComponent(vnode, context),
89
+ cctx = getContext(vnode.type, context);
89
90
 
90
- // Necessary for createContext api. Setting this property will pass
91
- // the context value as `this.context` just for this component.
92
- function getContext(nodeName, context) {
93
- let cxType = nodeName.contextType;
94
- let provider = cxType && context[cxType.__c];
95
- return cxType != null
96
- ? provider
97
- ? provider.props.value
98
- : cxType.__
99
- : context;
100
- }
91
+ vnode[COMPONENT] = c;
101
92
 
102
- /** The default export is an alias of `render()`. */
103
- function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
104
- if (vnode == null || typeof vnode === 'boolean') {
105
- return '';
106
- }
93
+ // If a hook invokes setState() to invalidate the component during rendering,
94
+ // re-render it up to 25 times to allow "settling" of memoized states.
95
+ // Note:
96
+ // This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
97
+ // https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
98
+ let renderHook = options[RENDER];
99
+ let count = 0;
100
+ while (c[DIRTY] && count++ < 25) {
101
+ c[DIRTY] = false;
107
102
 
108
- // #text nodes
109
- if (typeof vnode !== 'object') {
110
- return encodeEntities(vnode);
111
- }
103
+ if (renderHook) renderHook(vnode);
112
104
 
113
- if (Array.isArray(vnode)) {
114
- const rendered = [];
115
- for (let i = 0; i < vnode.length; i++) {
116
- rendered.push(
117
- _renderToString(vnode[i], context, opts, inner, isSvgMode, selectValue)
118
- );
119
- }
120
- return rendered.join('');
105
+ // stateless functional components
106
+ rendered = vnode.type.call(c, vnode.props, cctx);
121
107
  }
122
108
 
123
- let nodeName = vnode.type,
124
- props = vnode.props,
125
- isComponent = false;
126
-
127
- // components
128
- if (typeof nodeName === 'function') {
129
- isComponent = true;
130
- if (opts.shallow && (inner || opts.renderRootComponent === false)) {
131
- nodeName = getComponentName(nodeName);
132
- } else if (nodeName === Fragment) {
133
- const children = [];
134
- getChildren(children, vnode.props.children);
135
- return _renderToString(
136
- children,
137
- context,
138
- opts,
139
- opts.shallowHighOrder !== false,
140
- isSvgMode,
141
- selectValue
142
- );
143
- } else {
144
- let rendered;
145
-
146
- let c = (vnode.__c = createComponent(vnode, context));
147
-
148
- // options._diff
149
- if (options.__b) options.__b(vnode);
150
-
151
- // options._render
152
- let renderHook = options.__r;
153
-
154
- if (
155
- !nodeName.prototype ||
156
- typeof nodeName.prototype.render !== 'function'
157
- ) {
158
- let cctx = getContext(nodeName, context);
159
-
160
- // If a hook invokes setState() to invalidate the component during rendering,
161
- // re-render it up to 25 times to allow "settling" of memoized states.
162
- // Note:
163
- // This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
164
- // https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
165
- let count = 0;
166
- while (c.__d && count++ < 25) {
167
- c.__d = false;
168
-
169
- if (renderHook) renderHook(vnode);
170
-
171
- // stateless functional components
172
- rendered = nodeName.call(vnode.__c, props, cctx);
173
- }
174
- } else {
175
- let cctx = getContext(nodeName, context);
176
-
177
- // c = new nodeName(props, context);
178
- c = vnode.__c = new nodeName(props, cctx);
179
- c.__v = vnode;
180
- // turn off stateful re-rendering:
181
- c._dirty = c.__d = true;
182
- c.props = props;
183
- if (c.state == null) c.state = {};
184
-
185
- if (c._nextState == null && c.__s == null) {
186
- c._nextState = c.__s = c.state;
187
- }
188
-
189
- c.context = cctx;
190
- if (nodeName.getDerivedStateFromProps)
191
- c.state = Object.assign(
192
- {},
193
- 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 = Object.assign({}, context, c.getChildContext());
216
- }
109
+ return rendered;
110
+ }
217
111
 
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
- }
112
+ function renderClassComponent(vnode, context) {
113
+ let nodeName = vnode.type,
114
+ cctx = getContext(nodeName, context);
115
+
116
+ // c = new nodeName(props, context);
117
+ let c = new nodeName(vnode.props, cctx);
118
+ vnode[COMPONENT] = c;
119
+ c[VNODE] = vnode;
120
+ // turn off stateful re-rendering:
121
+ c[DIRTY] = true;
122
+ c.props = vnode.props;
123
+ if (c.state == null) c.state = {};
124
+
125
+ if (c[NEXT_STATE] == null) {
126
+ c[NEXT_STATE] = c.state;
228
127
  }
229
128
 
230
- // render JSX to HTML
231
- let s = `<${nodeName}`,
232
- propChildren,
233
- html;
234
-
235
- if (props) {
236
- for (let name in props) {
237
- let v = props[name];
238
- if (name === 'children') {
239
- propChildren = v;
240
- continue;
241
- }
242
-
243
- if (UNSAFE_NAME.test(name)) continue;
244
-
245
- if (
246
- !(opts && opts.allAttributes) &&
247
- (name === 'key' ||
248
- name === 'ref' ||
249
- name === '__self' ||
250
- name === '__source')
251
- )
252
- continue;
253
-
254
- if (name === 'defaultValue') {
255
- name = 'value';
256
- } else if (name === 'defaultChecked') {
257
- name = 'checked';
258
- } else if (name === 'defaultSelected') {
259
- name = 'selected';
260
- } else if (name === 'className') {
261
- if (typeof props.class !== 'undefined') continue;
262
- name = 'class';
263
- } else if (isSvgMode && XLINK.test(name)) {
264
- name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
265
- }
266
-
267
- if (name === 'htmlFor') {
268
- if (props.for) continue;
269
- name = 'for';
270
- }
271
-
272
- if (name === 'style' && v && typeof v === 'object') {
273
- v = styleObjToCss(v);
274
- }
275
-
276
- // always use string values instead of booleans for aria attributes
277
- // also see https://github.com/preactjs/preact/pull/2347/files
278
- if (name[0] === 'a' && name['1'] === 'r' && typeof v === 'boolean') {
279
- v = String(v);
280
- }
281
-
282
- let hooked =
283
- opts.attributeHook &&
284
- opts.attributeHook(name, v, context, opts, isComponent);
285
- if (hooked || hooked === '') {
286
- s = s + hooked;
287
- continue;
288
- }
289
-
290
- if (name === 'dangerouslySetInnerHTML') {
291
- html = v && v.__html;
292
- } else if (nodeName === 'textarea' && name === 'value') {
293
- // <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
294
- propChildren = v;
295
- } else if ((v || v === 0 || v === '') && typeof v !== 'function') {
296
- if (v === true || v === '') {
297
- v = name;
298
- // in non-xml mode, allow boolean attributes
299
- if (!opts || !opts.xml) {
300
- s = s + ' ' + name;
301
- continue;
302
- }
303
- }
304
-
305
- if (name === 'value') {
306
- if (nodeName === 'select') {
307
- selectValue = v;
308
- continue;
309
- } else if (
310
- // If we're looking at an <option> and it's the currently selected one
311
- nodeName === 'option' &&
312
- selectValue == v &&
313
- // and the <option> doesn't already have a selected attribute on it
314
- typeof props.selected === 'undefined'
315
- ) {
316
- s = `${s} selected`;
317
- }
318
- }
319
- s = s + ` ${name}="${encodeEntities(v)}"`;
320
- }
321
- }
129
+ c.context = cctx;
130
+ if (nodeName.getDerivedStateFromProps) {
131
+ c.state = assign(
132
+ {},
133
+ c.state,
134
+ nodeName.getDerivedStateFromProps(c.props, c.state)
135
+ );
136
+ } else if (c.componentWillMount) {
137
+ c.componentWillMount();
138
+
139
+ // If the user called setState in cWM we need to flush pending,
140
+ // state updates. This is the same behaviour in React.
141
+ c.state = c[NEXT_STATE] !== c.state ? c[NEXT_STATE] : c.state;
322
142
  }
323
143
 
324
- s = s + '>';
325
-
326
- if (UNSAFE_NAME.test(nodeName))
327
- throw new Error(`${nodeName} is not a valid HTML tag name in ${s}`);
328
-
329
- let isVoid =
330
- VOID_ELEMENTS.test(nodeName) ||
331
- (opts.voidElements && opts.voidElements.test(nodeName));
332
- let pieces = '';
333
-
334
- let children;
335
- if (html) {
336
- s = s + html;
337
- } else if (
338
- propChildren != null &&
339
- getChildren((children = []), propChildren).length
340
- ) {
341
- for (let i = 0; i < children.length; i++) {
342
- let child = children[i];
144
+ let renderHook = options[RENDER];
145
+ if (renderHook) renderHook(vnode);
343
146
 
344
- if (child != null && child !== false) {
345
- let childSvgMode =
346
- nodeName === 'svg'
347
- ? true
348
- : nodeName === 'foreignObject'
349
- ? false
350
- : isSvgMode,
351
- ret = _renderToString(
352
- child,
353
- context,
354
- opts,
355
- true,
356
- childSvgMode,
357
- selectValue
358
- );
147
+ return c.render(c.props, c.state, c.context);
148
+ }
359
149
 
360
- // Skip if we received an empty string
361
- if (ret) {
362
- pieces = pieces + ret;
363
- }
364
- }
365
- }
150
+ function normalizePropName(name, isSvgMode) {
151
+ if (name === 'className') {
152
+ return 'class';
153
+ } else if (name === 'htmlFor') {
154
+ return 'for';
155
+ } else if (name === 'defaultValue') {
156
+ return 'value';
157
+ } else if (name === 'defaultChecked') {
158
+ return 'checked';
159
+ } else if (name === 'defaultSelected') {
160
+ return 'selected';
161
+ } else if (isSvgMode && XLINK.test(name)) {
162
+ return name.toLowerCase().replace(/^xlink:?/, 'xlink:');
366
163
  }
367
164
 
368
- if (pieces.length || html) {
369
- s = s + pieces;
370
- } else if (opts && opts.xml) {
371
- return s.substring(0, s.length - 1) + ' />';
372
- }
165
+ return name;
166
+ }
373
167
 
374
- if (isVoid && !children && !html) {
375
- return s.replace(/>$/, ' />');
168
+ function normalizePropValue(name, v) {
169
+ if (name === 'style' && v != null && typeof v === 'object') {
170
+ return styleObjToCss(v);
171
+ } else if (name[0] === 'a' && name[1] === 'r' && typeof v === 'boolean') {
172
+ // always use string values instead of booleans for aria attributes
173
+ // also see https://github.com/preactjs/preact/pull/2347/files
174
+ return String(v);
376
175
  }
377
176
 
378
- return `${s}</${nodeName}>`;
177
+ return v;
379
178
  }
380
179
 
180
+ const isArray = Array.isArray;
181
+ const assign = Object.assign;
182
+
381
183
  /** The default export is an alias of `render()`. */
382
- function _renderToStringPretty(
383
- vnode,
384
- context,
385
- opts,
386
- inner,
387
- isSvgMode,
388
- selectValue
389
- ) {
390
- if (vnode == null || typeof vnode === 'boolean') {
184
+ function _renderToString(vnode, context, isSvgMode, selectValue) {
185
+ // Ignore non-rendered VNodes/values
186
+ if (vnode == null || vnode === true || vnode === false || vnode === '') {
391
187
  return '';
392
188
  }
393
189
 
394
- // #text nodes
190
+ // Text VNodes: escape as HTML
395
191
  if (typeof vnode !== 'object') {
396
192
  return encodeEntities(vnode);
397
193
  }
398
194
 
399
- let pretty = opts.pretty,
400
- indentChar = pretty && typeof pretty === 'string' ? pretty : '\t';
401
-
402
- if (Array.isArray(vnode)) {
195
+ // Recurse into children / Arrays
196
+ if (isArray(vnode)) {
403
197
  let rendered = '';
404
198
  for (let i = 0; i < vnode.length; i++) {
405
- if (pretty && i > 0) rendered = rendered + '\n';
406
199
  rendered =
407
- rendered +
408
- _renderToStringPretty(
409
- vnode[i],
410
- context,
411
- opts,
412
- inner,
413
- isSvgMode,
414
- selectValue
415
- );
200
+ rendered + _renderToString(vnode[i], context, isSvgMode, selectValue);
416
201
  }
417
202
  return rendered;
418
203
  }
419
204
 
420
- let nodeName = vnode.type,
421
- props = vnode.props,
422
- isComponent = false;
423
-
424
- // components
425
- if (typeof nodeName === 'function') {
426
- isComponent = true;
427
- if (opts.shallow && (inner || opts.renderRootComponent === false)) {
428
- nodeName = getComponentName(nodeName);
429
- } else if (nodeName === Fragment) {
430
- const children = [];
431
- getChildren(children, vnode.props.children);
432
- return _renderToStringPretty(
433
- children,
434
- context,
435
- opts,
436
- opts.shallowHighOrder !== false,
437
- isSvgMode,
438
- selectValue
439
- );
440
- } else {
441
- let rendered;
442
-
443
- let c = (vnode.__c = createComponent(vnode, context));
444
-
445
- // options._diff
446
- if (options.__b) options.__b(vnode);
447
-
448
- // options._render
449
- let renderHook = options.__r;
450
-
451
- if (
452
- !nodeName.prototype ||
453
- typeof nodeName.prototype.render !== 'function'
454
- ) {
455
- let cctx = getContext(nodeName, context);
205
+ if (options[DIFF]) options[DIFF](vnode);
456
206
 
457
- // If a hook invokes setState() to invalidate the component during rendering,
458
- // re-render it up to 25 times to allow "settling" of memoized states.
459
- // Note:
460
- // This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
461
- // https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
462
- let count = 0;
463
- while (c.__d && count++ < 25) {
464
- c.__d = false;
207
+ let type = vnode.type,
208
+ props = vnode.props;
465
209
 
466
- if (renderHook) renderHook(vnode);
467
-
468
- // stateless functional components
469
- rendered = nodeName.call(vnode.__c, props, cctx);
470
- }
471
- } else {
472
- let cctx = getContext(nodeName, context);
473
-
474
- // c = new nodeName(props, context);
475
- c = vnode.__c = new nodeName(props, cctx);
476
- c.__v = vnode;
477
- // turn off stateful re-rendering:
478
- c._dirty = c.__d = true;
479
- c.props = props;
480
- if (c.state == null) c.state = {};
481
-
482
- if (c._nextState == null && c.__s == null) {
483
- c._nextState = c.__s = c.state;
484
- }
485
-
486
- c.context = cctx;
487
- if (nodeName.getDerivedStateFromProps)
488
- c.state = Object.assign(
489
- {},
490
- c.state,
491
- nodeName.getDerivedStateFromProps(c.props, c.state)
492
- );
493
- else if (c.componentWillMount) {
494
- c.componentWillMount();
495
-
496
- // If the user called setState in cWM we need to flush pending,
497
- // state updates. This is the same behaviour in React.
498
- c.state =
499
- c._nextState !== c.state
500
- ? c._nextState
501
- : c.__s !== c.state
502
- ? c.__s
503
- : c.state;
504
- }
505
-
506
- if (renderHook) renderHook(vnode);
507
-
508
- rendered = c.render(c.props, c.state, c.context);
509
- }
510
-
511
- if (c.getChildContext) {
512
- context = Object.assign({}, context, c.getChildContext());
513
- }
514
-
515
- if (options.diffed) options.diffed(vnode);
516
- return _renderToStringPretty(
517
- rendered,
210
+ // Invoke rendering on Components
211
+ const isComponent = typeof type === 'function';
212
+ if (isComponent) {
213
+ if (type === Fragment) {
214
+ return _renderToString(
215
+ vnode.props.children,
518
216
  context,
519
- opts,
520
- opts.shallowHighOrder !== false,
521
217
  isSvgMode,
522
218
  selectValue
523
219
  );
524
220
  }
525
- }
526
221
 
527
- // render JSX to HTML
528
- let s = '<' + nodeName,
529
- propChildren,
530
- html;
222
+ let rendered;
223
+ if (type.prototype && typeof type.prototype.render === 'function') {
224
+ rendered = renderClassComponent(vnode, context);
225
+ } else {
226
+ rendered = renderFunctionComponent(vnode, context);
227
+ }
531
228
 
532
- if (props) {
533
- let attrs = Object.keys(props);
229
+ let component = vnode[COMPONENT];
230
+ if (component.getChildContext) {
231
+ context = assign({}, context, component.getChildContext());
232
+ }
534
233
 
535
- // allow sorting lexicographically for more determinism (useful for tests, such as via preact-jsx-chai)
536
- if (opts && opts.sortAttributes === true) attrs.sort();
234
+ // Recurse into children before invoking the after-diff hook
235
+ const str = _renderToString(rendered, context, isSvgMode, selectValue);
236
+ if (options[DIFFED]) options[DIFFED](vnode);
237
+ return str;
238
+ }
537
239
 
538
- for (let i = 0; i < attrs.length; i++) {
539
- let name = attrs[i],
540
- v = props[name];
541
- if (name === 'children') {
542
- propChildren = v;
543
- continue;
544
- }
240
+ // Serialize Element VNodes to HTML
241
+ let s = '<',
242
+ children,
243
+ html;
545
244
 
546
- if (UNSAFE_NAME.test(name)) continue;
245
+ s = s + type;
246
+
247
+ if (props) {
248
+ children = props.children;
249
+ for (let name in props) {
250
+ let v = props[name];
547
251
 
548
252
  if (
549
- !(opts && opts.allAttributes) &&
550
- (name === 'key' ||
551
- name === 'ref' ||
552
- name === '__self' ||
553
- name === '__source')
554
- )
253
+ name === 'key' ||
254
+ name === 'ref' ||
255
+ name === '__self' ||
256
+ name === '__source' ||
257
+ name === 'children' ||
258
+ (name === 'className' && 'class' in props) ||
259
+ (name === 'htmlFor' && 'for' in props)
260
+ ) {
555
261
  continue;
556
-
557
- if (name === 'defaultValue') {
558
- name = 'value';
559
- } else if (name === 'defaultChecked') {
560
- name = 'checked';
561
- } else if (name === 'defaultSelected') {
562
- name = 'selected';
563
- } else if (name === 'className') {
564
- if (typeof props.class !== 'undefined') continue;
565
- name = 'class';
566
- } else if (isSvgMode && XLINK.test(name)) {
567
- name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
568
- }
569
-
570
- if (name === 'htmlFor') {
571
- if (props.for) continue;
572
- name = 'for';
573
- }
574
-
575
- if (name === 'style' && v && typeof v === 'object') {
576
- v = styleObjToCss(v);
577
262
  }
578
263
 
579
- // always use string values instead of booleans for aria attributes
580
- // also see https://github.com/preactjs/preact/pull/2347/files
581
- if (name[0] === 'a' && name['1'] === 'r' && typeof v === 'boolean') {
582
- v = String(v);
583
- }
264
+ if (UNSAFE_NAME.test(name)) continue;
584
265
 
585
- let hooked =
586
- opts.attributeHook &&
587
- opts.attributeHook(name, v, context, opts, isComponent);
588
- if (hooked || hooked === '') {
589
- s = s + hooked;
590
- continue;
591
- }
266
+ name = normalizePropName(name, isSvgMode);
267
+ v = normalizePropValue(name, v);
592
268
 
593
269
  if (name === 'dangerouslySetInnerHTML') {
594
270
  html = v && v.__html;
595
- } else if (nodeName === 'textarea' && name === 'value') {
271
+ } else if (type === 'textarea' && name === 'value') {
596
272
  // <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
597
- propChildren = v;
273
+ children = v;
598
274
  } else if ((v || v === 0 || v === '') && typeof v !== 'function') {
599
275
  if (v === true || v === '') {
600
276
  v = name;
601
- // in non-xml mode, allow boolean attributes
602
- if (!opts || !opts.xml) {
603
- s = s + ' ' + name;
604
- continue;
605
- }
277
+ s = s + ' ' + name;
278
+ continue;
606
279
  }
607
280
 
608
281
  if (name === 'value') {
609
- if (nodeName === 'select') {
282
+ if (type === 'select') {
610
283
  selectValue = v;
611
284
  continue;
612
285
  } else if (
613
286
  // If we're looking at an <option> and it's the currently selected one
614
- nodeName === 'option' &&
287
+ type === 'option' &&
615
288
  selectValue == v &&
616
289
  // and the <option> doesn't already have a selected attribute on it
617
- typeof props.selected === 'undefined'
290
+ !('selected' in props)
618
291
  ) {
619
- s = s + ` selected`;
292
+ s = s + ' selected';
620
293
  }
621
294
  }
622
- s = s + ` ${name}="${encodeEntities(v)}"`;
295
+ s = s + ' ' + name + '="' + encodeEntities(v) + '"';
623
296
  }
624
297
  }
625
298
  }
626
299
 
627
- // account for >1 multiline attribute
628
- if (pretty) {
629
- let sub = s.replace(/\n\s*/, ' ');
630
- if (sub !== s && !~sub.indexOf('\n')) s = sub;
631
- else if (pretty && ~s.indexOf('\n')) s = s + '\n';
632
- }
633
-
300
+ let startElement = s;
634
301
  s = s + '>';
635
302
 
636
- if (UNSAFE_NAME.test(nodeName))
637
- throw new Error(`${nodeName} is not a valid HTML tag name in ${s}`);
303
+ if (UNSAFE_NAME.test(type)) {
304
+ throw new Error(`${type} is not a valid HTML tag name in ${s}`);
305
+ }
638
306
 
639
- let isVoid =
640
- VOID_ELEMENTS.test(nodeName) ||
641
- (opts.voidElements && opts.voidElements.test(nodeName));
642
- let pieces = [];
307
+ let pieces = '';
308
+ let hasChildren = false;
643
309
 
644
- let children;
645
310
  if (html) {
646
- // if multiline, indent.
647
- if (pretty && isLargeString(html)) {
648
- html = '\n' + indentChar + indent(html, indentChar);
649
- }
650
- s = s + html;
651
- } else if (
652
- propChildren != null &&
653
- getChildren((children = []), propChildren).length
654
- ) {
655
- let hasLarge = pretty && ~s.indexOf('\n');
656
- let lastWasText = false;
657
-
311
+ pieces = pieces + html;
312
+ hasChildren = true;
313
+ } else if (typeof children === 'string') {
314
+ pieces = pieces + encodeEntities(children);
315
+ hasChildren = true;
316
+ } else if (isArray(children)) {
658
317
  for (let i = 0; i < children.length; i++) {
659
318
  let child = children[i];
660
319
 
661
320
  if (child != null && child !== false) {
662
321
  let childSvgMode =
663
- nodeName === 'svg'
664
- ? true
665
- : nodeName === 'foreignObject'
666
- ? false
667
- : isSvgMode,
668
- ret = _renderToStringPretty(
669
- child,
670
- context,
671
- opts,
672
- true,
673
- childSvgMode,
674
- selectValue
675
- );
676
-
677
- if (pretty && !hasLarge && isLargeString(ret)) hasLarge = true;
322
+ type === 'svg' || (type !== 'foreignObject' && isSvgMode);
323
+ let ret = _renderToString(child, context, childSvgMode, selectValue);
678
324
 
679
325
  // Skip if we received an empty string
680
326
  if (ret) {
681
- if (pretty) {
682
- let isText = ret.length > 0 && ret[0] != '<';
683
-
684
- // We merge adjacent text nodes, otherwise each piece would be printed
685
- // on a new line.
686
- if (lastWasText && isText) {
687
- pieces[pieces.length - 1] += ret;
688
- } else {
689
- pieces.push(ret);
690
- }
691
-
692
- lastWasText = isText;
693
- } else {
694
- pieces.push(ret);
695
- }
327
+ pieces = pieces + ret;
328
+ hasChildren = true;
696
329
  }
697
330
  }
698
331
  }
699
- if (pretty && hasLarge) {
700
- for (let i = pieces.length; i--; ) {
701
- pieces[i] = '\n' + indentChar + indent(pieces[i], indentChar);
702
- }
332
+ } else if (children != null && children !== false && children !== true) {
333
+ let childSvgMode =
334
+ type === 'svg' || (type !== 'foreignObject' && isSvgMode);
335
+ let ret = _renderToString(children, context, childSvgMode, selectValue);
336
+
337
+ // Skip if we received an empty string
338
+ if (ret) {
339
+ pieces = pieces + ret;
340
+ hasChildren = true;
703
341
  }
704
342
  }
705
343
 
706
- if (pieces.length || html) {
707
- s = s + pieces.join('');
708
- } else if (opts && opts.xml) {
709
- return s.substring(0, s.length - 1) + ' />';
710
- }
344
+ if (options[DIFFED]) options[DIFFED](vnode);
711
345
 
712
- if (isVoid && !children && !html) {
713
- s = s.replace(/>$/, ' />');
714
- } else {
715
- if (pretty && ~s.indexOf('\n')) s = s + '\n';
716
- s = s + `</${nodeName}>`;
346
+ if (hasChildren) {
347
+ s = s + pieces;
348
+ } else if (VOID_ELEMENTS.test(type)) {
349
+ return startElement + ' />';
717
350
  }
718
351
 
719
- return s;
352
+ return s + '</' + type + '>';
720
353
  }
721
354
 
722
- function getComponentName(component) {
723
- return (
724
- component.displayName ||
725
- (component !== Function && component.name) ||
726
- getFallbackComponentName(component)
727
- );
728
- }
355
+ /** The default export is an alias of `render()`. */
729
356
 
730
- function getFallbackComponentName(component) {
731
- let str = Function.prototype.toString.call(component),
732
- name = (str.match(/^\s*function\s+([^( ]+)/) || '')[1];
733
- if (!name) {
734
- // search for an existing indexed name for the given component:
735
- let index = -1;
736
- for (let i = UNNAMED.length; i--; ) {
737
- if (UNNAMED[i] === component) {
738
- index = i;
739
- break;
740
- }
741
- }
742
- // not found, create a new indexed name:
743
- if (index < 0) {
744
- index = UNNAMED.push(component) - 1;
745
- }
746
- name = `UnnamedComponent${index}`;
747
- }
748
- return name;
749
- }
750
357
  renderToString.shallowRender = shallowRender;
751
358
 
752
359
  export default renderToString;