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:
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!