react-remarkify
Version:
A simple React.js utility to convert Markdown into React components.
63 lines (62 loc) • 3.06 kB
JavaScript
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
import { useMemo, isValidElement, cloneElement, useCallback, useState, useEffect, useRef } from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import rehypeReact from "rehype-react";
import remarkParse from "remark-parse";
import remarkToRehype from "remark-rehype";
import { unified } from "unified";
import { nodeToKey, tryCatch } from "./lib/utils.js";
function useStableValue(value, mode, delay) {
const [stableValue, setStableValue] = useState(value);
const lastUpdated = useRef(0);
useEffect(() => {
let timeout = null;
if (mode === "immediate" || delay <= 0)
timeout = setTimeout(() => setStableValue(value), 0);
else if (mode === "debounce")
timeout = setTimeout(() => setStableValue(value), delay);
else if (mode === "throttle") {
const now = Date.now();
const elapsed = now - lastUpdated.current;
timeout = setTimeout(() => {
setStableValue(value);
lastUpdated.current = Date.now();
}, Math.max(0, delay - elapsed));
}
return () => {
if (timeout)
clearTimeout(timeout);
};
}, [value, mode, delay]);
return stableValue;
}
export function useRemark({ markdown, stableMarkdown = false, rehypePlugins = [], rehypeReactOptions, remarkParseOptions, remarkPlugins = [], remarkToRehypeOptions, components, updateMode = "immediate", updateDelay = 0, onError = console.error, }) {
const processor = useMemo(() => unified()
.use(remarkParse, remarkParseOptions)
.use(remarkPlugins)
.use(remarkToRehype, remarkToRehypeOptions)
.use(rehypePlugins)
.use(rehypeReact, Object.assign(Object.assign({}, rehypeReactOptions), { Fragment, jsx, jsxs })), []);
const processReactNode = useCallback((node, index = 0) => {
var _a;
if (typeof node === "string") {
const { success, data, error } = tryCatch(() => {
const file = processor.processSync(node);
return (components ? processor.runSync(processor.parse(file), file) : file.result);
});
if (success)
return components ? toJsxRuntime(data, { Fragment, components, ignoreInvalidStyle: true, jsx, jsxs, passKeys: true, passNode: true }) : data;
onError(error);
return node;
}
if (Array.isArray(node))
return node.map(processReactNode);
if (isValidElement(node))
return cloneElement(node, { key: (_a = node.key) !== null && _a !== void 0 ? _a : index, children: processReactNode(node.props.children) });
return null;
}, []);
const key = useMemo(() => (stableMarkdown ? markdown : nodeToKey(markdown)), [markdown, stableMarkdown]);
const stableKey = useStableValue(key, updateMode, updateDelay);
const reactContent = useMemo(() => processReactNode(markdown), [stableKey]);
return reactContent;
}