1 | import { encodeEntities, indent, isLargeString, styleObjToCss, assign, getChildren } from './util';
|
2 | import { ENABLE_PRETTY } from '../env';
|
3 | import { options, Fragment } from 'preact';
|
4 |
|
5 | const SHALLOW = { shallow: true };
|
6 |
|
7 |
|
8 | const UNNAMED = [];
|
9 |
|
10 | const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | renderToString.render = renderToString;
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
|
34 |
|
35 |
|
36 |
|
37 | function renderToString(vnode, context, opts, inner, isSvgMode) {
|
38 | if (vnode==null || typeof vnode==='boolean') {
|
39 | return '';
|
40 | }
|
41 |
|
42 | let nodeName = vnode.type,
|
43 | props = vnode.props,
|
44 | isComponent = false;
|
45 | context = context || {};
|
46 | opts = opts || {};
|
47 |
|
48 | let pretty = ENABLE_PRETTY && opts.pretty,
|
49 | indentChar = pretty && typeof pretty==='string' ? pretty : '\t';
|
50 |
|
51 |
|
52 | if (typeof vnode!=='object' && !nodeName) {
|
53 | return encodeEntities(vnode);
|
54 | }
|
55 |
|
56 |
|
57 | if (typeof nodeName==='function') {
|
58 | isComponent = true;
|
59 | if (opts.shallow && (inner || opts.renderRootComponent===false)) {
|
60 | nodeName = getComponentName(nodeName);
|
61 | }
|
62 | else if (nodeName===Fragment) {
|
63 | let rendered = '';
|
64 | let children = [];
|
65 | getChildren(children, vnode.props.children);
|
66 |
|
67 | for (let i = 0; i < children.length; i++) {
|
68 | rendered += renderToString(children[i], context, opts, opts.shallowHighOrder!==false, isSvgMode);
|
69 | }
|
70 | return rendered;
|
71 | }
|
72 | else {
|
73 | let rendered;
|
74 |
|
75 | let c = vnode.__c = { __v: vnode, context, props: vnode.props };
|
76 | if (options.render) options.render(vnode);
|
77 |
|
78 | if (!nodeName.prototype || typeof nodeName.prototype.render!=='function') {
|
79 |
|
80 | rendered = nodeName.call(vnode.__c, props, context);
|
81 | }
|
82 | else {
|
83 |
|
84 |
|
85 | c = vnode.__c = new nodeName(props, context);
|
86 | c.__v = vnode;
|
87 |
|
88 | c._dirty = c.__d = true;
|
89 | c.props = props;
|
90 | c.context = context;
|
91 | if (nodeName.getDerivedStateFromProps) c.state = assign(assign({}, c.state), nodeName.getDerivedStateFromProps(c.props, c.state));
|
92 | else if (c.componentWillMount) c.componentWillMount();
|
93 | rendered = c.render(c.props, c.state, c.context);
|
94 | }
|
95 |
|
96 | if (c.getChildContext) {
|
97 | context = assign(assign({}, context), c.getChildContext());
|
98 | }
|
99 |
|
100 | return renderToString(rendered, context, opts, opts.shallowHighOrder!==false);
|
101 | }
|
102 | }
|
103 |
|
104 |
|
105 | let s = '', html;
|
106 |
|
107 | if (props) {
|
108 | let attrs = Object.keys(props);
|
109 |
|
110 |
|
111 | if (opts && opts.sortAttributes===true) attrs.sort();
|
112 |
|
113 | for (let i=0; i<attrs.length; i++) {
|
114 | let name = attrs[i],
|
115 | v = props[name];
|
116 | if (name==='children') continue;
|
117 |
|
118 | if (name.match(/[\s\n\\/='"\0<>]/)) continue;
|
119 |
|
120 | if (!(opts && opts.allAttributes) && (name==='key' || name==='ref')) continue;
|
121 |
|
122 | if (name==='className') {
|
123 | if (props.class) continue;
|
124 | name = 'class';
|
125 | }
|
126 | else if (isSvgMode && name.match(/^xlink:?./)) {
|
127 | name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
|
128 | }
|
129 |
|
130 | if (name==='style' && v && typeof v==='object') {
|
131 | v = styleObjToCss(v);
|
132 | }
|
133 |
|
134 | let hooked = opts.attributeHook && opts.attributeHook(name, v, context, opts, isComponent);
|
135 | if (hooked || hooked==='') {
|
136 | s += hooked;
|
137 | continue;
|
138 | }
|
139 |
|
140 | if (name==='dangerouslySetInnerHTML') {
|
141 | html = v && v.__html;
|
142 | }
|
143 | else if ((v || v===0 || v==='') && typeof v!=='function') {
|
144 | if (v===true || v==='') {
|
145 | v = name;
|
146 |
|
147 | if (!opts || !opts.xml) {
|
148 | s += ' ' + name;
|
149 | continue;
|
150 | }
|
151 | }
|
152 | s += ` ${name}="${encodeEntities(v)}"`;
|
153 | }
|
154 | }
|
155 | }
|
156 |
|
157 |
|
158 | if (pretty) {
|
159 | let sub = s.replace(/^\n\s*/, ' ');
|
160 | if (sub!==s && !~sub.indexOf('\n')) s = sub;
|
161 | else if (pretty && ~s.indexOf('\n')) s += '\n';
|
162 | }
|
163 |
|
164 | s = `<${nodeName}${s}>`;
|
165 | if (String(nodeName).match(/[\s\n\\/='"\0<>]/)) throw s;
|
166 |
|
167 | let isVoid = String(nodeName).match(VOID_ELEMENTS);
|
168 | if (isVoid) s = s.replace(/>$/, ' />');
|
169 |
|
170 | let pieces = [];
|
171 |
|
172 | let children;
|
173 | if (html) {
|
174 |
|
175 | if (pretty && isLargeString(html)) {
|
176 | html = '\n' + indentChar + indent(html, indentChar);
|
177 | }
|
178 | s += html;
|
179 | }
|
180 | else if (props && getChildren(children = [], props.children).length) {
|
181 | let hasLarge = pretty && ~s.indexOf('\n');
|
182 | for (let i=0; i<children.length; i++) {
|
183 | let child = children[i];
|
184 | if (child!=null && child!==false) {
|
185 | let childSvgMode = nodeName==='svg' ? true : nodeName==='foreignObject' ? false : isSvgMode,
|
186 | ret = renderToString(child, context, opts, true, childSvgMode);
|
187 | if (pretty && !hasLarge && isLargeString(ret)) hasLarge = true;
|
188 | if (ret) pieces.push(ret);
|
189 | }
|
190 | }
|
191 | if (pretty && hasLarge) {
|
192 | for (let i=pieces.length; i--; ) {
|
193 | pieces[i] = '\n' + indentChar + indent(pieces[i], indentChar);
|
194 | }
|
195 | }
|
196 | }
|
197 |
|
198 | if (pieces.length) {
|
199 | s += pieces.join('');
|
200 | }
|
201 | else if (opts && opts.xml) {
|
202 | return s.substring(0, s.length-1) + ' />';
|
203 | }
|
204 |
|
205 | if (!isVoid) {
|
206 | if (pretty && ~s.indexOf('\n')) s += '\n';
|
207 | s += `</${nodeName}>`;
|
208 | }
|
209 |
|
210 | return s;
|
211 | }
|
212 |
|
213 | function getComponentName(component) {
|
214 | return component.displayName || component!==Function && component.name || getFallbackComponentName(component);
|
215 | }
|
216 |
|
217 | function getFallbackComponentName(component) {
|
218 | let str = Function.prototype.toString.call(component),
|
219 | name = (str.match(/^\s*function\s+([^( ]+)/) || '')[1];
|
220 | if (!name) {
|
221 |
|
222 | let index = -1;
|
223 | for (let i=UNNAMED.length; i--; ) {
|
224 | if (UNNAMED[i]===component) {
|
225 | index = i;
|
226 | break;
|
227 | }
|
228 | }
|
229 |
|
230 | if (index<0) {
|
231 | index = UNNAMED.push(component) - 1;
|
232 | }
|
233 | name = `UnnamedComponent${index}`;
|
234 | }
|
235 | return name;
|
236 | }
|
237 | renderToString.shallowRender = shallowRender;
|
238 |
|
239 | export default renderToString;
|
240 |
|
241 | export {
|
242 | renderToString as render,
|
243 | renderToString,
|
244 | shallowRender
|
245 | };
|