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/README.md +62 -17
- package/dist/commonjs.js +1 -1
- package/dist/commonjs.js.map +1 -1
- package/dist/index.d.ts +1 -14
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.module.js +1 -1
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/jsx-entry.js +1 -1
- package/dist/jsx-entry.js.map +1 -1
- package/dist/jsx.d.ts +10 -2
- package/dist/jsx.js +4 -1
- package/dist/jsx.js.map +1 -1
- package/dist/jsx.mjs +1 -1
- package/dist/jsx.modern.js +1 -1
- package/dist/jsx.modern.js.map +1 -1
- package/dist/jsx.module.js +1 -1
- package/dist/jsx.module.js.map +1 -1
- package/dist/jsx.umd.js +2 -0
- package/dist/jsx.umd.js.map +1 -0
- package/package.json +27 -17
- package/src/constants.js +16 -0
- package/src/index.d.ts +1 -14
- package/src/index.js +311 -371
- package/src/jsx.d.ts +10 -2
- package/src/jsx.js +35 -7
- package/src/pretty.js +433 -0
- package/src/util.js +99 -36
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
23
|
+
// Global state for the current render pass
|
|
24
|
+
let beforeDiff, afterDiff, renderHook, ummountHook;
|
|
24
25
|
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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
|
-
|
|
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
|
|
58
|
-
options
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
//
|
|
131
|
+
// Text VNodes: escape as HTML
|
|
77
132
|
if (typeof vnode !== 'object') {
|
|
78
|
-
|
|
133
|
+
if (typeof vnode === 'function') return '';
|
|
134
|
+
return encodeEntities(vnode + '');
|
|
79
135
|
}
|
|
80
136
|
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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 (
|
|
204
|
-
context = assign(
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
|
|
236
|
+
// Serialize Element VNodes to HTML
|
|
237
|
+
let s = '<' + type,
|
|
238
|
+
html = '',
|
|
239
|
+
children;
|
|
226
240
|
|
|
227
|
-
|
|
228
|
-
|
|
241
|
+
for (let name in props) {
|
|
242
|
+
let v = props[name];
|
|
229
243
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
264
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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&b</textarea>
|
|
280
|
+
case 'textarea':
|
|
281
|
+
children = v;
|
|
292
282
|
continue;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
283
|
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
let pieces = [];
|
|
298
|
+
case 'dangerouslySetInnerHTML':
|
|
299
|
+
html = v && v.__html;
|
|
300
|
+
continue;
|
|
325
301
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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 (
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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 (
|
|
395
|
-
|
|
396
|
-
} else {
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
402
|
-
|
|
351
|
+
if (afterDiff) afterDiff(vnode);
|
|
352
|
+
vnode[PARENT] = undefined;
|
|
353
|
+
if (ummountHook) ummountHook(vnode);
|
|
403
354
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
360
|
+
return s + '>' + html + '</' + type + '>';
|
|
361
|
+
}
|
|
435
362
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
+
]);
|