diff --git a/index.js b/index.js index 008c1d3..21a03d2 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ function rehypeReact(options) { var createElement = settings.createElement var Fragment = settings.Fragment var components = settings.components || {} + var passNode = settings.passNode this.Compiler = compiler @@ -38,7 +39,14 @@ function rehypeReact(options) { // Wrap `createElement` to pass components in. function h(name, props, children) { - var component = has.call(components, name) ? components[name] : name + var component = name + if (has.call(components, name)) { + component = components[name] + if (passNode) { + props.node = this + } + } + return createElement(component, props, children) } } diff --git a/readme.md b/readme.md index fa9e0e5..cfc72b5 100644 --- a/readme.md +++ b/readme.md @@ -134,6 +134,11 @@ instead of `

`, so something like this: React key prefix (`string`, default: `'h-'`). +###### `options.passNode` + +Pass the original hast node as `props.node` to custom React components +(`boolean`, default: `false`). + ## Security Use of `rehype-react` can open you up to a [cross-site scripting (XSS)][xss] diff --git a/test.js b/test.js index 167760a..d45c4f5 100644 --- a/test.js +++ b/test.js @@ -141,5 +141,26 @@ test('React ' + React.version, function (t) { 'should transform an element with align property' ) + const headingNode = h('h1') + const Heading1 = function (props) { + return React.createElement('h1', props) + } + t.deepEqual( + unified() + .use(rehype2react, { + createElement: React.createElement, + passNode: true, + components: { + h1: Heading1 + } + }) + .stringify(u('root', [headingNode, h('p')])), + React.createElement('div', {}, [ + React.createElement(Heading1, {key: 'h-2', node: headingNode}, undefined), + React.createElement('p', {key: 'h-3'}, undefined) + ]), + 'should expose node from node prop' + ) + t.end() }) diff --git a/types/index.d.ts b/types/index.d.ts index aaf5bb3..e526fba 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -2,11 +2,17 @@ import {Transformer} from 'unified' import {Prefix, CreateElementLike} from 'hast-to-hyperscript' +import {Node} from 'unist' declare namespace rehypeReact { type FragmentLike = (props: any) => T | null - type ComponentLike = (props: {[prop: string]: unknown}) => T | null + interface ComponentProps { + [prop: string]: unknown + node?: Node + } + + type ComponentLike = (props: ComponentProps) => T | null interface Options { /** @@ -34,6 +40,13 @@ declare namespace rehypeReact { * @defaultValue 'h-' */ prefix?: Prefix + + /** + * Expose HAST Node objects to `node` prop of react components + * + * @defaultValue false + */ + passNode?: boolean } } diff --git a/types/rehype-react-test.tsx b/types/rehype-react-test.tsx index 7e6fcd8..06d0e2a 100644 --- a/types/rehype-react-test.tsx +++ b/types/rehype-react-test.tsx @@ -43,6 +43,19 @@ unified().use(rehypeToReact, { } }) +unified().use(rehypeToReact, { + createElement: React.createElement, + passNode: true +}) + +unified().use(rehypeToReact, { + createElement: React.createElement, + passNode: true, + components: { + a: (props: rehypeToReact.ComponentProps) => {props.node} + } +}) + // Mismatch between framework of createElement and components or Fragment should fail unified().use(rehypeToReact, { createElement: virtualDomCreateElement,