Next.js: Allow @next/mdx plugin to accept webpack loaders

Created on 30 Jan 2020  路  7Comments  路  Source: vercel/next.js

Feature request

Is your feature request related to a problem? Please describe.

The problem is quite similar to what has been described in the #8857 issue. I want to use the front-matter in my markdown files to export any meta data. Currently we can export a variable from mdx files e.g.

export const meta = {}

In this post we will ....

and it will work. But that is more of a tech heavy solution. Writing that meta data in frontmatter would be much helpful for non-techy people to write blogs. That is the first part.

Next, what I want is to add some custom properties like short info, time to read, formatted published date to each markdown based on it's content i.e. assume that we have following markdown file

---
title: 'My Blog'
date: '2020-12-12'
---

In this post we will ....

Expected output (input for @mdx-js/loader after processing with my custom logic) after formatting date, calculating time to read based on content, adding a description like so

export const meta = {
  title: 'My Blog',
  displayDate: 'December 12, 2020',
  description: 'In this pos',
  timeToRead: '2 min read'
}


In this post we will ....

And the blog writer doesn't need to care about it.

Describe the solution you'd like

This can be easily accomplished if we can just provide a plugin option to take loaders for webpack i.e.

module.exports = (pluginOptions = {}) => (nextConfig = {}) => {
  const extension = pluginOptions.extension || /\.mdx$/
+ const loaders = plugionOptions.loaders || []
  return Object.assign({}, nextConfig, {
    webpack(config, options) {
      config.module.rules.push({
        test: extension,
        use: [
          options.defaultLoaders.babel,
          {
            loader: '@mdx-js/loader',
            options: pluginOptions.options,
          },
+         ...loaders
        ],
      })

and when using the @next/mdx plugin, on will just have to pass the loaders (optional)

module.exports = require("@next/mdx")({
+  loaders: [],
})( .... )

Describe alternatives you've considered

Currently I am using the above code snippet loaded via a custom nextjs plugin. see here

module.exports = require("@next/mdx")()( .... )
module.exports = require("./my-plugin")()( .... )

Additional context

I can create a PR if this feature may be useful to the community.

Most helpful comment

I could not found any library that would fit my need out of box, so here is the custom plugin that I wrote. It may be useful to anybody else stumbling over this issue.

All 7 comments

Adding this would conflict with future plans, so it's unlikely we'll add it.

Alright. Thank you.

Do you think we can add a loader to the webpack for the /.mdx?$/ extension and it would work. i.e.

modules.exports = require("@next/mdx")()({
    webpack() {
      config.module.rules.push({
        test: /\.mdx?$/,
        use: [
          path.join(__dirname, './frontmatter-loader'),
        ],
      })
    }
})

I have no idea.

you can create a custom implementation, but you could also write a MDX plugin that converts frontmatter to export const meta

Oh.. okay. Then I'll do that only and pass my plugin to the @next/mdx's pluginOptions. Hope it's is not too tough to write a mdx plugin 馃槃

Thank you again.

I could not found any library that would fit my need out of box, so here is the custom plugin that I wrote. It may be useful to anybody else stumbling over this issue.

I figured out even a bit shorter solution using remark-frontmatter (note: you have to use version 2):

// next.config.js
const yaml = require('yaml');
const frontmatter = require('remark-frontmatter');

const exportFrontMatter = () => (tree) => {
  if (tree.children[0].type === 'yaml') {
    tree.children.push({
      type: 'export',
      value: `export const frontMatter = ${JSON.stringify(
        yaml.parse(tree.children[0].value)
      )}`,
    });
  }
};

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [frontmatter, exportFrontMatter],
  },
});

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'md', 'mdx'],
});

Actually I came up with a way how to read the front matter from parent component (e.g. layout), working for me so far:

// next.config.js
const yaml = require('yaml');
const remarkFrontmatter = require('remark-frontmatter');

const exportFrontMatter = () => (tree) => {
  if (tree.children[0].type === 'yaml') {
    tree.children.push({
      type: 'export',
      value: `export const frontMatter = ${JSON.stringify(
        yaml.parse(tree.children[0].value)
      )}`,
    });
  }
};

const appendFrontMatter = () => (tree) => {
  if (tree.children[0].type === 'yaml') {
    tree.children.push({
      type: 'export',
      value: `MDXContent.frontMatter = frontMatter;`,
    });
  }
};

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [remarkFrontmatter, exportFrontMatter, appendFrontMatter],
  },
});

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'md', 'mdx'],
});

After that, you can just read the value in _app.js for example:

const { title } = Component.frontMatter;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

knipferrc picture knipferrc  路  3Comments

rauchg picture rauchg  路  3Comments

YarivGilad picture YarivGilad  路  3Comments

swrdfish picture swrdfish  路  3Comments

ghost picture ghost  路  3Comments