Skip to content

Commit 8ac54fd

Browse files
Merge pull request #160 from remarkablemark/refactor/parser-types
refactor: improve parser types
2 parents 8560493 + 2287038 commit 8ac54fd

File tree

10 files changed

+88
-46
lines changed

10 files changed

+88
-46
lines changed

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"node": true
77
},
88
"extends": "eslint:recommended",
9+
"parser": "@typescript-eslint/parser",
910
"parserOptions": {
1011
"ecmaVersion": 6,
1112
"sourceType": "module"

index.d.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@ import domToReact from './lib/dom-to-react';
55
import htmlToDOM from 'html-dom-parser';
66

77
export interface HTMLReactParserOptions {
8-
// TODO: Replace `object` by type for objects like `{ type: 'h1', props: { children: 'Heading' } }`
98
replace?: (
109
domNode: DomElement
1110
) => JSX.Element | object | void | undefined | null | false;
12-
library?: object;
11+
library?: {
12+
cloneElement: (
13+
element: JSX.Element,
14+
props?: object,
15+
...children: any
16+
) => JSX.Element;
17+
createElement: (type: any, props?: object, ...children: any) => JSX.Element;
18+
isValidElement: (element: any) => boolean;
19+
[key: string]: any;
20+
};
1321
}
1422

1523
/**
1624
* Converts HTML string to JSX element(s).
1725
*
18-
* @param html - The HTML string to parse to JSX element(s).
19-
* @param options - The parser options.
20-
* @return - Single or array of JSX elements.
26+
* @param html - HTML string to parse to JSX element(s).
27+
* @param options - Parser options.
28+
* @return - JSX element(s).
2129
*/
2230
declare function HTMLReactParser(
2331
html: string,

index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ function HTMLReactParser(html, options) {
1616
if (typeof html !== 'string') {
1717
throw new TypeError('First argument must be a string');
1818
}
19+
if (html === '') {
20+
return [];
21+
}
1922
return domToReact(htmlToDOM(html, domParserOptions), options);
2023
}
2124

lib/dom-to-react.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { DomElement } from 'domhandler';
66
/**
77
* Converts DOM nodes to JSX element(s).
88
*
9-
* @param nodes - An array of DomNodes to convert to JSX element(s).
10-
* @param options - Options to use when converting to JSX.
11-
* @returns Single or array of JSX elements.
9+
* @param nodes - DOM nodes.
10+
* @param options - Parser options.
11+
* @returns - JSX element(s).
1212
*/
1313
export default function domToReact(
1414
nodes: DomElement[],

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@rollup/plugin-commonjs": "^12.0.0",
4444
"@rollup/plugin-node-resolve": "^8.0.0",
4545
"@types/react": "^16.9.35",
46+
"@typescript-eslint/parser": "^3.1.0",
4647
"benchmark": "^2.1.4",
4748
"dtslint": "^3.6.10",
4849
"eslint": "^7.1.0",

test/attributes-to-props.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const assert = require('assert');
22
const attributesToProps = require('../lib/attributes-to-props');
33
const utilities = require('../lib/utilities');
44

5-
describe('attributes-to-props', () => {
5+
describe('attributes to props', () => {
66
describe('HTML', () => {
77
it('converts attributes to React props', () => {
88
assert.deepEqual(

test/dom-to-react.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const domToReact = require('../lib/dom-to-react');
55
const { data, render } = require('./helpers/');
66
const utilities = require('../lib/utilities');
77

8-
describe('dom-to-react', () => {
8+
describe('DOM to React', () => {
99
it('converts single DOM node to React', () => {
1010
const html = data.html.single;
1111
const reactElement = domToReact(htmlToDOM(html));

test/html-to-react.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const React = require('react');
33
const parse = require('..');
44
const { data, render } = require('./helpers/');
55

6-
describe('html-to-react', () => {
6+
describe('HTML to React', () => {
77
describe('exports', () => {
88
it('has default ES Module', () => {
99
assert.strictEqual(parse.default, parse);
@@ -13,7 +13,7 @@ describe('html-to-react', () => {
1313
assert.strictEqual(parse.domToReact, require('../lib/dom-to-react'));
1414
});
1515

16-
it('contains htmlToDOM', () => {
16+
it('has htmlToDOM', () => {
1717
assert.strictEqual(parse.htmlToDOM, require('html-dom-parser'));
1818
});
1919
});
@@ -27,6 +27,10 @@ describe('html-to-react', () => {
2727
});
2828
});
2929

30+
it('converts empty string to empty array', () => {
31+
assert.deepEqual(parse(''), []);
32+
});
33+
3034
it('returns string if it cannot be parsed as HTML', () => {
3135
assert.strictEqual(parse('foo'), 'foo');
3236
});

test/types/index.test.tsx

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,67 @@
1-
import parse, { HTMLReactParserOptions, domToReact, htmlToDOM } from 'html-react-parser';
1+
import parse, {
2+
HTMLReactParserOptions,
3+
domToReact,
4+
htmlToDOM
5+
} from 'html-react-parser';
26
import * as React from 'react';
37

4-
/* $ExpectType ReactElement | ReactElement[] */
5-
parse('<div>text</div>');
8+
// $ExpectError
9+
parse();
610

7-
// `options.replace`
11+
// $ExpectType Element | Element[]
12+
parse('');
813

9-
// Return React.createElement from `replace`
10-
parse('<p id="replace">text</p>', {
14+
// $ExpectType Element | Element[]
15+
parse('string');
16+
17+
// $ExpectType Element | Element[]
18+
parse('<p>text</p>');
19+
20+
// $ExpectType Element | Element[]
21+
parse('<li>1</li><li>2</li>');
22+
23+
// $ExpectType Element | Element[]
24+
parse('<br id="replace">', {
1125
replace: domNode => {
1226
if (domNode.attribs && domNode.attribs.id === 'replace') {
13-
return React.createElement('span', {}, 'replaced');
27+
return <span>replaced</span>;
1428
}
1529
}
1630
});
1731

18-
// Return ReactElement
19-
const options: HTMLReactParserOptions = {
32+
// $ExpectType Element | Element[]
33+
parse('<br id="remove">', {
2034
replace({ attribs }) {
21-
return attribs && attribs.id === 'remove' && <React.Fragment />;
35+
return attribs && attribs.id === 'remove' && <></>;
2236
}
23-
};
37+
});
2438

25-
parse('<p><br id="remove"></p>', options);
39+
let options: HTMLReactParserOptions;
2640

27-
// Return domhandler node
28-
parse('<a id="header" href="#">Heading</a>', {
41+
options = {
2942
replace: node => {
3043
if (node.attribs && node.attribs.id === 'header') {
31-
return {
32-
type: 'h1',
33-
props: { children: 'Heading' }
34-
};
44+
return;
3545
}
3646
}
47+
};
48+
49+
// $ExpectType Element | Element[]
50+
parse('<a id="header" href="#">Heading</a>', options);
51+
52+
// $ExpectType Element | Element[]
53+
parse('<hr>', {
54+
library: {
55+
cloneElement: (element, props, children) =>
56+
React.cloneElement(element, props, children),
57+
createElement: (type, props, children) =>
58+
React.createElement(type, props, children),
59+
isValidElement: element => React.isValidElement(element)
60+
}
3761
});
3862

3963
// $ExpectType DomElement[]
40-
const dom = htmlToDOM('<div>text</div>');
64+
const domNodes = htmlToDOM('<div>text</div>');
4165

42-
/* $ExpectType ReactElement | ReactElement[] */
43-
domToReact(dom);
66+
// $ExpectType Element | Element[]
67+
domToReact(domNodes);

test/types/lib/dom-to-react.test.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,40 @@
1+
import { DomElement } from 'domhandler';
12
import { HTMLReactParserOptions } from 'html-react-parser';
23
import domToReact from 'html-react-parser/lib/dom-to-react';
34
import * as React from 'react';
45
import htmlToDOM from 'html-dom-parser';
56

6-
/* $ExpectType ReactElement | ReactElement[] */
7-
domToReact(htmlToDOM('<div>text</div>'));
7+
// $ExpectType DomElement[]
8+
htmlToDOM('<div>text</div>');
89

9-
// `options.replace`
10+
// $ExpectType Element | Element[]
11+
domToReact(htmlToDOM('<div>text</div>'));
1012

11-
// Return React.createElement from `replace`
13+
// $ExpectType Element | Element[]
1214
domToReact(htmlToDOM('<p id="replace">text</p>'), {
1315
replace: domNode => {
1416
if (domNode.attribs && domNode.attribs.id === 'replace') {
15-
return React.createElement('span', {}, 'replaced');
17+
return <span>replaced</span>;
1618
}
1719
}
1820
});
1921

20-
// Return ReactElement
21-
const options: HTMLReactParserOptions = {
22+
let options: HTMLReactParserOptions;
23+
24+
options = {
2225
replace({ attribs }) {
23-
return attribs && attribs.id === 'remove' && <React.Fragment />;
26+
return attribs && attribs.id === 'remove' && <></>;
2427
}
2528
};
2629

30+
// $ExpectType Element | Element[]
2731
domToReact(htmlToDOM('<p><br id="remove"></p>'), options);
2832

29-
// Return domhandler node
33+
// $ExpectType Element | Element[]
3034
domToReact(htmlToDOM('<a id="header" href="#">Heading</a>'), {
3135
replace: node => {
3236
if (node.attribs && node.attribs.id === 'header') {
33-
return {
34-
type: 'h1',
35-
props: { children: 'Heading' }
36-
};
37+
return;
3738
}
3839
}
3940
});

0 commit comments

Comments
 (0)