import {Note} from '../_component/note.server.js' export const navSortSelf = 4 export const info = { author: [ {name: 'Titus Wormer', github: 'wooorm', twitter: 'wooorm'} ], published: new Date('2021-10-06'), modified: new Date('2022-08-27') }
This guide explores how to apply syntax highlighting to code blocks. {/* more */} MDX supports standard markdown syntax (CommonMark). It does not apply syntax highlighting to code blocks by default.
There are two ways to accomplish syntax highlighting: at compile time or at runtime. Doing it at compile time means the effort is spent upfront so that readers will have a fast experience as no extra code is sent to them (syntax highlighting needs a lot of code to work). Doing it at runtime gives more flexibility by moving the work to the client. This can result in a slow experience for readers though. It also depends on what framework you use (as in it’s specific to React, Preact, Vue, etc.)
Use either rehype-highlight
(highlight.js
) or @mapbox/rehype-prism
(Prism) by doing something like this:
import rehypeHighlight from 'rehype-highlight'
import {compile} from '@mdx-js/mdx'
const code = `~~~js
console.log(1)
~~~`
console.log(
String(await compile(code, {rehypePlugins: [rehypeHighlight]}))
)
Expand equivalent JSX
<>
<pre>
<code className="hljs language-js">
<span className="hljs-built_in">console</span>.log(
<span className="hljs-number">1</span>)
</code>
</pre>
</>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/github-dark.min.css">
If you chose @mapbox/rehype-prism
, include something like this instead to
get Prism Dark:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/themes/prism-dark.min.css">
Use for example
react-syntax-highlighter
,
by doing something like this:
import SyntaxHighlighter from 'react-syntax-highlighter'
import Post from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<Post components={{code}} />
function code({className, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return match
? <SyntaxHighlighter language={match[1]} PreTag="div" {...props} />
: <code className={className} {...props} />
}
Expand equivalent JSX
<>
<pre>
<div
className="language-js"
style={{
display: 'block',
overflowX: 'auto',
padding: '0.5em',
background: '#F0F0F0',
color: '#444'
}}
>
<code style={{whiteSpace: 'pre'}}>
<span>console.</span>
<span style={{color: '#397300'}}>log</span>
<span>(</span>
<span style={{color: '#880000'}}>1</span>
<span>)</span>
</code>
</div>
</pre>
</>
Markdown supports a meta string for code:
```js filename="index.js"
console.log(1)
```
The meta
part is everything after the language (in thise case, js
).
This is a hidden part of markdown: it’s normally ignored.
But as the above example shows, it’s a useful place to put some extra fields.
@mdx-js/mdx
doesn’t know whether you’re handling code as a component or what
the format of that meta string is, so it defaults to how markdown handles it:
meta
is ignored.
But what if you want to access meta
?
The short answer is: use remark-mdx-code-meta
.
It lets you type JSX attributes in the meta
part which you can access by
with a component for pre
.
The long answer is: do it yourself, however you want, by writing a custom plugin
to interpret the meta
field.
For example, it’s possible to pass that string as a prop with a rehype plugin:
import {visit} from 'unist-util-visit'
/** @type {import('unified').Plugin<Array<void>, import('hast').Root>} */
function rehypeMetaAsAttributes() {
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'code' && node.data && node.data.meta) {
node.properties.meta = node.data.meta
}
})
}
}
This would yields the following JSX:
<>
<pre>
<code className="language-js" meta='filename="index.js"'>
console.log(1)
</code>
</pre>
</>
The meta string in this example looks a lot like HTML attributes.
What if we wanted to add each “attribute” as a prop?
That can be achieved with the same rehype plugin as above with a different
onelement
handler:
// A regex that looks for a simplified attribute name, optionally followed
// by a double, single, or unquoted attribute value
const re = /\b([-\w]+)(?:=(?:"([^"]*)"|'([^']*)'|([^"'\s]+)))?/g
// …
visit(tree, 'element', (node) => {
let match
if (node.tagName === 'code' && node.data && node.data.meta) {
re.lastIndex = 0 // Reset regex.
while ((match = re.exec(node.data.meta))) {
node.properties[match[1]] = match[2] || match[3] || match[4] || ''
}
}
})
// …
This would yields the following JSX:
<>
<pre>
<code className="language-js" filename="index.js">
console.log(1)
</code>
</pre>
</>