Skip to content

Instantly share code, notes, and snippets.

@andrelandgraf
Last active June 15, 2022 03:20
Show Gist options
  • Save andrelandgraf/895d6251d9d3c8160251d86cd3c10d50 to your computer and use it in GitHub Desktop.
Save andrelandgraf/895d6251d9d3c8160251d86cd3c10d50 to your computer and use it in GitHub Desktop.
How to process markdown without async code to make it work with server-side React and common-js (e.g. in Remix.run)
/*
* DEPRECATION NOTICE: This workaround is no longer needed. You can now use ESM packages with Remix by
* tracking all ESM packages in your remix.config.js file. This means we can now use react-markdown
* and are not required to reimplement everything from scratch.
* Find a nice example implementation here: https://stackblitz.com/edit/node-ydjuwx?file=remix.config.js
*/
/*
* react-remark does not support cjs anymore but Remix.run will not work with esm just yet (without async imports)
* Unfortunately, the older versions of react-remark do not implement useRemarkSync
* Thus, right now there is no way to use react-remark in remix to render markdown server-side
* since it will always require async code (and require a useEffect)
*
* My solution: Using old versions of remark, unified, and rehype directly to basically re-implement react-remark
*/
import { FC, ReactElement, useMemo } from 'react';
import { Fragment, createElement } from 'react';
import unified from 'unified';
import remarkParse from 'remark-parse';
import remarkToRehype from 'remark-rehype';
import rehypeReact, { Options as RehypeReactOptions } from 'rehype-react';
// My custom components: Replace this with your own custom components or delete it
import H1 from './h1';
import H2 from './h2';
import H3 from './h3';
import H4 from './h4';
import Paragraph from './p';
import OrderedList from './ol';
import UnorderedList from './ul';
import CodeBlock from './pre';
import DocLink from './link';
import ListItem from './li';
import DocImage from './img';
import HorizontalLine from './hr';
import Decoder from './decoder';
import Blockquote from './blockquote';
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type ReactOptions = PartialBy<RehypeReactOptions<typeof createElement>, 'createElement'>;
export const useRemarkSync = (source: string, rehypeReactOptions: ReactOptions): React.ReactElement =>
useMemo(
() =>
unified()
.use(remarkParse)
.use(remarkToRehype)
.use(rehypeReact, {
createElement,
Fragment,
...rehypeReactOptions,
} as RehypeReactOptions<typeof createElement>)
.processSync(source).result as React.ReactElement,
[source, rehypeReactOptions],
);
interface MarkdownContainerProps {
source: string; // The markdown string
}
const MarkdownContainer: FC<MarkdownContainerProps> = ({ source }) => {
const html = useRemarkSync(source, {
// Optional: a mapping of html tags to custom React components
components: {
h1: H1,
h2: H2,
h3: H3,
h4: H4,
p: Paragraph,
ol: OrderedList,
ul: UnorderedList,
li: ListItem,
pre: CodeBlock,
code: Decoder,
a: DocLink,
img: DocImage,
hr: HorizontalLine,
blockquote: Blockquote,
},
});
return html;
};
export default MarkdownContainer;
THESE VERSIONS ARE REQUIRED AS THEY STILL SUPPORT CJS:
"rehype-react": "^6.2.1",
"remark-parse": "^9.0.0",
"remark-rehype": "^8.1.0",
"unified": "^9.2.2"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment