-
-
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.