Last active
June 15, 2022 03:20
-
-
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)
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
/* | |
* 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; |
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
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