preact-render-to-string 5.2.1 → 5.2.2

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
@@ -3,7 +3,6 @@ import {
3
3
  indent,
4
4
  isLargeString,
5
5
  styleObjToCss,
6
- assign,
7
6
  getChildren
8
7
  } from './util';
9
8
  import { options, Fragment } from 'preact';
@@ -15,10 +14,10 @@ const SHALLOW = { shallow: true };
15
14
  // components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
16
15
  const UNNAMED = [];
17
16
 
18
- const VOID_ELEMENTS =
19
- /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
17
+ const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
20
18
 
21
19
  const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
20
+ const XLINK = /^xlink:?./;
22
21
 
23
22
  function markAsDirty() {
24
23
  this.__d = true;
@@ -59,7 +58,12 @@ function renderToString(vnode, context, opts) {
59
58
  const previousSkipEffects = options.__s;
60
59
  options.__s = true;
61
60
 
62
- const res = _renderToString(vnode, context, opts);
61
+ let res;
62
+ if (opts.pretty || opts.sortAttributes) {
63
+ res = _renderToStringPretty(vnode, context, opts);
64
+ } else {
65
+ res = _renderToString(vnode, context, opts);
66
+ }
63
67
 
64
68
  // options._commit, we don't schedule any effects in this library right now,
65
69
  // so we can pass an empty queue to this hook.
@@ -69,6 +73,32 @@ function renderToString(vnode, context, opts) {
69
73
  return res;
70
74
  }
71
75
 
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
+ }
89
+
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
+ }
101
+
72
102
  /** The default export is an alias of `render()`. */
73
103
  function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
74
104
  if (vnode == null || typeof vnode === 'boolean') {
@@ -80,6 +110,292 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
80
110
  return encodeEntities(vnode);
81
111
  }
82
112
 
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('');
121
+ }
122
+
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
+ }
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
+ 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
+ }
322
+ }
323
+
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];
343
+
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
+ );
359
+
360
+ // Skip if we received an empty string
361
+ if (ret) {
362
+ pieces = pieces + ret;
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ if (pieces.length || html) {
369
+ s = s + pieces;
370
+ } else if (opts && opts.xml) {
371
+ return s.substring(0, s.length - 1) + ' />';
372
+ }
373
+
374
+ if (isVoid && !children && !html) {
375
+ return s.replace(/>$/, ' />');
376
+ }
377
+
378
+ return `${s}</${nodeName}>`;
379
+ }
380
+
381
+ /** 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') {
391
+ return '';
392
+ }
393
+
394
+ // #text nodes
395
+ if (typeof vnode !== 'object') {
396
+ return encodeEntities(vnode);
397
+ }
398
+
83
399
  let pretty = opts.pretty,
84
400
  indentChar = pretty && typeof pretty === 'string' ? pretty : '\t';
85
401
 
@@ -89,7 +405,14 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
89
405
  if (pretty && i > 0) rendered = rendered + '\n';
90
406
  rendered =
91
407
  rendered +
92
- _renderToString(vnode[i], context, opts, inner, isSvgMode, selectValue);
408
+ _renderToStringPretty(
409
+ vnode[i],
410
+ context,
411
+ opts,
412
+ inner,
413
+ isSvgMode,
414
+ selectValue
415
+ );
93
416
  }
94
417
  return rendered;
95
418
  }
@@ -106,7 +429,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
106
429
  } else if (nodeName === Fragment) {
107
430
  const children = [];
108
431
  getChildren(children, vnode.props.children);
109
- return _renderToString(
432
+ return _renderToStringPretty(
110
433
  children,
111
434
  context,
112
435
  opts,
@@ -117,17 +440,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
117
440
  } else {
118
441
  let rendered;
119
442
 
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
- });
443
+ let c = (vnode.__c = createComponent(vnode, context));
131
444
 
132
445
  // options._diff
133
446
  if (options.__b) options.__b(vnode);
@@ -139,16 +452,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
139
452
  !nodeName.prototype ||
140
453
  typeof nodeName.prototype.render !== 'function'
141
454
  ) {
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;
455
+ let cctx = getContext(nodeName, context);
152
456
 
153
457
  // If a hook invokes setState() to invalidate the component during rendering,
154
458
  // re-render it up to 25 times to allow "settling" of memoized states.
@@ -165,15 +469,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
165
469
  rendered = nodeName.call(vnode.__c, props, cctx);
166
470
  }
167
471
  } 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;
472
+ let cctx = getContext(nodeName, context);
177
473
 
178
474
  // c = new nodeName(props, context);
179
475
  c = vnode.__c = new nodeName(props, cctx);
@@ -189,8 +485,9 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
189
485
 
190
486
  c.context = cctx;
191
487
  if (nodeName.getDerivedStateFromProps)
192
- c.state = assign(
193
- assign({}, c.state),
488
+ c.state = Object.assign(
489
+ {},
490
+ c.state,
194
491
  nodeName.getDerivedStateFromProps(c.props, c.state)
195
492
  );
196
493
  else if (c.componentWillMount) {
@@ -212,11 +509,11 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
212
509
  }
213
510
 
214
511
  if (c.getChildContext) {
215
- context = assign(assign({}, context), c.getChildContext());
512
+ context = Object.assign({}, context, c.getChildContext());
216
513
  }
217
514
 
218
515
  if (options.diffed) options.diffed(vnode);
219
- return _renderToString(
516
+ return _renderToStringPretty(
220
517
  rendered,
221
518
  context,
222
519
  opts,
@@ -266,7 +563,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
266
563
  } else if (name === 'className') {
267
564
  if (typeof props.class !== 'undefined') continue;
268
565
  name = 'class';
269
- } else if (isSvgMode && /^xlink:?./.test(name)) {
566
+ } else if (isSvgMode && XLINK.test(name)) {
270
567
  name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
271
568
  }
272
569
 
@@ -368,7 +665,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
368
665
  : nodeName === 'foreignObject'
369
666
  ? false
370
667
  : isSvgMode,
371
- ret = _renderToString(
668
+ ret = _renderToStringPretty(
372
669
  child,
373
670
  context,
374
671
  opts,
package/src/util.js CHANGED
@@ -3,16 +3,34 @@ export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine
3
3
 
4
4
  const ENCODED_ENTITIES = /[&<>"]/;
5
5
 
6
- export function encodeEntities(input) {
7
- const s = String(input);
8
- if (!ENCODED_ENTITIES.test(s)) {
9
- return s;
6
+ export function encodeEntities(str) {
7
+ // Ensure we're always parsing and returning a string:
8
+ str += '';
9
+
10
+ // Skip all work for strings with no entities needing encoding:
11
+ if (ENCODED_ENTITIES.test(str) === false) return str;
12
+
13
+ let last = 0,
14
+ i = 0,
15
+ out = '',
16
+ ch = '';
17
+
18
+ // Seek forward in str until the next entity char:
19
+ for (; i<str.length; i++) {
20
+ switch (str.charCodeAt(i)) {
21
+ case 60: ch = '&lt;'; break;
22
+ case 62: ch = '&gt;'; break;
23
+ case 34: ch = '&quot;'; break;
24
+ case 38: ch = '&amp;'; break;
25
+ default: continue;
26
+ }
27
+ // Append skipped/buffered characters and the encoded entity:
28
+ if (i > last) out += str.slice(last, i);
29
+ out += ch;
30
+ // Start the next seek/buffer after the entity's offset:
31
+ last = i + 1;
10
32
  }
11
- return s
12
- .replace(/&/g, '&amp;')
13
- .replace(/</g, '&lt;')
14
- .replace(/>/g, '&gt;')
15
- .replace(/"/g, '&quot;');
33
+ return out + str.slice(last, i);
16
34
  }
17
35
 
18
36
  export let indent = (s, char) =>
@@ -57,8 +75,7 @@ export function styleObjToCss(s) {
57
75
  * @private
58
76
  */
59
77
  export function assign(obj, props) {
60
- for (let i in props) obj[i] = props[i];
61
- return obj;
78
+ return Object.assign(obj, props);
62
79
  }
63
80
 
64
81
  /**