Remark + Unified(Rehype) are the core of the React-Mardown library.

Three different ways to render a videos using React-Markdown

Some time ago, while we were building the portfolio of your server, we found the need to parse multimedia (images and videos), from markdown language to html, so that they would be displayed correctly on the screen; at the time we solved it thanks to a small tutorial (that we could not find again) that we found within a sea of tutorials on plugins (for example, gatsby-remark-videos) for Gatsby (remark + video on Google).

Right now, updating the portfolio, we have found different changes in the latest update of the flagship package that allows us to show these beautiful pages with their content: react-markdown. Due to the changes between renderers and components, we have been forced to update the implementation of the small plugin that is responsible for rendering videos, and here we have found three ways to do it.

To begin, the codebase that worked with the previous version of this package is as follows:

1const visit = require("unist-util-visit")
2
3const allowedFiletypes = ["avi", "mp4", "mov", "mkv"]
4
5function video() {
6  function transformer(tree) {
7    visit(tree, "image", (node) => {
8      const fileType = node.url.split(".").pop()
9
10      if (allowedFiletypes.includes(fileType)) node.type = `video`
11    })
12  }
13  return transformer
14}
15
16module.exports = video
17

We go through the whole syntactic tree until we find an image type node, inside it we check the file's url extension (since we are using a CDN) and we check if it is within the set of files considered video (allowedFiletypes); if it is included, then we change the type of the node to video.

Now, the syntax tree now uses hast, instead of mdast, so we have to use different parameters (node.data.hName and node.data.hProperties) to change the data type as Christian Murphy comments on this issue related to this tutorial:

1const visit = require("unist-util-visit");
2
3const allowedFiletypes = ["avi", "mp4", "mov", "mkv"];
4
5function video() {
6  function transformer(tree) {
7    visit(tree, "image", (node) => {
8      const fileType = node.url.split(".").pop();
9
10      if (allowedFiletypes.includes(fileType)) {
11        node.data = node.data || {};
12        node.data.hName = "video";
13        node.data.hProperties = { url: node.url };
14      }
15    });
16  }
17
18  return transformer;
19}
20
21module.exports = video;
22

Additionally, as you can see in the comments on the issue, react-markdown works as follows:

react-markdown-rendering-workflow.png

Since the package now works with both remark and rehype, in order, you need to pass parameters to it by context to rehype so that you can convert an mdast element to a hast one.

For its part, Titus (just), also provides two different ways to render videos, one as a component for the img tag:

1const components = {
2  img({node, src, alt, title}) {
3    // Or `import.meta.url` in Node, or some other base URL
4    const base = window.location.href
5    const pathname = new URL(src, base).pathname
6
7    if (/\.{avi,mp4,mov,mkv}$/.test(pathname)) {
8      // Do something with alt
9      return <MyFancyVideo url={src} title={title} />
10    }
11
12    return <img {...{src, alt, title}} />
13  }
14}
15

And another as rehype plugin:

1var visit = require('unist-util-visit')
2
3function myRehypePlugin() {
4  return transform
5  function transform(tree) {
6    visit(tree, 'element', onelement)
7  }
8  function onelement(element) {
9    if (element.tagName === 'img') {
10      // Or `import.meta.url` in Node, or some other base URL
11      const base = window.location.href
12      const pathname = new URL(element.properties.src, base).pathname
13
14      if (/\.{avi,mp4,mov,mkv}$/.test(pathname)) {
15        element.tagName = 'video'
16        // Do something with `alt`
17      }
18    }
19  }
20}
21

It is up to you to choose the form that suits you best.

If you want to know more about the operation of remark/rehype plugins and different implementations, do not hesitate to consult the following tutorial (quite extensive and complete).

Bonus track: rehype-sanitize and react-syntax-highlighter

If you, as your server, need to render links to other pages and for this you used the option escapeHtml: false, since the latest version it is necessary to use rehype-raw and rehype-sanitize.

However, althoughreact-markdown works perfectly with react-syntax-highlighter, useing the two packages mentioned above together with this package prevents code blocks from being rendered with the appropriate style.

To solve it, we must white-list the parameter className in the list of parameters accepted by hast-util-sanitize (used by rehype-sanitize to keep the syntax tree clean), extending the validation scheme.

Doing this is simple:

1import rehypeRaw from "rehype-raw";
2import rehypeSanitize from "rehype-sanitize";
3import unwrapImages from "remark-unwrap-images";
4import gh from "hast-util-sanitize/lib/github";
5import merge from "deepmerge";
6
7rehypePlugins: [
8    rehypeRaw,
9    [rehypeSanitize, merge(gh, { attributes: { code: ["className"] } })],
10  ],
11

We can declare the rehype plugins with specific options, in this case, we do a deep merge, making use of the deepmerge package, to the validation scheme together with the parameter to enable and the tag for which it will be enabled.

And that's it, with this we can now render code blocks and links safely within the content of our page.

We want to thank Christian Murphy and Titus for the support provided in the solution of these problems, always open to questions and quick to answer; your support has been essential in the achievement of this article, thank you very much!

Front-end
ReactJS
NextJS
Blog
Development