Skip to content

Commit 6f4a54d

Browse files
committed
fix JSX entrypoint and tests
1 parent 7f6ffc3 commit 6f4a54d

File tree

7 files changed

+147
-59
lines changed

7 files changed

+147
-59
lines changed

src/jsx.d.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ import { VNode } from 'preact';
33
interface Options {
44
jsx?: boolean;
55
xml?: boolean;
6+
pretty?: boolean | string;
7+
shallow?: boolean;
68
functions?: boolean;
79
functionNames?: boolean;
810
skipFalseAttributes?: boolean;
9-
pretty?: boolean | string;
1011
}
1112

12-
export function render(vnode: VNode, context?: any, options?: Options): string;
13+
export default function renderToStringPretty(
14+
vnode: VNode,
15+
context?: any,
16+
options?: Options
17+
): string;
18+
19+
export function shallowRender(vnode: VNode, context?: any): string;
20+
1321
export default render;

src/jsx.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import './polyfills';
2-
import renderToString from './index';
2+
import renderToString from './pretty';
33
import { indent, encodeEntities } from './util';
44
import prettyFormat from 'pretty-format';
55

6+
/** @typedef {import('preact').VNode} VNode */
7+
68
// we have to patch in Array support, Possible issue in npm.im/pretty-format
79
let preactPlugin = {
810
test(object) {
@@ -67,10 +69,36 @@ let defaultOpts = {
6769
pretty: ' '
6870
};
6971

70-
function renderToJsxString(vnode, context, opts, inner) {
71-
opts = Object.assign({}, defaultOpts, opts || {});
72-
return renderToString(vnode, context, opts, inner);
72+
/**
73+
* Render Preact JSX + Components to a pretty-printed HTML-like string.
74+
* @param {VNode} vnode JSX Element / VNode to render
75+
* @param {Object} [context={}] Initial root context object
76+
* @param {Object} [options={}] Rendering options
77+
* @param {Boolean} [options.jsx=true] Generate JSX/XML output instead of HTML
78+
* @param {Boolean} [options.xml=false] Use self-closing tags for elements without children
79+
* @param {Boolean} [options.shallow=false] Serialize nested Components (`<Foo a="b" />`) instead of rendering
80+
* @param {Boolean} [options.pretty=false] Add whitespace for readability
81+
* @param {RegExp|undefined} [options.voidElements] RegeEx to define which element types are self-closing
82+
* @returns {String} a pretty-printed HTML-like string
83+
*/
84+
export default function renderToStringPretty(vnode, context, options) {
85+
const opts = Object.assign({}, defaultOpts, options || {});
86+
if (!opts.jsx) opts.attributeHook = null;
87+
return renderToString(vnode, context, opts);
7388
}
89+
export { renderToStringPretty as render };
90+
91+
const SHALLOW = { shallow: true };
7492

75-
export default renderToJsxString;
76-
export { renderToJsxString as render };
93+
/** Only render elements, leaving Components inline as `<ComponentName ... />`.
94+
* This method is just a convenience alias for `render(vnode, context, { shallow:true })`
95+
* @name shallow
96+
* @function
97+
* @param {VNode} vnode JSX VNode to render.
98+
* @param {Object} [context={}] Optionally pass an initial context object through the render path.
99+
* @param {Parameters<typeof renderToStringPretty>[2]} [options] Optionally pass an initial context object through the render path.
100+
*/
101+
export function shallowRender(vnode, context, options) {
102+
const opts = Object.assign({}, SHALLOW, options || {});
103+
return renderToStringPretty(vnode, context, opts);
104+
}

src/pretty.js

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,53 @@ import {
55
styleObjToCss,
66
getChildren,
77
createComponent,
8-
getContext,
98
UNSAFE_NAME,
109
XLINK,
1110
VOID_ELEMENTS
1211
} from './util';
12+
import { COMMIT, DIFF, DIFFED, RENDER, SKIP_EFFECTS } from './constants';
1313
import { options, Fragment } from 'preact';
1414

15+
/** @typedef {import('preact').VNode} VNode */
16+
1517
// components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
1618
const UNNAMED = [];
1719

18-
export function _renderToStringPretty(
20+
const EMPTY_ARR = [];
21+
22+
/**
23+
* Render Preact JSX + Components to a pretty-printed HTML-like string.
24+
* @param {VNode} vnode JSX Element / VNode to render
25+
* @param {Object} [context={}] Initial root context object
26+
* @param {Object} [opts={}] Rendering options
27+
* @param {Boolean} [opts.shallow=false] Serialize nested Components (`<Foo a="b" />`) instead of rendering
28+
* @param {Boolean} [opts.xml=false] Use self-closing tags for elements without children
29+
* @param {Boolean} [opts.pretty=false] Add whitespace for readability
30+
* @param {RegExp|undefined} [opts.voidElements] RegeEx to define which element types are self-closing
31+
* @param {boolean} [_inner]
32+
* @returns {String} a pretty-printed HTML-like string
33+
*/
34+
export default function renderToStringPretty(vnode, context, opts, _inner) {
35+
// Performance optimization: `renderToString` is synchronous and we
36+
// therefore don't execute any effects. To do that we pass an empty
37+
// array to `options._commit` (`__c`). But we can go one step further
38+
// and avoid a lot of dirty checks and allocations by setting
39+
// `options._skipEffects` (`__s`) too.
40+
const previousSkipEffects = options[SKIP_EFFECTS];
41+
options[SKIP_EFFECTS] = true;
42+
43+
try {
44+
return _renderToStringPretty(vnode, context || {}, opts, _inner);
45+
} finally {
46+
// options._commit, we don't schedule any effects in this library right now,
47+
// so we can pass an empty queue to this hook.
48+
if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
49+
options[SKIP_EFFECTS] = previousSkipEffects;
50+
EMPTY_ARR.length = 0;
51+
}
52+
}
53+
54+
function _renderToStringPretty(
1955
vnode,
2056
context,
2157
opts,
@@ -29,7 +65,7 @@ export function _renderToStringPretty(
2965

3066
// #text nodes
3167
if (typeof vnode !== 'object') {
32-
return encodeEntities(vnode);
68+
return encodeEntities(vnode + '');
3369
}
3470

3571
let pretty = opts.pretty,
@@ -53,6 +89,8 @@ export function _renderToStringPretty(
5389
return rendered;
5490
}
5591

92+
if (options[DIFF]) options[DIFF](vnode);
93+
5694
let nodeName = vnode.type,
5795
props = vnode.props,
5896
isComponent = false;
@@ -78,17 +116,20 @@ export function _renderToStringPretty(
78116

79117
let c = (vnode.__c = createComponent(vnode, context));
80118

81-
// options._diff
82-
if (options.__b) options.__b(vnode);
119+
let renderHook = options[RENDER];
83120

84-
// options._render
85-
let renderHook = options.__r;
121+
let cctx = context;
122+
let cxType = nodeName.contextType;
123+
if (cxType != null) {
124+
let provider = context[cxType.__c];
125+
cctx = provider ? provider.props.value : cxType.__;
126+
}
86127

87128
if (
88129
!nodeName.prototype ||
89130
typeof nodeName.prototype.render !== 'function'
90131
) {
91-
let cctx = getContext(nodeName, context);
132+
// let cctx = getContext(nodeName, context);
92133

93134
// If a hook invokes setState() to invalidate the component during rendering,
94135
// re-render it up to 25 times to allow "settling" of memoized states.
@@ -105,7 +146,7 @@ export function _renderToStringPretty(
105146
rendered = nodeName.call(vnode.__c, props, cctx);
106147
}
107148
} else {
108-
let cctx = getContext(nodeName, context);
149+
// let cctx = getContext(nodeName, context);
109150

110151
// c = new nodeName(props, context);
111152
c = vnode.__c = new nodeName(props, cctx);
@@ -148,15 +189,18 @@ export function _renderToStringPretty(
148189
context = Object.assign({}, context, c.getChildContext());
149190
}
150191

151-
if (options.diffed) options.diffed(vnode);
152-
return _renderToStringPretty(
192+
const res = _renderToStringPretty(
153193
rendered,
154194
context,
155195
opts,
156196
opts.shallowHighOrder !== false,
157197
isSvgMode,
158198
selectValue
159199
);
200+
201+
if (options[DIFFED]) options[DIFFED](vnode);
202+
203+
return res;
160204
}
161205
}
162206

@@ -255,7 +299,7 @@ export function _renderToStringPretty(
255299
s = s + ` selected`;
256300
}
257301
}
258-
s = s + ` ${name}="${encodeEntities(v)}"`;
302+
s = s + ` ${name}="${encodeEntities(v + '')}"`;
259303
}
260304
}
261305
}
@@ -339,6 +383,8 @@ export function _renderToStringPretty(
339383
}
340384
}
341385

386+
if (options[DIFFED]) options[DIFFED](vnode);
387+
342388
if (pieces.length || html) {
343389
s = s + pieces.join('');
344390
} else if (opts && opts.xml) {

test/compat.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render } from '../src';
1+
import render from '../src';
22
import { createElement } from 'preact/compat';
33
import { expect } from 'chai';
44

test/index.test.js

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,21 @@
1-
import renderToString, {
2-
render,
3-
shallowRender,
4-
renderToStaticMarkup,
5-
renderToString as _renderToString
6-
} from '../src';
1+
import renderToString from '../src';
2+
import { default as renderToStringPretty, shallowRender } from '../src/jsx';
73
import { expect } from 'chai';
84

95
describe('render-to-string', () => {
106
describe('exports', () => {
117
it('exposes renderToString as default', () => {
128
expect(renderToString).to.be.a('function');
139
});
10+
});
11+
});
1412

15-
it('exposes render as a named export', () => {
16-
expect(render).to.be.a('function');
17-
expect(render).to.equal(renderToString);
18-
});
19-
20-
it('exposes renderToString as a named export', () => {
21-
expect(_renderToString).to.be.a('function');
22-
expect(_renderToString).to.equal(renderToString);
23-
});
24-
25-
it('exposes renderToStaticMarkup as a named export', () => {
26-
expect(renderToStaticMarkup).to.be.a('function');
27-
expect(renderToStaticMarkup).to.equal(renderToStaticMarkup);
28-
});
13+
describe('render-to-string/jsx', () => {
14+
it('exposes renderToStringPretty as default export', () => {
15+
expect(renderToStringPretty).to.be.a('function');
16+
});
2917

30-
it('exposes shallowRender as a named export', () => {
31-
expect(shallowRender).to.be.a('function');
32-
});
18+
it('exposes shallowRender as a named export', () => {
19+
expect(shallowRender).to.be.a('function');
3320
});
3421
});

test/render.test.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { render, shallowRender } from '../src';
1+
import render from '../src';
2+
import renderToStringPretty from '../src/pretty';
3+
import renderToStringJSX from '../src/jsx';
24
import { h, Component, createContext, Fragment, options } from 'preact';
35
import {
46
useState,
@@ -10,6 +12,15 @@ import {
1012
import { expect } from 'chai';
1113
import { spy, stub, match } from 'sinon';
1214

15+
function shallowRender(vnode) {
16+
return renderToStringJSX(vnode, context, {
17+
jsx: false,
18+
xml: false,
19+
pretty: ' ',
20+
shallow: true
21+
});
22+
}
23+
1324
describe('render', () => {
1425
describe('Basic JSX', () => {
1526
it('should render JSX', () => {
@@ -227,7 +238,7 @@ describe('render', () => {
227238
});
228239

229240
it('should self-close custom void elements', () => {
230-
let rendered = render(
241+
let rendered = renderToStringPretty(
231242
<div>
232243
<hello-world />
233244
</div>,
@@ -257,14 +268,14 @@ describe('render', () => {
257268

258269
it('should serialize object styles', () => {
259270
let rendered = render(<div style={{ color: 'red', border: 'none' }} />),
260-
expected = `<div style="color: red; border: none;"></div>`;
271+
expected = `<div style="color:red;border:none;"></div>`;
261272

262273
expect(rendered).to.equal(expected);
263274
});
264275

265276
it('should preserve CSS Custom Properties', () => {
266277
let rendered = render(<div style={{ '--foo': 1, '--foo-bar': '2' }} />),
267-
expected = `<div style="--foo: 1; --foo-bar: 2;"></div>`;
278+
expected = `<div style="--foo:1;--foo-bar:2;"></div>`;
268279

269280
expect(rendered).to.equal(expected);
270281
});
@@ -734,7 +745,7 @@ describe('render', () => {
734745
</Outer>
735746
);
736747
expect(rendered).to.equal(
737-
'<Inner a="b" b="b" p="1">child <span>foo</span></Inner>'
748+
`<Inner a="b" b="b" p="1">\n child \n <span>foo</span>\n</Inner>`
738749
);
739750
});
740751

@@ -757,10 +768,10 @@ describe('render', () => {
757768
let rendered = render(<Outer />);
758769
expect(rendered).to.equal('<div>hi</div>');
759770

760-
rendered = render(<Outer />, null, { shallow: true });
771+
rendered = renderToStringPretty(<Outer />, null, { shallow: true });
761772
expect(rendered, '{shallow:true}').to.equal('<Middle></Middle>');
762773

763-
rendered = render(<Outer />, null, {
774+
rendered = renderToStringPretty(<Outer />, null, {
764775
shallow: true,
765776
shallowHighOrder: false
766777
});
@@ -832,15 +843,19 @@ describe('render', () => {
832843
});
833844

834845
it('should sort attributes lexicographically if enabled', () => {
835-
let rendered = render(<div b1="b1" c="c" a="a" b="b" />, null, {
836-
sortAttributes: true
837-
});
846+
let rendered = renderToStringPretty(
847+
<div b1="b1" c="c" a="a" b="b" />,
848+
null,
849+
{
850+
sortAttributes: true
851+
}
852+
);
838853
expect(rendered).to.equal('<div a="a" b="b" b1="b1" c="c"></div>');
839854
});
840855
});
841856

842857
describe('xml:true', () => {
843-
let renderXml = (jsx) => render(jsx, null, { xml: true });
858+
let renderXml = (jsx) => renderToStringPretty(jsx, null, { xml: true });
844859

845860
it('should render end-tags', () => {
846861
expect(renderXml(<div />)).to.equal(`<div />`);
@@ -944,7 +959,7 @@ describe('render', () => {
944959
});
945960

946961
it('should indent Fragment children when pretty printing', () => {
947-
let html = render(
962+
let html = renderToStringPretty(
948963
<div>
949964
<Fragment>
950965
<div>foo</div>

0 commit comments

Comments
 (0)