Skip to content

Commit e9c59dd

Browse files
committed
Move JSX (test-style) rendering into separate entrypoint & factor utilities out into a separate file
1 parent 9c238b7 commit e9c59dd

File tree

3 files changed

+119
-95
lines changed

3 files changed

+119
-95
lines changed

src/index.js

Lines changed: 6 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import prettyFormat from 'pretty-format';
1+
import { objectKeys, encodeEntities, falsey, memoize, indent, isLargeString, styleObjToCss, assign, getNodeProps } from './util';
22

33
const SHALLOW = { shallow: true };
44

5-
const ESC = {
6-
'<': '&lt;',
7-
'>': '&gt;',
8-
'"': '&quot;',
9-
'&': '&amp;'
10-
};
5+
// components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
6+
const UNNAMED = [];
117

128
const EMPTY = {};
139

@@ -28,81 +24,6 @@ const VOID_ELEMENTS = [
2824
'wbr'
2925
];
3026

31-
// DOM properties that should NOT have "px" added when numeric
32-
export const NON_DIMENSION_PROPS = {
33-
boxFlex:1, boxFlexGroup:1, columnCount:1, fillOpacity:1, flex:1, flexGrow:1,
34-
flexPositive:1, flexShrink:1, flexNegative:1, fontWeight:1, lineClamp:1, lineHeight:1,
35-
opacity:1, order:1, orphans:1, strokeOpacity:1, widows:1, zIndex:1, zoom:1
36-
};
37-
38-
// components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
39-
const UNNAMED = [];
40-
41-
const objectKeys = Object.keys || (obj => {
42-
let keys = [];
43-
for (let i in obj) if (obj.hasOwnProperty(i)) keys.push(i);
44-
return keys;
45-
});
46-
47-
let encodeEntities = s => String(s).replace(/[<>"&]/g, escapeChar);
48-
49-
let escapeChar = a => ESC[a] || a;
50-
51-
let falsey = v => v==null || v===false;
52-
53-
let memoize = (fn, mem={}) => v => mem[v] || (mem[v] = fn(v));
54-
55-
let indent = (s, char) => String(s).replace(/(\n+)/g, '$1' + (char || '\t'));
56-
57-
let isLargeString = (s, length, ignoreLines) => (String(s).length>(length || 40) || (!ignoreLines && String(s).indexOf('\n')!==-1) || String(s).indexOf('<')!==-1);
58-
59-
function styleObjToCss(s) {
60-
let str = '';
61-
for (let prop in s) {
62-
let val = s[prop];
63-
if (val!=null) {
64-
if (str) str += ' ';
65-
str += jsToCss(prop);
66-
str += ': ';
67-
str += val;
68-
if (typeof val==='number' && !NON_DIMENSION_PROPS[prop]) {
69-
str += 'px';
70-
}
71-
str += ';';
72-
}
73-
}
74-
return str;
75-
}
76-
77-
// Convert a JavaScript camel-case CSS property name to a CSS property name
78-
let jsToCss = memoize( s => s.replace(/([A-Z])/g,'-$1').toLowerCase() );
79-
80-
function assign(obj, props) {
81-
for (let i in props) obj[i] = props[i];
82-
return obj;
83-
}
84-
85-
function getNodeProps(vnode) {
86-
let defaultProps = vnode.nodeName.defaultProps,
87-
props = assign({}, defaultProps || vnode.attributes);
88-
if (defaultProps) assign(props, vnode.attributes);
89-
if (vnode.children) props.children = vnode.children;
90-
return props;
91-
}
92-
93-
// we have to patch in Array support, Possible issue in npm.im/pretty-format
94-
let preactPlugin = {
95-
test(object) {
96-
return object && typeof object==='object' && 'nodeName' in object && 'attributes' in object && 'children' in object && !('nodeType' in object);
97-
},
98-
print(val, print, indent) {
99-
return renderToString(val, preactPlugin.context, preactPlugin.opts, true);
100-
}
101-
};
102-
103-
let prettyFormatOpts = {
104-
plugins: [preactPlugin]
105-
};
10627

10728
/** Render Preact JSX + Components to an HTML string.
10829
* @name render
@@ -186,19 +107,9 @@ export default function renderToString(vnode, context, opts, inner) {
186107
if (name==='children') continue;
187108
if (!(opts && opts.allAttributes) && (name==='key' || name==='ref')) continue;
188109

189-
if (opts.jsx) {
190-
if (typeof v!=='string') {
191-
preactPlugin.context = context;
192-
preactPlugin.opts = opts;
193-
v = prettyFormat(v, prettyFormatOpts);
194-
if (~v.indexOf('\n')) {
195-
v = `${indent('\n'+v, indentChar)}\n`;
196-
}
197-
s += indent(`\n${name}={${v}}`, indentChar);
198-
}
199-
else {
200-
s += `\n${indentChar}${name}="${encodeEntities(v)}"`;
201-
}
110+
let hooked = opts.attributeHook && opts.attributeHook(name, v, context, opts);
111+
if (hooked) {
112+
s += hooked;
202113
continue;
203114
}
204115

src/jsx.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import renderToString from '.';
2+
import { indent, encodeEntities, assign } from './util';
3+
import prettyFormat from 'pretty-format';
4+
5+
6+
// we have to patch in Array support, Possible issue in npm.im/pretty-format
7+
let preactPlugin = {
8+
test(object) {
9+
return object && typeof object==='object' && 'nodeName' in object && 'attributes' in object && 'children' in object && !('nodeType' in object);
10+
},
11+
print(val, print, indent) {
12+
return renderToString(val, preactPlugin.context, preactPlugin.opts, true);
13+
}
14+
};
15+
16+
17+
let prettyFormatOpts = {
18+
plugins: [preactPlugin]
19+
};
20+
21+
22+
function attributeHook(name, value, context, opts) {
23+
let indentChar = typeof opts.pretty==='string' ? opts.pretty : '\t';
24+
if (typeof value!=='string') {
25+
preactPlugin.context = context;
26+
preactPlugin.opts = opts;
27+
value = prettyFormat(value, prettyFormatOpts);
28+
if (~value.indexOf('\n')) {
29+
value = `${indent('\n'+value, indentChar)}\n`;
30+
}
31+
return indent(`\n${name}={${value}}`, indentChar);
32+
}
33+
return `\n${indentChar}${name}="${encodeEntities(value)}"`;
34+
}
35+
36+
37+
let defaultOpts = {
38+
attributeHook,
39+
jsx: true,
40+
xml: true,
41+
pretty: ' '
42+
};
43+
44+
45+
export default function renderToJsxString(vnode, context, opts, inner) {
46+
opts = assign(assign({}, defaultOpts), opts || {});
47+
return renderToString(vnode, context, opts, inner);
48+
}

src/util.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// DOM properties that should NOT have "px" added when numeric
2+
export const NON_DIMENSION_PROPS = {
3+
boxFlex:1, boxFlexGroup:1, columnCount:1, fillOpacity:1, flex:1, flexGrow:1,
4+
flexPositive:1, flexShrink:1, flexNegative:1, fontWeight:1, lineClamp:1, lineHeight:1,
5+
opacity:1, order:1, orphans:1, strokeOpacity:1, widows:1, zIndex:1, zoom:1
6+
};
7+
8+
const ESC = {
9+
'<': '&lt;',
10+
'>': '&gt;',
11+
'"': '&quot;',
12+
'&': '&amp;'
13+
};
14+
15+
export const objectKeys = Object.keys || (obj => {
16+
let keys = [];
17+
for (let i in obj) if (obj.hasOwnProperty(i)) keys.push(i);
18+
return keys;
19+
});
20+
21+
export let encodeEntities = s => String(s).replace(/[<>"&]/g, escapeChar);
22+
23+
let escapeChar = a => ESC[a] || a;
24+
25+
export let falsey = v => v==null || v===false;
26+
27+
export let memoize = (fn, mem={}) => v => mem[v] || (mem[v] = fn(v));
28+
29+
export let indent = (s, char) => String(s).replace(/(\n+)/g, '$1' + (char || '\t'));
30+
31+
export let isLargeString = (s, length, ignoreLines) => (String(s).length>(length || 40) || (!ignoreLines && String(s).indexOf('\n')!==-1) || String(s).indexOf('<')!==-1);
32+
33+
export function styleObjToCss(s) {
34+
let str = '';
35+
for (let prop in s) {
36+
let val = s[prop];
37+
if (val!=null) {
38+
if (str) str += ' ';
39+
str += jsToCss(prop);
40+
str += ': ';
41+
str += val;
42+
if (typeof val==='number' && !NON_DIMENSION_PROPS[prop]) {
43+
str += 'px';
44+
}
45+
str += ';';
46+
}
47+
}
48+
return str;
49+
}
50+
51+
// Convert a JavaScript camel-case CSS property name to a CSS property name
52+
export let jsToCss = memoize( s => s.replace(/([A-Z])/g,'-$1').toLowerCase() );
53+
54+
export function assign(obj, props) {
55+
for (let i in props) obj[i] = props[i];
56+
return obj;
57+
}
58+
59+
export function getNodeProps(vnode) {
60+
let defaultProps = vnode.nodeName.defaultProps,
61+
props = assign({}, defaultProps || vnode.attributes);
62+
if (defaultProps) assign(props, vnode.attributes);
63+
if (vnode.children) props.children = vnode.children;
64+
return props;
65+
}

0 commit comments

Comments
 (0)