Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change to support preact, solid, svelte, vue, too
This switches from `Fragment`/`createElement` to a JSX runtime (such as from `'react/jsx-runtime'`), to support many more frameworks: ```diff - import {Fragment, createElement} from 'react' + import * as production from 'react/jsx-runtime' - .use(rehypeReact, {Fragment, createElement}) + .use(rehypeReact, production) ```
- Loading branch information
Showing
9 changed files
with
393 additions
and
432 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,5 +4,4 @@ node_modules/ | |
*.d.ts | ||
*.log | ||
yarn.lock | ||
!/lib/complex-types.d.ts | ||
!/index.d.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,47 @@ | ||
import type {Root} from 'hast' | ||
import type {ReactElement} from 'react' | ||
import type {Plugin} from 'unified' | ||
import type {Options} from './lib/index.js' | ||
|
||
export type {Components, Options} from 'hast-util-to-jsx-runtime' | ||
|
||
/** | ||
* Plugin to compile to React | ||
* Turn HTML into preact, react, solid, svelte, vue, etc. | ||
* | ||
* ###### Result | ||
* | ||
* This plugin registers a compiler that returns a `JSX.Element` where | ||
* compilers typically return `string`. | ||
* When using `.stringify` on `unified`, the result is such a `JSX.Element`. | ||
* When using `.process` (or `.processSync`), the result is available at | ||
* `file.result`. | ||
* | ||
* ###### Frameworks | ||
* | ||
* There are differences between what JSX frameworks accept, such as whether | ||
* they accept `class` or `className`, or `background-color` or | ||
* `backgroundColor`. | ||
* | ||
* For hast elements transformed by this project, this is be handled through | ||
* options: | ||
* | ||
* | Framework | `elementAttributeNameCase` | `stylePropertyNameCase` | | ||
* | --------- | -------------------------- | ----------------------- | | ||
* | Preact | `'html'` | `'dom'` | | ||
* | React | `'react'` | `'dom'` | | ||
* | Solid | `'html'` | `'css'` | | ||
* | Vue | `'html'` | `'dom'` | | ||
* | ||
* @param options | ||
* Configuration. | ||
* Configuration (required). | ||
* @returns | ||
* Nothing. | ||
*/ | ||
// Note: defining all react nodes as result value seems to trip TS up. | ||
declare const rehypeReact: Plugin<[Options], Root, ReactElement<unknown>> | ||
declare const rehypeReact: Plugin<[Options], Root, JSX.Element> | ||
export default rehypeReact | ||
|
||
export type {Options} from './lib/index.js' | ||
// Register the result type. | ||
declare module 'unified' { | ||
interface CompileResultMap { | ||
JsxElement: JSX.Element | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
// Note: types exposed from `index.d.ts`. | ||
export {default} from './lib/index.js' |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,131 +1,29 @@ | ||
/** | ||
* @typedef {import('hast').Root} Root | ||
* @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 | ||
* Base configuration (without `components`). | ||
* @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. | ||
* @property {boolean|undefined} [fixTableCellAlign=true] | ||
* Fix obsolete align attributes on table cells by turning them | ||
* into inline styles. | ||
* Keep it on when working with markdown, turn it off when working | ||
* with markup for emails. | ||
* The default is `true`. | ||
* | ||
* @typedef {SharedOptions & (import('./complex-types.js').ComponentsWithNodeOptions | import('./complex-types.js').ComponentsWithoutNodeOptions)} Options | ||
* Configuration. | ||
* @typedef {import('hast-util-to-jsx-runtime').Options} Options | ||
* @typedef {import('unified').Compiler<Root, JSX.Element>} Compiler | ||
* @typedef {import('unified').Processor<undefined, undefined, undefined, Root, JSX.Element>} Processor | ||
*/ | ||
|
||
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']) | ||
import {toJsxRuntime} from 'hast-util-to-jsx-runtime' | ||
|
||
/** | ||
* Compile HTML to React nodes. | ||
* | ||
* > 👉 **Note**: this compiler returns a React node where compilers typically | ||
* > return `string`. | ||
* > When using `.stringify`, the result is such a React node. | ||
* > When using `.process` (or `.processSync`), the result is available at | ||
* > `file.result`. | ||
* Turn HTML into preact, react, solid, svelte, vue, etc. | ||
* | ||
* @this {import('unified').Processor} | ||
* @type {import('unified').Plugin<[Options], Root, ReactElement>} | ||
* @param {Options} options | ||
* Configuration (required). | ||
* @returns {undefined} | ||
* Nothing. | ||
*/ | ||
export default function rehypeReact(options) { | ||
if (!options || typeof options.createElement !== 'function') { | ||
throw new TypeError('createElement is not a function') | ||
} | ||
|
||
const createElement = options.createElement | ||
|
||
const fixTableCellAlign = options.fixTableCellAlign !== false | ||
|
||
Object.assign(this, {Compiler: compiler}) | ||
|
||
// @ts-expect-error: to do: register result. | ||
/** @type {import('unified').Compiler<Root, ReactNode>} */ | ||
function compiler(node) { | ||
/** @type {ReactNode} */ | ||
let result = toH( | ||
// @ts-expect-error: assume `name` is a known element. | ||
h, | ||
fixTableCellAlign ? tableCellStyle(node) : 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 {Array<ReactNode>} [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>. | ||
// See: <https://github.com/rehypejs/rehype-react/pull/29>. | ||
// See: <https://github.com/rehypejs/rehype-react/pull/32>. | ||
// See: <https://github.com/rehypejs/rehype-react/pull/45>. | ||
if (children && tableElements.has(name)) { | ||
children = children.filter( | ||
(child) => typeof child !== 'string' || !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) | ||
} | ||
// @ts-expect-error: TypeScript doesn’t handle `this` well. | ||
// eslint-disable-next-line unicorn/no-this-assignment | ||
const self = /** @type {Processor} */ (this) | ||
|
||
return createElement(component, props, children) | ||
} | ||
self.compiler = compiler | ||
|
||
return createElement(name, props, children) | ||
/** @type {Compiler} */ | ||
function compiler(tree, file) { | ||
return toJsxRuntime(tree, {filePath: file.path, ...options}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.