hashicorp / next-mdx-remote

Load MDX content from anywhere
Mozilla Public License 2.0
2.74k stars 141 forks source link

Code fences with options? #354

Closed maximilianschmitt closed 1 year ago

maximilianschmitt commented 1 year ago

Hi! I'm using Next.js 13 (app folder) with next-mdx-remote/rsc (version 4.4.1).

So far, everything has been working great. However, I was wondering how I could add options to code fences, such as:

```tsx filename=app.tsx highlight=3,4
// ...
```

I don't need them to do anything, I would just like to be able to access them in my custom <CodeBlock> component, like so:

const components = {
  pre: CodeBlock
}

function CodeBlock({ children, filename, highlight }) {
  // ...
}

If you could point me in the right direction for this, that would be super helpful, thank you!

BRKalow commented 1 year ago

Hiya, this section of the MDX docs might be helpful.

maximilianschmitt commented 1 year ago

Hey @BRKalow, amazing, thank you!

With the help of the docs, I ended up writing the following rehype plugin that exposes meta attributes in code blocks to the pre component:

import { visit } from 'unist-util-visit'

// A regex that looks for a simplified attribute name, optionally followed
// by a double, single, or unquoted attribute value
// Taken from: https://mdxjs.com/guides/syntax-highlighting/
const re = /\b([-\w]+)(?:=(?:"([^"]*)"|'([^']*)'|([^"'\s]+)))?/g

function rehypeMetaAsAttributes() {
    return (tree: any) => {
        visit(tree, 'element', (node) => {
            if (node.tagName !== 'pre') {
                return
            }

            const child = node.children[0]
            if (!child) {
                return
            }

            if (child.tagName !== 'code' || !child.data || !child.data.meta) {
                return
            }

            node.properties.meta = child.data.meta

            const language = child.properties?.className?.[0]?.replace(/language-/, '') || ''
            node.properties.language = language

            re.lastIndex = 0 // Reset regex
            let match: any
            while ((match = re.exec(child.data.meta))) {
                node.properties[match[1]] = match[2] || match[3] || match[4] || ''
            }
        })
    }
}

export default rehypeMetaAsAttributes