/
index.js
111 lines (97 loc) · 3.44 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Element} Element
* @typedef {import('react').ReactNode} ReactNode
* @typedef {import('react').ReactElement<unknown>} ReactElement
*
* @callback CreateElementLike
* @param {any} name
* @param {any} props
* @param {...ReactNode} children
* @returns {ReactNode}
*
* @typedef SharedOptions
* @property {CreateElementLike} createElement
* How to create elements or components.
* You should typically pass `React.createElement`.
* @property {((props: any) => ReactNode)|undefined} [Fragment]
* Create fragments instead of an outer `<div>` if available.
* You should typically pass `React.Fragment`.
* @property {string|undefined} [prefix='h-']
* React key prefix
*
* @typedef {SharedOptions & (import("./complex-types").ComponentsWithNodeOptions|import("./complex-types").ComponentsWithoutNodeOptions)} Options
*/
import {toH} from 'hast-to-hyperscript'
// @ts-expect-error: hush.
import tableCellStyle from '@mapbox/hast-util-table-cell-style'
import {whitespace} from 'hast-util-whitespace'
const own = {}.hasOwnProperty
const tableElements = new Set([
'table',
'thead',
'tbody',
'tfoot',
'tr',
'th',
'td'
])
/**
* @type {import('unified').Plugin<[Options], Root, ReactElement>}
*/
export default function rehypeReact(options) {
if (!options || typeof options.createElement !== 'function') {
throw new TypeError('createElement is not a function')
}
const createElement = options.createElement
Object.assign(this, {Compiler: compiler})
/** @type {import('unified').CompilerFunction<Root, ReactNode>} */
function compiler(node) {
/** @type {ReactNode} */
// @ts-expect-error: assume `name` is a known element.
let result = toH(h, tableCellStyle(node), options.prefix)
if (node.type === 'root') {
// Invert <https://github.com/syntax-tree/hast-to-hyperscript/blob/d227372/index.js#L46-L56>.
result =
result &&
typeof result === 'object' &&
'type' in result &&
'props' in result &&
result.type === 'div' &&
(node.children.length !== 1 || node.children[0].type !== 'element')
? // `children` does exist.
// type-coverage:ignore-next-line
result.props.children
: [result]
return createElement(options.Fragment || 'div', {}, result)
}
return result
}
/**
* @param {keyof JSX.IntrinsicElements} name
* @param {Record<string, unknown>} props
* @param {unknown[]} [children]
* @returns {ReactNode}
*/
function h(name, props, children) {
// Currently, a warning is triggered by react for *any* white space in
// tables.
// So we remove the pretty lines for now.
// See: <https://github.com/facebook/react/pull/7081>.
// See: <https://github.com/facebook/react/pull/7515>.
// See: <https://github.com/remarkjs/remark-react/issues/64>.
if (children && tableElements.has(name)) {
children = children.filter((child) => !whitespace(child))
}
if (options.components && own.call(options.components, name)) {
const component = options.components[name]
if (options.passNode && typeof component === 'function') {
// @ts-expect-error: `toH` passes the current node.
// type-coverage:ignore-next-line
props = Object.assign({node: this}, props)
}
return createElement(component, props, children)
}
return createElement(name, props, children)
}
}