UNPKG

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