-
-
Save sudkumar/70834062f9243558846249f2c2f98902 to your computer and use it in GitHub Desktop.
// helps us in parsing the frontmatter from text content | |
const matter = require('gray-matter') | |
// helps us safely stringigy the frontmatter as a json object | |
const stringifyObject = require('stringify-object') | |
// helps us in getting the reading time for a given text | |
const readingTime = require('reading-time') | |
// please make sure you have installed these dependencies | |
// before proceeding further, or remove the require statements | |
// that you don't use | |
/** | |
* This is a plugin for remark in mdx. | |
* This should be a function that may take some options and | |
* should return a function with the following signature | |
* @param tree - the MDXAST | |
* @param file - the file node | |
* @return void - it should mutate the tree if needed | |
*/ | |
module.exports = () => (tree, file) => { | |
// we will get the frontMatter using `gray-matter` | |
const { data: frontMatter, content } = matter(file.contents) | |
// the frontMatter holds the json object of the frontmatter | |
// the content holds the text of markdown except frontmatter | |
// we can do whatever we want with the frontmatter | |
// like, adding the time to read, formatting the date to display, | |
// adding a short description using the content | |
const { text } = readingTime(content) | |
frontMatter.timeToRead = text | |
// finally we will add a `export` node to the tree | |
tree.children.push({ | |
type: 'export', | |
value: `export const frontMatter = ${stringifyObject(frontMatter)}`, | |
}) | |
// now `frontMatter` will be available to use in our codebase | |
// we essentically changed the frontmatter of yml form to a | |
// constant and exported it | |
// now we need to remove the frontmatter from the tree | |
// because it has already been processed by mdx and nodes | |
// have beed created for it assuming it was a markdown content | |
// | |
// remove the thematicBreak "<hr />" to first heading | |
// --- => thematicBreak | |
// title: this | |
// date: 2020-12-12 => becomes heading | |
// --- | |
if (tree.children[0].type === 'thematicBreak') { | |
const firstHeadingIndex = tree.children.findIndex(t => t.type === 'heading') | |
if (firstHeadingIndex !== -1) { | |
// we will mutate the tree.children by removing these nodes | |
tree.children.splice(0, firstHeadingIndex + 1) | |
} | |
} | |
} |
--- | |
title: Hello World | |
date: 2020-12-12 | |
--- | |
Some content | |
<!-- is essentially same as writting --> | |
export const frontMatter = { | |
title: "Hello World" | |
date: "2020-12-12", | |
timeToRead: "3 min read" <!-- this is automatically added so that's an advantage --> | |
} | |
Some content |
// we will import our custom remark plugin | |
const frontmatterRemarkPlugin = require('./frontmatter') | |
// here I'm using next.config.js example to use our custom plugin, which | |
// internally passes these options to `@mdx-js/loader`. | |
// so you can our custom plugin wherever we can use `@mdx-js` | |
// add it to the remarkPlugins option to the @next/mdx plugin | |
const mdxPlugin = require('@next/mdx')({ | |
// these options directly gets passed to `@mdx-js/loader` | |
options: { | |
remarkPlugins: [frontmatterRemarkPlugin], | |
}, | |
}) | |
// export the configuration | |
module.exports = mdxPlugin({ | |
pageExtensions: ['ts', 'tsx', 'md', 'mdx'], | |
}) | |
Great! Just using Next.js 12 I had to change line 22 of frontmatter.js
to:
const { data: frontMatter, content } = matter(file.value)
For some reason, the export appears as text in the rendered page. I followed every step.
Any ideas?
thanks a lot for this gist anyway
EDIT: it seems more complex than I thought vercel/next.js#39590
Maybe the AST changed from MDX 1.0. to 2.0
After hours of digging, I ended up creating my own @next/mdx
loader that works the way I needed (loading frontmatter into a custom layout, customizable via options). Its here: https://github.com/itsjavi/next-mdx-frontmatter
EDIT
See also: mdx-js/mdx#1971 (comment)
I hope this helps someone.
I'm a newb at this, so I couldn't figure out for the life of me what was going on!
After a couple of days, it finally clicked!!
I did not want to create a custom loader! So, I found that we can use abstract-syntax-tree to parse the code into AST and inject it into the tree!
// <root>/unified/plugins/remark-default-export.mjs
import AST from "abstract-syntax-tree";
export default function remarkDefaultExport({
path = "../../app/components/blog.layout",
name = "Layout",
} = {}) {
return (tree, file) => {
const { frontmatter = {}, ...rest } = file.data;
const data = { ...rest, ...frontmatter };
const LAYOUT = {
IMPORT: `import ${name} from "${path}";`,
EXPORT: `export default ${name}(JSON.parse(\`${JSON.stringify(data)}\`));`,
};
tree.children.unshift(
{
type: "mdxjsEsm",
value: LAYOUT.IMPORT,
data: {
estree: AST.parse(LAYOUT.IMPORT),
},
},
{
type: "mdxjsEsm",
default: true,
value: LAYOUT.EXPORT,
data: {
estree: AST.parse(LAYOUT.EXPORT),
},
}
);
};
}
And then:
// <root>/next.config.mjs
import nextMDX from "@next/mdx";
// 3rd party remark plugins
import remarkFrontmatter from "remark-frontmatter";
import remarkParseFrontmatter from "remark-parse-frontmatter";
import remarkReadingTime from "remark-reading-time";
import remarkGfm from "remark-gfm";
// Custom remark plugins
import remarkDefaultExport from "./unified/plugins/remark-default-export.mjs";
const mdxPlugin = nextMDX({
// these options directly gets passed to `@mdx-js/loader`
options: {
remarkPlugins: [
remarkFrontmatter,
remarkParseFrontmatter,
remarkUnwrapTexts,
remarkReadingTime,
[remarkDefaultExport, { path: "../../app/components/blog.layout" }],
remarkGfm,
],
// etc...
// rehypePlugins: [ rehypePrettyCode /* https://rehype-pretty-code.netlify.app/ */, rehypeSlug, rehypeAutolinkHeadings ]
},
})
And my component looks like:
// <root>/app/components/blog.layout.tsx
function BlogPostLayout(props) { /* ... */ }
type LayoutProps = { /* ... */ }
export default function Layout(layoutProps: LayoutProps) {
return (props: PropsWithChildren<TBlogPostLayout>) => (
<BlogPostLayout {...layoutProps} {...props} />
)
}
Thank you very much for this.