Giter Site home page Giter Site logo

kentcdodds / mdx-bundler Goto Github PK

View Code? Open in Web Editor NEW
1.7K 9.0 74.0 193 KB

🦀 Give me MDX/TSX strings and I'll give you back a component you can render. Supports imports!

License: MIT License

JavaScript 99.43% MDX 0.57%
mdx mdx-bundler esbuild

mdx-bundler's Introduction

mdx-bundler 🦀

Compile and bundle your MDX files and their dependencies. FAST.


Build Status Code Coverage version downloads MIT License All Contributors PRs Welcome Code of Conduct

The problem

You have a string of MDX and various TS/JS files that it uses and you want to get a bundled version of these files to eval in the browser.

This solution

This is an async function that will compile and bundle your MDX files and their dependencies. It uses MDX v3 and esbuild, so it's VERY fast and supports TypeScript files (for the dependencies of your MDX files).

Your source files could be local, in a remote github repo, in a CMS, or wherever else and it doesn't matter. All mdx-bundler cares about is that you pass it all the files and source code necessary and it will take care of bundling everything for you.

FAQ:

"What's so cool about MDX?"

MDX enables you to combine terse markdown syntax for your content with the power of React components. For content-heavy sites, writing the content with straight-up HTML can be annoyingly verbose. Often people solve this using a WSYWIG editor, but too often those fall short in mapping the writer's intent to HTML. Many people prefer using markdown to express their content source and have that parsed into HTML to be rendered.

The problem with using Markdown for your content is if you want to have some interactivity embedded into your content, you're pretty limited. You either need to insert an element that JavaScript targets (which is annoyingly indirect), or you can use an iframe or something.

As previously stated, MDX enables you to combine terse markdown syntax for your content with the power of React components. So you can import a React component and render it within the markdown itself. It's the best of both worlds.

"How is this different from next-mdx-remote?"

mdx-bundler actually bundles dependencies of your MDX files. For example, this won't work with next-mdx-remote, but it will with mdx-bundler:

---
title: Example Post
published: 2021-02-13
description: This is some description
---

# Wahoo

import Demo from './demo'

Here's a **neat** demo:

<Demo />

next-mdx-remote chokes on that import because it's not a bundler, it's just a compiler. mdx-bundler is an MDX compiler and bundler. That's the difference.

"How is this different from the mdx plugins for webpack or rollup?"

Those tools are intended to be run "at build time" and then you deploy the built version of your files. This means if you have some content in MDX and want to make a typo change, you have to rebuild and redeploy the whole site. This also means that every MDX page you add to your site will increase your build-times, so it doesn't scale all that well.

mdx-bundler can definitely be used at build-time, but it's more powerfully used as a runtime bundler. A common use case is to have a route for your MDX content and when that request comes in, you load the MDX content and hand that off to mdx-bundler for bundling. This means that mdx-bundler is infinitely scalable. Your build won't be any longer regardless of how much MDX content you have. Also, mdx-bundler is quite fast, but to make this on-demand bundling even faster, you can use appropriate cache headers to avoid unnecessary re-bundling.

Webpack/rollup/etc also require that all your MDX files are on the local filesystem to work. If you want to store your MDX content in a separate repo or CMS, you're kinda out of luck or have to do some build-time gymnastics to get the files in place for the build.

With mdx-bundler, it doesn't matter where your MDX content comes from, you can bundle files from anywhere, you're just responsible for getting the content into memory and then you hand that off to mdx-bundler for bundling.

"Does this work with Remix/Gatsby/Next/CRA/etc?"

Totally. It works with any of those tools. Depending on whether your meta-framework supports server-side rendering, you'll implement it differently. You might decide to go with a built-time approach (for Gatsby/CRA), but as mentioned, the true power of mdx-bundler comes in the form of on-demand bundling. So it's best suited for SSR frameworks like Remix/Next.

"Why the dodo bird emoji? 🦀"

Why not?

"Why is esbuild a peer dependency?"

esbuild provides a service written in GO that it interacts with. Only one instance of this service can run at a time and it must have an identical version to the npm package. If it was a hard dependency you would only be able to use the esbuild version mdx-bundler uses.

Table of Contents

Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project's dependencies:

npm install --save mdx-bundler esbuild

One of mdx-bundler's dependencies requires a working node-gyp setup to be able to install correctly.

Usage

import {bundleMDX} from 'mdx-bundler'

const mdxSource = `
---
title: Example Post
published: 2021-02-13
description: This is some description
---

# Wahoo

import Demo from './demo'

Here's a **neat** demo:

<Demo />
`.trim()

const result = await bundleMDX({
  source: mdxSource,
  files: {
    './demo.tsx': `
import * as React from 'react'

function Demo() {
  return <div>Neat demo!</div>
}

export default Demo
    `,
  },
})

const {code, frontmatter} = result

From there, you send the code to your client, and then:

import * as React from 'react'
import {getMDXComponent} from 'mdx-bundler/client'

function Post({code, frontmatter}) {
  // it's generally a good idea to memoize this function call to
  // avoid re-creating the component every render.
  const Component = React.useMemo(() => getMDXComponent(code), [code])
  return (
    <>
      <header>
        <h1>{frontmatter.title}</h1>
        <p>{frontmatter.description}</p>
      </header>
      <main>
        <Component />
      </main>
    </>
  )
}

Ultimately, this gets rendered (basically):

<header>
  <h1>This is the title</h1>
  <p>This is some description</p>
</header>
<main>
  <div>
    <h1>Wahoo</h1>

    <p>Here's a <strong>neat</strong> demo:</p>

    <div>Neat demo!</div>
  </div>
</main>

Options

source

The string source of your MDX.

Can not be set if file is set

file

The path to the file on your disk with the MDX in. You will probably want to set cwd as well.

Can not be set if source is set

files

The files config is an object of all the files you're bundling. The key is the path to the file (relative to the MDX source) and the value is the string of the file source code. You could get these from the filesystem or from a remote database. If your MDX doesn't reference other files (or only imports things from node_modules), then you can omit this entirely.

mdxOptions

This allows you to modify the built-in MDX configuration (passed to @mdx-js/esbuild). This can be helpful for specifying your own remarkPlugins/rehypePlugins.

The function is passed the default mdxOptions and the frontmatter.

bundleMDX({
  source: mdxSource,
  mdxOptions(options, frontmatter) {
    // this is the recommended way to add custom remark/rehype plugins:
    // The syntax might look weird, but it protects you in case we add/remove
    // plugins in the future.
    options.remarkPlugins = [...(options.remarkPlugins ?? []), myRemarkPlugin]
    options.rehypePlugins = [...(options.rehypePlugins ?? []), myRehypePlugin]

    return options
  },
})

esbuildOptions

You can customize any of esbuild options with the option esbuildOptions. This takes a function which is passed the default esbuild options and the frontmatter and expects an options object to be returned.

bundleMDX({
  source: mdxSource,
  esbuildOptions(options, frontmatter) {
    options.minify = false
    options.target = [
      'es2020',
      'chrome58',
      'firefox57',
      'safari11',
      'edge16',
      'node12',
    ]

    return options
  },
})

More information on the available options can be found in the esbuild documentation.

It's recommended to use this feature to configure the target to your desired output, otherwise, esbuild defaults to esnext which is to say that it doesn't compile any standardized features so it's possible users of older browsers will experience errors.

globals

This tells esbuild that a given module is externally available. For example, if your MDX file uses the d3 library and you're already using the d3 library in your app then you'll end up shipping d3 to the user twice (once for your app and once for this MDX component). This is wasteful and you'd be better off just telling esbuild to not bundle d3 and you can pass it to the component yourself when you call getMDXComponent.

Global external configuration options: https://www.npmjs.com/package/@fal-works/esbuild-plugin-global-externals

Here's an example:

// server-side or build-time code that runs in Node:
import {bundleMDX} from 'mdx-bundler'

const mdxSource = `
# This is the title

import leftPad from 'left-pad'

<div>{leftPad("Neat demo!", 12, '!')}</div>
`.trim()

const result = await bundleMDX({
  source: mdxSource,
  // NOTE: this is *only* necessary if you want to share deps between your MDX
  // file bundle and the host app. Otherwise, all deps will just be bundled.
  // So it'll work either way, this is just an optimization to avoid sending
  // multiple copies of the same library to your users.
  globals: {'left-pad': 'myLeftPad'},
})
// server-rendered and/or client-side code that can run in the browser or Node:
import * as React from 'react'
import leftPad from 'left-pad'
import {getMDXComponent} from 'mdx-bundler/client'

function MDXPage({code}: {code: string}) {
  const Component = React.useMemo(
    () => getMDXComponent(result.code, {myLeftPad: leftPad}),
    [result.code, leftPad],
  )
  return (
    <main>
      <Component />
    </main>
  )
}

cwd

Setting cwd (current working directory) to a directory will allow esbuild to resolve imports. This directory could be the directory the mdx content was read from or a directory that off-disk mdx should be run in.

content/pages/demo.tsx

import * as React from 'react'

function Demo() {
  return <div>Neat demo!</div>
}

export default Demo

src/build.ts

import {bundleMDX} from 'mdx-bundler'

const mdxSource = `
---
title: Example Post
published: 2021-02-13
description: This is some description
---

# Wahoo

import Demo from './demo'

Here's a **neat** demo:

<Demo />
`.trim()

const result = await bundleMDX({
  source: mdxSource,
  cwd: '/users/you/site/_content/pages',
})

const {code, frontmatter} = result

grayMatterOptions

This allows you to configure the gray-matter options.

Your function is passed the current gray-matter configuration for you to modify. Return your modified configuration object for gray matter.

bundleMDX({
  grayMatterOptions: options => {
    options.excerpt = true

    return options
  },
})

bundleDirectory & bundlePath

This allows you to set the output directory for the bundle and the public URL to the directory. If one option is set the other must be as well.

The Javascript bundle is not written to this directory and is still returned as a string from bundleMDX.

This feature is best used with tweaks to mdxOptions and esbuildOptions. In the example below .png files are written to the disk and then served from /file/.

This allows you to store assets with your MDX and then have esbuild process them like anything else.

It is recommended that each bundle has its own bundleDirectory so that multiple bundles don't overwrite each others assets.

const {code} = await bundleMDX({
  file: '/path/to/site/content/file.mdx',
  cwd: '/path/to/site/content',
  bundleDirectory: '/path/to/site/public/file',
  bundlePath: '/file/',
  mdxOptions: options => {
    options.remarkPlugins = [remarkMdxImages]

    return options
  },
  esbuildOptions: options => {
    options.loader = {
      ...options.loader,
      '.png': 'file',
    }

    return options
  },
})

Returns

bundleMDX returns a promise for an object with the following properties.

Types

mdx-bundler supplies complete typings within its own package.

bundleMDX has a single type parameter which is the type of your frontmatter. It defaults to {[key: string]: any} and must be an object. This is then used to type the returned frontmatter and the frontmatter passed to esbuildOptions and mdxOptions.

const {frontmatter} = bundleMDX<{title: string}>({source})

frontmatter.title // has type string

Component Substitution

MDX Bundler passes on MDX's ability to substitute components through the components prop on the component returned by getMDXComponent.

Here's an example that removes p tags from around images.

import * as React from 'react'
import {getMDXComponent} from 'mdx-bundler/client'

const Paragraph: React.FC = props => {
  if (typeof props.children !== 'string' && props.children.type === 'img') {
    return <>{props.children}</>
  }

  return <p {...props} />
}

function MDXPage({code}: {code: string}) {
  const Component = React.useMemo(() => getMDXComponent(code), [code])

  return (
    <main>
      <Component components={{p: Paragraph}} />
    </main>
  )
}

Frontmatter and const

You can reference frontmatter meta or consts in the mdx content.

---
title: Example Post
---

export const exampleImage = 'https://example.com/image.jpg'

# {frontmatter.title}

<img src={exampleImage} alt="Image alt text" />

Accessing named exports

You can use getMDXExport instead of getMDXComponent to treat the mdx file as a module instead of just a component. It takes the same arguments that getMDXComponent does.

---
title: Example Post
---

export const toc = [{depth: 1, value: 'The title'}]

# The title
import * as React from 'react'
import {getMDXExport} from 'mdx-bundler/client'

function MDXPage({code}: {code: string}) {
  const mdxExport = getMDXExport(code)
  console.log(mdxExport.toc) // [ { depth: 1, value: 'The title' } ]

  const Component = React.useMemo(() => mdxExport.default, [code])

  return <Component />
}

Image Bundling

With the cwd and the remark plugin remark-mdx-images you can bundle images in your mdx!

There are two loaders in esbuild that can be used here. The easiest is dataurl which outputs the images as inline data urls in the returned code.

import {remarkMdxImages} from 'remark-mdx-images'

const {code} = await bundleMDX({
  source: mdxSource,
  cwd: '/users/you/site/_content/pages',
  mdxOptions: options => {
    options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkMdxImages]

    return options
  },
  esbuildOptions: options => {
    options.loader = {
      ...options.loader,
      '.png': 'dataurl',
    }

    return options
  },
})

The file loader requires a little more configuration to get working. With the file loader your images are copied to the output directory so esbuild needs to be set to write files and needs to know where to put them plus the url of the folder to be used in image sources.

Each call to bundleMDX is isolated from the others. If you set the directory the same for everything bundleMDX will overwrite images without warning. As a result each bundle needs its own output directory.

// For the file `_content/pages/about.mdx`

const {code} = await bundleMDX({
  source: mdxSource,
  cwd: '/users/you/site/_content/pages',
  mdxOptions: options => {
    options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkMdxImages]

    return options
  },
  esbuildOptions: options => {
    // Set the `outdir` to a public location for this bundle.
    options.outdir = '/users/you/site/public/img/about'
    options.loader = {
      ...options.loader,
      // Tell esbuild to use the `file` loader for pngs
      '.png': 'file',
    }
    // Set the public path to /img/about
    options.publicPath = '/img/about'

    // Set write to true so that esbuild will output the files.
    options.write = true

    return options
  },
})

Bundling a file.

If your MDX file is on your disk you can save some time and code by having mdx-bundler read the file for you. Instead of supplying a source string you can set file to the path of the MDX on disk. Set cwd to its folder so that relative imports work.

import {bundleMDX} from 'mdx-bundler'

const {code, frontmatter} = await bundleMDX({
  file: '/users/you/site/content/file.mdx',
  cwd: '/users/you/site/content/',
})

Custom Components in Downstream Files

To make sure custom components are accessible in downstream MDX files, you can use the MDXProvider from @mdx-js/react to pass custom components to your nested imports.

npm install --save @mdx-js/react
const globals = {
  '@mdx-js/react': {
    varName: 'MdxJsReact',
    namedExports: ['useMDXComponents'],
    defaultExport: false,
  },
};
const { code } = bundleMDX({
  source,
  globals,
  mdxOptions(options: Record<string, any>) {
      return {
        ...options,
        providerImportSource: '@mdx-js/react',
      };
  }
});

From there, you send the code to your client, and then:

import { MDXProvider, useMDXComponents } from '@mdx-js/react';
const MDX_GLOBAL_CONFIG = {
  MdxJsReact: {
    useMDXComponents,
  },
};
export const MDXComponent: React.FC<{
  code: string;
  frontmatter: Record<string, any>;
}> = ({ code }) => {
  const Component = useMemo(
    () => getMDXComponent(code, MDX_GLOBAL_CONFIG),
    [code],
  );

  return (
    <MDXProvider components={{ Text: ({ children }) => <p>{children}</p> }}>
      <Component />
    </MDXProvider>
  );
};

Known Issues

Cloudflare Workers

We'd love for this to work in cloudflare workers. Unfortunately cloudflares have two limitations that prevent mdx-bundler from working in that environment:

  1. Workers can't run binaries. bundleMDX uses esbuild (a binary) to bundle your MDX code.
  2. Workers can't run eval or similar. getMDXComponent evaluates the bundled code using new Function.

One workaround to this is to put your mdx-bundler related code in a different environment and call that environment from within the Cloudflare worker. IMO, this defeats the purpose of using Cloudflare workers. Another potential workaround is to use WASM from within the worker. There is esbuild-wasm but there are some issues with that package explained at that link. Then there's wasm-jseval, but I couldn't get that to run code that was output from mdx-bundler without error.

If someone would like to dig into this, that would be stellar, but unfortunately it's unlikely I'll ever work on it.

Next.JS esbuild ENOENT

esbuild relies on __dirname to work out where is executable is, Next.JS and Webpack can sometimes break this and esbuild needs to be told manually where to look.

Adding the following code before your bundleMDX will point esbuild directly at the correct executable for your platform.

import path from 'path'

if (process.platform === 'win32') {
  process.env.ESBUILD_BINARY_PATH = path.join(
    process.cwd(),
    'node_modules',
    'esbuild',
    'esbuild.exe',
  )
} else {
  process.env.ESBUILD_BINARY_PATH = path.join(
    process.cwd(),
    'node_modules',
    'esbuild',
    'bin',
    'esbuild',
  )
}

More information on this issue can be found in this article.

Inspiration

As I was rewriting kentcdodds.com to remix, I decided I wanted to keep my blog posts as MDX, but I didn't want to have to compile them all at build time or be required to redeploy every time I fix a typo. So I made this which allows my server to compile on demand.

Other Solutions

There's next-mdx-remote but it's more of an mdx-compiler than a bundler (can't bundle your mdx for dependencies). Also it's focused on Next.js whereas this is meta-framework agnostic.

Issues

Looking to contribute? Look for the Good First Issue label.

πŸ› Bugs

Please file an issue for bugs, missing documentation, or unexpected behavior.

See Bugs

πŸ’‘ Feature Requests

Please file an issue to suggest new features. Vote on feature requests by adding a πŸ‘. This helps maintainers prioritize what to work on.

See Feature Requests

Contributors ✨

Thanks goes to these people (emoji key):

Kent C. Dodds
Kent C. Dodds

πŸ’» πŸ“– πŸš‡ ⚠️
benwis
benwis

πŸ› πŸ‘€
Adam Laycock
Adam Laycock

πŸ’» ⚠️ πŸ€” πŸ‘€ πŸ“–
Titus
Titus

πŸ€” πŸ‘€ πŸ’»
Christian Murphy
Christian Murphy

πŸ€”
Pedro Duarte
Pedro Duarte

πŸ“–
Erik Rasmussen
Erik Rasmussen

πŸ“–
Omar Syx
Omar Syx

πŸ›
GaΓ«l HamΓ©on
GaΓ«l HamΓ©on

πŸ“–
Gabriel LoiΓ‘cono
Gabriel LoiΓ‘cono

πŸ’» ⚠️
Spencer Miskoviak
Spencer Miskoviak

πŸ“–
Casper
Casper

πŸ’»
Apostolos Christodoulou
Apostolos Christodoulou

πŸ“–
Yordis Prieto
Yordis Prieto

πŸ’»
xoumi
xoumi

πŸ’»
Yasin
Yasin

πŸ’»
Mohammed 'Mo' Mulazada
Mohammed 'Mo' Mulazada

πŸ“–
Can Rau
Can Rau

πŸ“–
Hosenur Rahaman
Hosenur Rahaman

πŸ“–
Maciek Sitkowski
Maciek Sitkowski

πŸ“–
Priyang
Priyang

πŸ’» πŸ“–
Mosaad
Mosaad

πŸ“–
stefanprobst
stefanprobst

πŸ’» ⚠️
Vlad Moroz
Vlad Moroz

πŸ’»

This project follows the all-contributors specification. Contributions of any kind welcome!

LICENSE

MIT

mdx-bundler's People

Contributors

allcontributors[bot] avatar arcath avatar beaussan avatar canrau avatar casperiv0 avatar cottoncandyz avatar danielolaviobr avatar erikras avatar gaelhameon avatar hosenur avatar karlhorky avatar kentcdodds avatar loiacon avatar michaeldeboey avatar mohammedmulazada avatar priyang12 avatar prochalu avatar silvenon avatar sitek94 avatar skovy avatar stefanprobst avatar themosaad avatar tol-is avatar vladmoroz avatar xoumi avatar yasinmiran avatar yordis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mdx-bundler's Issues

Error: Build failed with 1 error: node_modules/esbuild/lib/main.js:215:12: error: [esbuild-xdm] Invalid option in element 0 of "errors": "pluginName"

i keep getting this error whenever i try to add a typescript react component

Error: Build failed with 1 error:
node_modules/esbuild/lib/main.js:215:12: error: [esbuild-xdm] Invalid option in element 0 of "errors": "pluginName"

here is my .mdx file

---
title: First post
---
import Cool from './cool";
<Cool />
This is an my first post. There's another one [here](/posts/second-post).

The file i am using to retrieve data

import { readFileSync, readdirSync } from 'fs';
import path from "path";
import matter from "gray-matter";
import { bundleMDX } from "mdx-bundler";
export const content_path = path.join(process.cwd(), "contents/lessons");
export function getContentSource(file: string) {
 return readFileSync(path.join(content_path, file));
}
export function getAllContents(){
    return readdirSync(content_path).
    filter((path) => /\.mdx?/.test(path)).
    map((file) => {
const source = getContentSource(file);
const slug = file.replace(/\.mdx?$/, "");
const { content } = matter(source);
return {
frontmatter: content,
slug: slug
}
    })
}
export const getLessonData = async (slug: string) => {

if (process.platform === 'win32') {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      'node_modules',
      'esbuild',
      'esbuild.exe',
    )
  } else {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      'node_modules',
      'esbuild',
      'bin',
      'esbuild',
    )
  }
    const source = getContentSource(slug + ".mdx");
  
    const { code, frontmatter } = await bundleMDX(source, {
      cwd: content_path,
    });
  
    return {
      frontmatter,
      code,
    };
  };

My index.tsx

import Link from "next/link";
import { getAllContents } from "../../lib/build/mdx";

export default function BlogList({ posts }) {
  return (
    <div className="wrapper">
      <h1>All Posts</h1>
      <p>
        Click the link below to navigate to a page generated by{" "}
        <code>mdx-bundler</code>.
      </p>
      <ul>
        {posts.map((post, index) => (
          <li key={index}>
            <Link href={`posts/${post.slug}`}>{post.frontmatter.title}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export const getStaticProps = async () => {
  const posts = getAllContents();

  return {
    props: { posts },
  };
};

My [slug].tsx

import React from "react";
import { getMDXComponent } from "mdx-bundler/client";
import { getAllContents, getLessonData } from "../../lib/build/mdx"

const Post = ({ code, frontmatter }) => {
  const Component = React.useMemo(() => getMDXComponent(code), [code]);
  return (
    <div className="wrapper">
      <h1>{frontmatter.title}</h1>
      <Component />
    </div>
  );
};

export const getStaticProps = async ({ params }) => {
  const post = await getLessonData(params.slug);
  return {
    props: { ...post },
  };
};

export const getStaticPaths = async () => {
  const paths = getAllContents().map(({ slug }) => ({ params: { slug } }));
  return {
    paths,
    fallback: false,
  };
};

export default Post;

More details:
node: 14
mdx-bundler: 5.1.0
framework: nextjs

Does not work on Vercel inside getServerSideProps from Next.js

Thanks for making this awesome npm package! πŸ˜„

  • mdx-bundler version: 4.0.0
  • node version: 14.16.1
  • npm version: 7.12.1

Problem description:

The bundleMDX function seems to doesn't work in Vercel when it is in getServerSideProps function from Next.js, but it actually works if it is in getStaticProps.
Also both work in local, I don't have errors at all on my local machine.
You can see see that currently it is a 500 error page : https://divlo-nlxbiy7k2-divlo.vercel.app/blog/hello-world
I saw that there was similar closed issue : #2

You can see the relevant code there : https://github.com/Divlo/Divlo/blob/feat/add-blog/pages/blog/%5Bslug%5D.tsx

Here is the log from the serverless function in Vercel :

ERROR	Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/var/task/node_modules/periscopic/node_modules/estree-walker/dist/esm/estree-walker.js' imported from /var/task/node_modules/periscopic/src/index.js
Did you mean to import estree-walker/dist/umd/estree-walker.js?
    at finalizeResolution (internal/modules/esm/resolve.js:276:11)
    at moduleResolve (internal/modules/esm/resolve.js:699:10)
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
    at Loader.resolve (internal/modules/esm/loader.js:86:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:230:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:56:40)
    at link (internal/modules/esm/module_job.js:55:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Thanks for your help!

Using custom typescript components (not replacements) breaking build.

Has any had difficulty using custom Typescript components (not replacement components) imported via the files props. My component renders when it's JavaScript but when I add TypeScript, such as an interface for its props, it breaks. I'm assuming what the bundler is doing is mapping the myComponent string to the contents of util/MyComponent.tsx file. Because the typescript isn't be parsed before get called in an .mdx, it breaks.

    const { code, frontmatter } = await bundleMDX(source, {
        cwd: NOTES_DATA_PATH,
        files: {
             "./myComponent": "util/MyComponent.tsx"
        },
    });

I cannot import the component into my mdx file

When i was trying to import the components into my mdx file i got this error

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

I am using nextjs 11 with latest mdx-bundler version
here is my file which bundler mdx to html

import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'

import { bundleMDX } from 'mdx-bundler'
if (process.platform === 'win32') {
  process.env.ESBUILD_BINARY_PATH = path.join(
    process.cwd(),
    'node_modules',
    'esbuild',
    'esbuild.exe',
  );
} else {
  process.env.ESBUILD_BINARY_PATH = path.join(
    process.cwd(),
    'node_modules',
    'esbuild',
    'bin',
    'esbuild',
  );
}
export const contentPath = path.join(process.cwd(), 'articles')
export const componentPath = path.join(process.cwd(), 'src/components');
export const getMdxSource = (mdxFile) => {
  return fs.readFileSync(path.join(contentPath, mdxFile));
}
export const getSortedContent = () => {
  return fs.readdirSync(contentPath).filter((path) => /\.mdx?$/.test(path)).map((mdxFile) => {
    const source = getMdxSource(mdxFile);
      const slug = mdxFile.replace(/\.mdx?$/, "");
      const { data } = matter(source);

      return {
        frontmatter: data,
        slug: slug,
      }
  }).sort((a, b) => {
    return new Date(a.frontmatter?.date) < new Date(b.frontmatter?.date) ? 1 : -1;
  });
}
export const getContentbyData = async(slug) => {
  const source = getMdxSource(slug + ".mdx");
  const { code, frontmatter } = await bundleMDX(source, {
    cwd: process.cwd(),
 })

  return {
    frontmatter,
    code,
  };
}

my mdx file

---
title: What is Programming ?
author: IshanKBG
date: 07-24-2021
---
import Hello from "src/components/Hello"

Before knowing what is programming you must know the what are computers and how they work.
I know that most of you know what are computers and you are actually using it to read this article.
If I say in simple words computers are just machines performing arithmetical and logical operations for you. Computers run programs which contains some sets of instructions to perform different tasks for you and programming is a process of writing set of instructions to tell computer to perform certain task. For example imagine you are trying to make a cake and to make it you have to follow some sort of instructions.
Those instructions must be in an arranged and sequenced manner else your cake will not become as you want to be as you can’t add eggs after baking. Making a cake is a same as writing programs as you have to write each and every piece of code in an arranged and sequenced manner in order to make it work as intended.
# Should I learn programming ?
<Hello />
This type of question has multiple answers, it depends on the person who is willing to either learn it or not. For some people programming is just a job, for some it’s only business or money and for some people it’s a grand passion of their life. Some want to learn programming so that they can stimulate robotic hands, for some it’s game development and for some it’s web development. It depends on where and how you want to apply programming skills. So ask this question to you and you will find the answer
Some might ask that why did I learn programming and my answer is that I have always been intrigued by computers and thought of them as a means to bridge the gap between countries and entire continents, allowing people from various communities to communicate with each other, I was fascinated by how it all worked and so I decided to pursue Programming as my career choice.

Github repo: https://github.com/IshanKBG/ishankbg.dev

Module not found: Error: Can't resolve 'fsevents'

  • mdx-bundler version: 1.1.3
  • node version: 14.15.4
  • npm version: 6.14.10
  • next version: 10.0.5

Relevant code or config

prepare-mdx.ts and mdx.ts

What you did:

Tried to build my Next.JS site on my Windows machine.

What happened:

Running next build results in this output:

info  - Creating an optimized production build  
Failed to compile.

ModuleNotFoundError: Module not found: Error: Can't resolve 'fsevents' in 'C:\Users\adam\Documents\GitHub\arcath.net-next\node_modules\mdx-bundler\node_modules\rollup\dist\shared'


> Build error occurred
Error: > Build failed because of webpack errors

Reproduction repository:

Sorry its not very minimal but my full site is here: https://github.com/Arcath/arcath.net-next
The files using mdx-bundler are prepare-mdx.ts and mdx.ts

Problem description:

Build doesn't work on Windows but does on Vercel. There is a seperate issue I need to diagnose which is what prompted me to do a local build.

Suggested solution:

Possibly a change to rollup config? This feels like it could be a rollup issue when running undex next.js.

Allow Image Extensions

  • mdx-bundler version: 3.1.2
  • node version: 14.9
  • npm version: yarn

Relevant code or config

import { D3JS } from "@/lib/technologies";

export const meta = {
  title:
    "Add Interactivity to a Line Graph using scaleLinear.invert to Find a Data Point to Highlight on MouseΒ Move",
  description:
    "",
  type: "TUTORIAL",
  createdAt: "2018-03-20T01:58:20.594+00:00",
  technology: D3JS,
  tags: [],
  images: [require("./highlighted.png")],
};

What you did:

Attempted to add a plugin to resolve
Attempted to supply to files

What happened:
When supplied to files as a string

__mdx_bundler_fake_dir__/index.mdx:16:19: error: [JavaScript plugins] Invalid loader: "png" (valid: js, jsx, ts, tsx, css, json, text, base64, dataurl, file, binary)

Problem description:

In my MDX rather than using frontmatter I leverage a meta export, along with next-images + https://github.com/sergioramos/remark-copy-linked-files

This allows me to colocate all the things next to each other, and then they are processed into Next.js public directory and such.

However mdx-bunlder doesn't let unknown files types fall through to potentially get resolved by a appended plugin

Suggested solution:

I don't know?

Add in an optional way for unknown extensions to fall through to a future resolver?

Build fails when loading react components from a `js` file

  • mdx-bundler version: ^3.4.0
  • node version: v15.14.0
  • npm version: 7.7.6

Relevant code or config

What you did: Use mdx-bundler to use react components to mdx within a Next aplication

What happened:

I get the following error when using components with a .js extension. It seems to work fine if I change the files under /components/shared/* to be .jsx

Server Error

Error: Build failed with 1 error:
__mdx_bundler_fake_dir__/demo.js:3:4: error: Unexpected "<"

I tried using the esbuildOptions and specify a loader for .js files but does not seem to make a difference.

const result = await bundleMDX(post, {
  files: components,
  esbuildOptions: (options) => {
    options.loader = {
      ...options.loader,
      '.js': 'jsx',
    };
    return options;
  },
});

Reproduction repository: next-mdx-simple-blog

Problem description: Build fails when loading react components from a js file

Suggested solution: -

How to convert dynamic data to MDX

Hello, I got data on GraphCMS in markdown text and I'd like to convert it to MDX. Could you tell me how to do it? I've installed mdx-bundler.
content: portfolioItem.portfolios[0].content, // <--- it containts the markdown text from GraphCMS.
I tried with mdx-remote but there were errors with severity vulnerabilities

I got

import { bundleMDX } from "mdx-bundler";

export const getStaticProps = async ({ params }) => {
  const portfolioItem = await getPortfolioItem(params.slug);
  return {
    props: {
      portfolioItem: portfolioItem.portfolios[0],
      content: portfolioItem.portfolios[0].content,
    },
  };
};

export default function Home({ portfolioItem, content }) {
  console.log(portfolioItem);
  return (
        <div className="prose prose-xl max-w-none mt-4 text-justify  dark:text-gray-100 mb-10">
          {content}
        </div>
  );
}

esbuild fails on `Generating static pages` step in serverless Next.js applications

  • mdx-bundler version: 3.1.2
  • node version: any (tried 12, 14, and 15.12)
  • npm version: 7.6.3

Relevant code or config: repl.it of bug

What you did: Added target: "serverless" to a next.js project with mdx-bundler

What happened:
image

Reproduction repository: https://replit.com/@TheoBr/mdx-bundler-static-demo#README.md

Problem description: Seems to be an issue with how esbuild is run in next.js's serverless path. Not functionality I'm super familiar with, I just know that netlify forces it to be enabled. (fwiw I've had no issues using this with vercel (see: tt.fm)

Suggested solution: Bug EvanW or Guillermo maybe? I'm lost on this one

Error: Cannot find module 'esbuild'

  • mdx-bundler version: 6.0.1
  • node version: 14.17.1
  • npm version: 6.14.13

This is my config file - mdx.ts

import { bundleMDX } from "mdx-bundler";
import path from "path";

export const prepareMDX = async () => {
  if (process.platform === "win32") {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      "node_modules",
      "esbuild",
      "esbuild.exe"
    );
  } else {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      "node_modules",
      "esbuild",
      "bin",
      "esbuild"
    );
  }

  const mdxSource = `
---
title: Example Post
published: 2021-02-13
description: This is some description
---

# Wahoo
`.trim();

  const { code, frontmatter } = await bundleMDX(mdxSource);

  return { code, frontmatter };
};

This is my page file - blog.tsx

import Layout from "@/components/Layout";
import { prepareMDX } from "@/lib/mdx";
import { getMDXComponent } from "mdx-bundler/client";
import { useMemo } from "react";

export default function blog({
  code,
  frontmatter
}: {
  code: any;
  frontmatter: any;
}) {
  const Component = useMemo(() => getMDXComponent(code), [code]);
  return (
    <Layout>
      <h2>{frontmatter.title}</h2>
      <p>{frontmatter.description}</p>
      <Component />
    </Layout>
  );
}

export async function getStaticProps() {
  const { code, frontmatter } = await prepareMDX();
  return { props: { code, frontmatter } };
}

Now I'm getting this error

mdx-bundler

Even though I see esbuild package in node_modules but still this error is shown.. why? how to solve it ?

Something wrong with xdm option Typescript types

"esbuild": "^0.12.20",
"mdx-bundler": "^5.2.1",
"remark-gfm": "^2.0.0",
"typescript": "^4.3.5",

node 16.6.2
npm 7.20.3

Relevant code or config

import { bundleMDX } from "mdx-bundler";
import remarkGfm from "remark-gfm";
import { compile } from "xdm";

const source = `# hello world`;

async function main() {
  const { code } = await bundleMDX(source, {
    xdmOptions: (options) => {
      options.remarkPlugins = [remarkGfm];
      return options;
    },
  });

  const output = await compile(source, {
    remarkPlugins: [remarkGfm],
  });
  console.log(code);
  console.log(output);
}

main();

What you did:

I've upgraded remark-gfm to 2.0.0.
Not sure if it's related but the package is now only exporting ESM.

What happened:

Nothing has changed while using it with xdm but using it with mdx-bundler something must be wrong with the definition of xdm options and Typescript is not happy about it.

Type '(options?: void | Options | undefined) => void | Transformer<Root, Root>' is not assignable to type 'Pluggable<any[], Settings>'.
  Type '(options?: void | Options | undefined) => void | Transformer<Root, Root>' is not assignable to type 'Plugin<any[], Settings>'.
    Type 'void | Transformer<Root, Root>' is not assignable to type 'void | Transformer'.
      Type 'Transformer<Root, Root>' is not assignable to type 'void | Transformer'.
        Type 'Transformer<Root, Root>' is not assignable to type 'Transformer'.
          Types of parameters 'node' and 'node' are incompatible.
            Property 'children' is missing in type 'Node<Data>' but required in type 'Root'.

Screenshot 2021-08-13 at 15 17 26

Reproduction repository:
https://github.com/mtt87/ts-issue-mdx-bundler

Suggested solution:

I'm not sure I checked xdm options and they use the type

type CompileOptions = CoreProcessorOptions & PluginOptions & ExtraOptions

I tried to change in the mdx-bundler types definition

xdmOptions?: (options: CoreProcessorOptions) => CoreProcessorOptions
// changed to
xdmOptions?: (options: CompileOptions) => CompileOptions

But the error is still there

Possible change in documentation for Image Bundling

  • mdx-bundler version: 4.0.0
  • node version: 14.16.1
  • yarn version: 1.22.10

Relevant code

const result = await bundleMDX(markdownSource, {
  cwd: path.join(process.cwd(), "/posts"),
  esbuildOptions: (options) => {
    options.outdir = path.join(process.cwd(), "/public/generated");
    options.loader = {
      ...options.loader,
      ".png": "file",
    };

    options.publicPath = "/generated/";

    options.write = true;

    return options;
  },
});

What happened:

I followed the bundling images guide in the documentation. See my bundleMDX function above. All my images will be bundled into the notouch/ directory under public/.

When you build with this config, you might get these errors:

Errors

(1)

> Build error occurred
Error: Export encountered errors on following paths:
        /
    at /.../node_modules/next/dist/export/index.js:31:1106
    at async Span.traceAsyncFn (/.../node_modules/next/dist/telemetry/trace/trace.js:6:584)
    at async /.../node_modules/next/dist/build/index.js:43:49
    at async Span.traceAsyncFn (/.../node_modules/next/dist/telemetry/trace/trace.js:6:584)
    at async /.../node_modules/next/dist/build/index.js:25:1475
    at async Span.traceAsyncFn (/.../node_modules/next/dist/telemetry/trace/trace.js:6:584)

(2)

Error: ENOENT: no such file or directory, unlink '/.../public/generatedimages/_mdx_bundler_entry_point.js'

(3)

ReferenceError: Component is not defined
    at eval (eval at getMDXComponent (/blog/node_modules/mdx-bundler/dist/client.js:34:50), <anonymous>:3:2)
    at getMDXComponent (/blog/node_modules/mdx-bundler/dist/client.js:35:10)
    at Page (/blog/.next/server/pages/[code]/[slug].js:4044:55)
    at d (/blog/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:33:498)
    at bb (/blog/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:16)
    at a.b.render (/blog/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:42:43)
    at a.b.read (/blog/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:41:83)
    at exports.renderToString (/blog/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:52:138)
    at Object.renderPage (/blog/node_modules/next/dist/next-server/server/render.js:54:854)
    at Function.getInitialProps (/blog/.next/server/pages/_document.js:721:19)

I got them around 50% of the times I ran yarn build

Reproduction repository:

https://github.com/ninest/next-mdx-bundler-images-bundling-issue

The important file is mdx.js at https://github.com/ninest/next-mdx-bundler-images-bundling-issue/blob/main/lib/mdx.js#L42

(None of the blog mdx files have images, but the error still occurs)

A few things to note:

When I use remark-mdx-images, I get the error:

Error with remark-mdx-images
Error: Build failed with 1 error:
node_modules/esbuild/lib/main.js:863:33: error: [plugin: esbuild-xdm] Cannot read property 'start' of undefined
    at failureErrorWithLog (blog/node_modules/esbuild/lib/main.js:1443:15)
    at blog/node_modules/esbuild/lib/main.js:1125:28
    at runOnEndCallbacks (blog/node_modules/esbuild/lib/main.js:915:63)
    at buildResponseToResult (blog/node_modules/esbuild/lib/main.js:1123:7)
    at blog/node_modules/esbuild/lib/main.js:1230:14
    at blog/node_modules/esbuild/lib/main.js:606:9
    at handleIncomingPacket (blog/node_modules/esbuild/lib/main.js:703:9)
    at Socket.readFromStdout (blog/node_modules/esbuild/lib/main.js:573:7)
    at Socket.emit (events.js:315:20)
    at addChunk (internal/streams/readable.js:309:12)
    at readableAddChunk (internal/streams/readable.js:284:9)
    at Socket.Readable.push (internal/streams/readable.js:223:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:188:23) {
  errors: [
    {
      detail: TypeError: Cannot read property 'start' of undefined
          at onload (file://blog/node_modules/xdm/lib/integration/esbuild.js:87:32)
          at callback (blog/node_modules/esbuild/lib/main.js:863:34)
          at runMicrotasks (<anonymous>)
          at processTicksAndRejections (internal/process/task_queues.js:93:5)
          at async handleRequest (blog/node_modules/esbuild/lib/main.js:649:30),
      location: [Object],
      notes: [Array],
      pluginName: 'esbuild-xdm',
      text: "Cannot read property 'start' of undefined"
    }
  ],
  warnings: []
}
 > node_modules/esbuild/lib/main.js:863:33: error: [plugin: esbuild-xdm] Cannot read property 'start' of undefined
    863 β”‚               let result = await callback2({
        β•΅                                  ^
    at onload (file://blog/node_modules/xdm/lib/integration/esbuild.js:87:32)
    at callback (blog/node_modules/esbuild/lib/main.js:863:34)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async handleRequest (blog/node_modules/esbuild/lib/main.js:649:30)

   node_modules/esbuild/lib/main.js:733:22: note: This error came from the "onLoad" callback registered here
    733 β”‚         let promise = setup({
        β•΅                       ^
    at setup (file://blog/node_modules/xdm/lib/integration/esbuild.js:35:11)
    at handlePlugins (blog/node_modules/esbuild/lib/main.js:733:23)
    at Object.buildOrServe (blog/node_modules/esbuild/lib/main.js:1018:7)
    at blog/node_modules/esbuild/lib/main.js:1742:17
    at new Promise (<anonymous>)
    at Object.build (blog/node_modules/esbuild/lib/main.js:1741:14)
    at Object.build (blog/node_modules/esbuild/lib/main.js:1617:51)
    at bundleMDX (blog/node_modules/mdx-bundler/dist/index.js:192:33)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async getMDX (webpack-internal:///./lib/mdx.js:33:18)
    at async getStaticProps (webpack-internal:///./pages/blog/[slug].js:86:7)
    at async renderToHTML (blog/node_modules/next/dist/next-server/server/render.js:28:1780)
    at async blog/node_modules/next/dist/next-server/server/next-server.js:112:97
    at async __wrapper (blog/node_modules/next/dist/lib/coalesced-function.js:1:330)

Error: Build failed with 1 error:
node_modules/esbuild/lib/main.js:863:33: error: [plugin: esbuild-xdm] Cannot read property 'start' of undefined
    at failureErrorWithLog (blog/node_modules/esbuild/lib/main.js:1443:15)
    at blog/node_modules/esbuild/lib/main.js:1125:28
    at runOnEndCallbacks (blog/node_modules/esbuild/lib/main.js:915:63)
    at buildResponseToResult (blog/node_modules/esbuild/lib/main.js:1123:7)
    at blog/node_modules/esbuild/lib/main.js:1230:14
    at blog/node_modules/esbuild/lib/main.js:606:9
    at handleIncomingPacket (blog/node_modules/esbuild/lib/main.js:703:9)
    at Socket.readFromStdout (blog/node_modules/esbuild/lib/main.js:573:7)
    at Socket.emit (events.js:315:20)
    at addChunk (internal/streams/readable.js:309:12)
    at readableAddChunk (internal/streams/readable.js:284:9)
    at Socket.Readable.push (internal/streams/readable.js:223:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:188:23) {
  errors: [
    {
      detail: TypeError: Cannot read property 'start' of undefined
          at onload (file://blog/node_modules/xdm/lib/integration/esbuild.js:87:32)
          at callback (blog/node_modules/esbuild/lib/main.js:863:34)
          at runMicrotasks (<anonymous>)
          at processTicksAndRejections (internal/process/task_queues.js:93:5)
          at async handleRequest (blog/node_modules/esbuild/lib/main.js:649:30),
      location: [Object],
      notes: [Array],
      pluginName: 'esbuild-xdm',
      text: "Cannot read property 'start' of undefined"
    }
  ],
  warnings: []
}

But everything seems to work fine without the remark plugin. Am I doing something incorrectly? Is it something to do with the first argument of xdmOptions being vfile? When options is the second argument, I'm getting the error TypeError: Cannot read property 'rehypePlugins' of undefined. (I'm following this section)

Solution:

Use a custom path for each mdx file. I've already made the change at https://github.com/ninest/next-mdx-bundler-images-bundling-issue/blob/main/lib/mdx.js#L42

In esbuildOptions, change

options.outdir = path.join(process.cwd(), `/public/generated`);
...
options.publicPath = "/generated/";

to

options.outdir = path.join(process.cwd(), `/public/generated`, filepath);
...
options.publicPath = filepath;

I am not sure why this fixes it, but I believe the explanation is

This prevents images from later builds overwriting earlier ones. You have to remember that each call to bundleMDX is isolated so esbuild can't tell if the image it wants to overwrite is from an another build or from the last time your site was built.

https://www.arcath.net/2021/04/images-with-mdx-bundler

Should the documentation be changed to show that the images for each mdx file should be at a custom path? I spent quite a while figuring this out since the error messages (shown under the What happened heading) weren't very clear

How to handle an imported components imports

Hi guys, thank you for the great project! I'm currently trying to get mdx-bundler set up with Next.js and I'm running into some issues when it comes to importing components. The imported component in the MDX file is fine but if that import has it's own imports it throws an error. Is there a way to tacke this? I'll show you what I have below

  • mdx-bundler version: 4.0.1
  • node version: v14.16.0
  • npm version: 6.14.11

So I have some code to prepare and get the components like in @Arcath's post https://www.arcath.net/2021/03/mdx-bundler

import { bundleMDX } from 'mdx-bundler';
import path from 'path';
import { existsSync } from 'fs';
import { readdir, readFile } from 'fs/promises';

export const prepareMDX = async (source, files) => {
  if (process.platform === 'win32') {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      'node_modules',
      'esbuild',
      'esbuild.exe'
    );
  } else {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      'node_modules',
      'esbuild',
      'bin',
      'esbuild'
    );
  }

  const { code } = await bundleMDX(source, {
    files,
  });

  return code;
};

export const getComponents = async (directory) => {
  const components = {};

  if (!existsSync(directory)) return components;

  const files = await readdir(directory);

  console.log(files);

  for (const file of files) {
    if (file.substr(-3) === 'jsx') {
      const fileBuffer = await readFile(path.join(directory, file));
      components[`./components/${file}`] = fileBuffer.toString().trim();
    }
  }

  return components;
};

which allows me to import the component in postdir > componentsdir
but say I am trying to import this form component

import React, { useState } from 'react';
import Button from '../../../src/components/Button';
import styles from '../form.module.css';

const FormWithStyles = ({ title }) => {
  const [content, setContent] = useState({
    subject: `Feedback sent from: ${title}`,
    email: '',
    handle: '',
    message: '',
    honeypot: '',
    accessKey: 'your-access-key',
  });

  const handleChange = (e) =>
    setContent({ ...content, [e.target.name]: e.target.value });

  return (
    <div className={styles.feedback}>
      <p>
        Please let me know if you found anything I wrote confusing, incorrect or
        outdated. Write a few words below and I will make sure to amend this
        blog post with your suggestions.
      </p>
      <form className={styles.form}>
        <label className={styles.message} htmlFor="message">
          Message
          <textarea
            name="message"
            placeholder="What should I know?"
            onChange={handleChange}
            required
          />
        </label>
        <label className={styles.email} htmlFor="email">
          Your Email (optional)
          <input type="email" name="email" onChange={handleChange} />
        </label>
        <label className={styles.handle} htmlFor="handle">
          Twitter Handle (optional)
          <input type="text" name="handle" onChange={handleChange} />
        </label>
        <input type="hidden" name="honeypot" style={{ display: 'none' }} />
        <Button className={styles.submit} type="button" text="Send Feedback" />
      </form>
    </div>
  );
};

export default FormWithStyles;

An error is thrown because the Button component is trying to be imported from my src/components directory and the styles css module is being from that same directory

This is a screenshot of the error:
image

and the repo can be found here:

https://github.com/Adam-Collier/portfolio-site/tree/render_mdx

(I am currently migrating over from Gatsby so everything is a bit of a mess atm but it should be easy enough to navigate to a blog page that errors)

I appreciate any guidance you have on this and I hope you can help

Importing mdx files from node_modules doesn't work

Hi ! Thanks for this neat tool !

I'd like to use it to do some transclusion of Mdx files, some of which I would like to manage as individual npm packages.

My understanding of the comment below is that this should work out of the box:

mdx-bundler/src/index.js

Lines 150 to 157 in 2802158

// NOTE: the only time the xdm esbuild plugin will be used
// is if it's not processed by our inMemory plugin which will
// only happen for mdx files imported from node_modules.
// This is an edge case, but it's easy enough to support so we do.
// If someone wants to customize *this* particular xdm compilation,
// they'll need to use the esbuildOptions function to swap this
// for their own configured version of this plugin.
xdmESBuild(),

But I can't make it work ...

I've created a new test case in my fork to illustrate:

test('bundles mdx files from node_modules', async () => {
  const mdxSource = `
import MdxTestData from 'mdx-test-data'
The content below was imported from an MDX File in node_modules.
<MdxTestData />
  `.trim()

  const result = await bundleMDX(mdxSource, {
    files: {},
  })

  const Component = getMDXComponent(result.code)
  render(React.createElement(Component))
})

mdx-test-data is a package that simply exposes README.mdx in the main field of package.json

And running this test yields this result:

   FAIL  "bundles mdx files from node_modules"
    Build failed with 1 error:
__mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:2:24: error: No loader is configured for ".mdx" files: node_modules/mdx-test-data/README.mdx

    __mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:2:24: error: No loader is configured for ".mdx" files: node_modules/mdx-test-data/README.mdx
    at failureErrorWithLog (C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:1432:15)
    at C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:1114:28
    at runOnEndCallbacks (C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:912:47)
    at buildResponseToResult (C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:1112:7)
    at C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:1219:14
    at C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:603:9
    at handleIncomingPacket (C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:700:9)
    at Socket.readFromStdout (C:/Users/gaelh/dev/mdx-bundler/node_modules/esbuild/lib/main.js:570:7)
    at Socket.emit (events.js:315:20)

Am I missing something ? Is there another way of importing mdx files from node_modules ?

Unable to Add remark plugins

Hi there,

I Stored my data on Graphcms. Mdx bundling is working fine, but I'm unable to add a remark plugin to the bundler. I tried to add this plugin separately remark-admonitions and remark-mdx-code-meta, but I'm getting this same error. Can you tell me how to resolve this?

Screenshot 2021-07-15 at 6 03 07 PM

Origin of vfile message when using remark plugin

I'm using a custom remarkPlugin that adds some message to a vfile under certain condition (broken link) :

const mdxFile = (route: string) => {
  // returns true or false depending on whether route is valid or not
  ...
}

const attacher = () => {
  return transformer

  function transformer(tree, file) {
    visit(tree, { type: "link" }, (node: Node) => {
      const lnode = node as LinkNode
      const route: string = lnode.url
      if (!/^http/.test(route) && !mdxFile(route)) {
        file.message(`${route} is not a valid link`)
        const title = lnode.title || ""
        lnode.title = `[BROKEN LINK]${title}`
      }
    })
  }
}

I'm later on using it like so :

const rawContent = fs.readFileSync(filePath).toString()
  const { code, frontmatter } = await bundleMDX(rawContent, {
    xdmOptions(options) {
      options.remarkPlugins = [...(options.remarkPlugins ?? []), brokenLinks]
      return options
    },
  })

It's kinda working but the output is not as helpful as I'd like as I'm loosing track of the original filePath :

> __mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:0:0: warning: [plugin: esbuild-xdm] /fr/recherche/equipes/sen/collisions-d-ions-lourds-et-equation-d-etat-de-la-matiere-nucleaire-2 is not a valid link
    0 β”‚ ---
      β•΅ ^

Is there a way to get the part before the warning (__mdx_bundler_fake_dir__/_mdx_bundler_entry_point.mdx:0:0 be the filePath ?

Using the cwd bundleMDX param I was able to change the __mdx_bundler_fake_dir__ part, but have no clue on how to deal with the other part (_mdx_bundler_entry_point.mdx:0:0).

Sorry if that's obvious.

  • mdx-bundler version: 4.0.0
  • node version: 14.15.3
  • npm version: 6.14.9

Unable to import components β€” "Could not resolve "./Cool""

Thank you for this great library and thank you to all the contributors and example code that has got me to this point.

I am trying to get a proof of concept going, but can't get past component importing using the cwd option.

I have tried pulling some of the example repositories as well as starting on my own from scratch. I can also reproduce the error in codesandbox.

Is there anything I'm doing incorrectly? Is there some bug in a dependency minor version somewhere? I'm stuck!

  • mdx-bundler version: 5.1.0
  • next version: 11.0.1
  • react version: 17.0.2
  • node version: 14.17.3
  • npm version: 6.14.13

Relevant code or config

export const getSinglePost = async (slug) => {
  const source = getSourceOfFile(slug);
  const directory = path.join(POSTS_PATH, slug);

  console.log("cwd", directory);

  const { code, frontmatter } = await bundleMDX(source, {
    cwd: directory
  });

  return {
    frontmatter,
    code
  };
};

What you did: Imported a component in the same directory as the .mdx file.

What happened: Got an error,

Error: Build failed with 1 error:
_content/blog/cool/_mdx_bundler_entry_point.mdx:10:17: error: Could not resolve "./Cool"

Reproduction repository: https://codesandbox.io/s/peaceful-violet-5oe07?file=/lib/data/posts.js&initialpath=/blog/cool

Footnote support

Hi, thank you for your hard working, really love the flexibility of mdx-bundler.

After searching for a while and testing, it seems that mdx-bundler doesn't support markdown footnote syntax,

This paragraph has a footnote.[^1]
[^1]: Here's the footnote

should be rendered like

<p>
  This paragraph has a footnote.<sup><a href="#fn1" id="fnref1">1</a></sup>
</p>

Here's the footnote. <a href="#fnref1">↩</a>

Really look forward to this feature.

Thank you!!

`files` vs virutal `path`?

I'm considering using mdx-bundler, and I ran into a snag reading the docs. The files option seems really weird to me. I understand the library is designed so that the mdx content doesn't need to exist on the filesystem, but part of what's so nice about mdx is that it behaves as if it does.

Do I understand correctly that an import like import Button from './components/Button' won't work out of the box? And that a user has to specify up front every import any content might use, import them, and build the files objectβ€”relative to the particular mdx file being loaded?

Assuming so, is it possible to circumvent this complexity by specifying a virtual path for the mdx content? If the doc at least seems to live on the filesystem, esbuild should be able to handle the rest, including alias, tsconfig paths, etc. Naturally if the mdx lives on the filesystem, you could just pass its actual path.

Am I missing something about the problem? Is this feasible? Thanks!

I can not import Image component from next/image

  • mdx-bundler version: ^5.2.1
  • node version: v16.3.0
  • npm version: 7.20.1

Relevant code or config

import * as React from 'react'
import Image from 'next/image'

function EsstentialismGraph() {
  return (
    <div className="mt-4 flex justify-center">
      <Image src={'/essentialism_graph.svg'} 
        alt="TΖ° tưởng cα»‘t lΓ΅i vΓ  khΓ΄ng cα»‘t lΓ΅i"
        layout="intrinsic" 
        width={250} 
        height={300} />
    </div>
    )
}

export default EsstentialismGraph

What you did: Thank you for an amazing project! I try to import EsstentialismGraph component to the MDX file but it turns out I get an error about the process. I figured out the problem was I used Image component from next.

What happened:
Screen Shot 2021-08-08 at 17 27 29

Reproduction repository:

Problem description:

Suggested solution:

error when using styled-components in imported components

  • mdx-bundler version: 3.4.1
  • node version: 14.16.1
  • npm version: 6.14.12

What you did:

Import a component that uses styled-cmponents:

import { Counter } from './components/Counter';

# Counter Example

<Counter />
import * as React from 'react';
import styled from 'styled-components';

const StyledContainer = styled.div`
  display: flex;
  background-color: orange;
`;

export function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <StyledContainer>
      {count} <button onClick={() => setCount(count + 1)}>Increment</button>
    </StyledContainer>
  );
}

What happened:

Errors seemingly about styled-components, other third-party libraries all seem to work fine:

image

Reproduction repository:

https://github.com/souporserious/mdx-bundler-and-styled-components

Problem description:

This is in the context of a NextJS app that reaches into another directory outside of the app and pulls all of the MDX files from it. This works without styled-components, but when it's added, I get the error above.

This is my getStaticProps function responsible for fetching the MDX files:

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const filePaths = await glob([
    path.resolve(__dirname, '../../../../../vapor/src/**/*.mdx'),
  ]);
  const filePath = filePaths.find((filePath) => {
    const extension = path.extname(filePath);
    const name = path
      .basename(filePath, extension)
      .split('.stories')[0];
    return name.toLowerCase().replace(/\s/g, '-') === params.slug;
  });
  const contents = await fs.readFile(path.resolve(filePath), 'utf-8');
  const { code, frontmatter } = await bundleMDX(contents, {
    cwd: path.dirname(filePath),
  });
  return {
    props: {
      code,
      frontmatter,
    },
  };
};

Hash navigation not working in Next.js

  • mdx-bundler version: 3.4.1
  • node version: 15.5
  • npm version: 7.3.0

Relevant code or config

import { bundleMDX } from "mdx-bundler";
import {getMDXComponent} from 'mdx-bundler/client'
import {useMemo} from 'react'
import path from 'path'

if (process.platform === 'win32') {
  process.env.ESBUILD_BINARY_PATH = path.join(
    process.cwd(),
    'node_modules',
    'esbuild',
    'esbuild.exe',
  )
} else {
  process.env.ESBUILD_BINARY_PATH = path.join(
    process.cwd(),
    'node_modules',
    'esbuild',
    'bin',
    'esbuild',
  )
}

export const getStaticProps = async ({ params }) => {
  const mdxSource = `

${Array(1000).fill(0).map((_, index) => {
  return `<h2 id="${index}">${index}</h2>`
})}

`.trim();

  const result = await bundleMDX(mdxSource);

  const { code } = result;
  return {
    props: { code }, // will be passed to the page component as props
  };
};

export default function Post({ code }) {
  const Component = useMemo(
    () => getMDXComponent(code))
  return (
    <div>
      <Component />
    </div>
  );
}

Hash links don't work in Next.js on refresh. If you navigate to them while on the page it will work but if you go to them directly you can get problems.

The screen goes down to the hash link but then goes up again.

next hash

Reproduction repository:

https://github.com/MatthewCaseres/mdx-bundler-experiments

Notes: This is an issue in next-mdx-remote as well, here's an issue that is related but not identical from their repo. hashicorp/next-mdx-remote#65

Fix build

After trying a bunch of things, I think I'm going to go with uvu. I'm bummed that I can't get jest to work, but I think this'll be fine. Hopefully by the time I need to get jest working with ESM next time it'll be better.

Overriding entry point does not handle correctly frontmatter ?

  • mdx-bundler version: 5.2.1
  • node version: 14.17.1
  • npm version: 6.14.13

What you did:

I'm using the entryPoint option to have esbuild reads the mdx files for me and I've noticed lately that the front matter ends up empty. I'm (pretty) sure that at point it worked (see #51), so I might just do something stupid and wrong... But I've tried to update the relevant test in a test repo, where I've moved the CONTRIBUTING.md file to __tests__ and add a front matter to it.

What happened:

The should support over-riding the entry point test is then failing because the returned front matter is empty, while I would expect it not to be :

β€’ β€’   (14 / 15)

   FAIL  "should support over-riding the entry point"
    Expected values not to be deeply equal  (not.equal)

    at assert (file:///Users/laurent/github.com/kentcdodds/mdx-bundler/node_modules/uvu/assert/index.mjs:31:8)
    at Function.not.equal (file:///Users/laurent/github.com/kentcdodds/mdx-bundler/node_modules/uvu/assert/index.mjs:113:2)
    at Object.handler (file:///Users/laurent/github.com/kentcdodds/mdx-bundler/src/__tests__/index.js:440:14)
    at async Number.runner (file:///Users/laurent/github.com/kentcdodds/mdx-bundler/node_modules/uvu/dist/index.mjs:77:5)
    at async exec (file:///Users/laurent/github.com/kentcdodds/mdx-bundler/node_modules/uvu/dist/index.mjs:131:33)
    at async Module.run (file:///Users/laurent/github.com/kentcdodds/mdx-bundler/node_modules/uvu/run/index.mjs:13:2)
    at async /Users/laurent/github.com/kentcdodds/mdx-bundler/node_modules/uvu/bin.js:26:5



  Total:     15
  Passed:    14
  Skipped:   0
  Duration:  457.96ms

Reproduction repository: https://github.com/aphecetche/mdx-bundler/blob/entry-point-with-frontmatter-bug

to reproduce just run the tests

mdx-bundler needs to have its dependencies updated

  • mdx-bundler version:
  • node version:
  • npm version:

Relevant code or config

What you did:

What happened:

Reproduction repository:

Problem description:
I updated from a pre xdm version of mdx-bundler to the current bundler on Remix v0.15 with the old compiler, and it appears mdx-bundler will not compile any content. I tried substituting my post content with the demo in the readme content was unsuccessful.

This might be the relevant error:

<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><title>Oops!</title></head><body><div><h1>App Error</h1><pre>Named export &#x27;generate&#x27; not found. The requested module &#x27;astring&#x27; is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from &#x27;astring&#x27;;
const {baseGenerator, generate} = pkg;
</pre><p>Replace this UI with what you want users to see when your app throws uncaught errors. The file is at <code>app/App.tsx</code>.</p></div><link rel="modulepreload" href="/build/_shared/react-dom-af783853.js"/><link rel="modulepreload" href="/build/_shared/__remix-run/react-57beb414.js"/><link rel="modulepreload" href="/build/_shared/react-f725fe97.js"/><link rel="modulepreload" href="/build/_shared/object-assign-510802f4.js"/><link rel="modulepreload" href="/build/_shared/scheduler-a04990bf.js"/><link rel="modulepreload" href="/build/_shared/history-f36bd753.js"/><link rel="modulepreload" href="/build/_shared/__babel/runtime-ed0db730.js"/><link rel="modulepreload" href="/build/_shared/react-router-125d454f.js"/><link rel="modulepreload" href="/build/_shared/prop-types-0a27d4c1.js"/><link rel="modulepreload" href="/build/_shared/react-is-9bd4a37b.js"/><link rel="modulepreload" href="/build/root-6c3b2226.js"/><link rel="modulepreload" href="/build/_shared/react-router-dom-5cafc605.js"/><link rel="modulepreload" href="/build/_shared/react-moment-69e65e4f.js"/><link rel="modulepreload" href="/build/_shared/index-b6dcd8ad.js"/><link rel="modulepreload" href="/build/_shared/graphql-request-13a5e6fe.js"/><link rel="modulepreload" href="/build/_shared/moment-063fa120.js"/><link rel="modulepreload" href="/build/_shared/index-9e7d4c1b.js"/><link rel="modulepreload" href="/build/_shared/cross-fetch-c6e83489.js"/><link rel="modulepreload" href="/build/_shared/graphql-ee6475a1.js"/><link rel="modulepreload" href="/build/_shared/extract-files-eda57c75.js"/><link rel="modulepreload" href="/build/_shared/form-data-35f14aa2.js"/><link rel="modulepreload" href="/build/routes/blog-fdcdb93d.js"/><link rel="modulepreload" href="/build/_shared/mdx-bundler-3054ae34.js"/><link rel="modulepreload" href="/build/path"/><link rel="modulepreload" href="/build/_shared/remark-frontmatter-a96f9e83.js"/><link rel="modulepreload" href="/build/_shared/micromark-extension-frontmatter-ead1a148.js"/><link rel="modulepreload" href="/build/_shared/fault-87b06a15.js"/><link rel="modulepreload" href="/build/_shared/format-ef0c8e7d.js"/><link rel="modulepreload" href="/build/_shared/mdast-util-frontmatter-1eb3e236.js"/><link rel="modulepreload" href="/build/_shared/remark-mdx-frontmatter-079ecf60.js"/><link rel="modulepreload" href="/build/_shared/estree-util-is-identifier-name-dbef2cfd.js"/><link rel="modulepreload" href="/build/_shared/estree-util-value-to-estree-46f2306a.js"/><link rel="modulepreload" href="/build/_shared/is-plain-obj-3e03ed06.js"/><link rel="modulepreload" href="/build/_shared/js-yaml-4fed501f.js"/><link rel="modulepreload" href="/build/_shared/toml-6eb0502e.js"/><link rel="modulepreload" href="/build/_shared/unist-util-visit-3423fa69.js"/><link rel="modulepreload" href="/build/_shared/unist-util-visit-parents-33bca2c9.js"/><link rel="modulepreload" href="/build/_shared/unist-util-is-b8a0263c.js"/><link rel="modulepreload" href="/build/_shared/gray-matter-dc1196f5.js"/><link rel="modulepreload" href="/build/fs"/><link rel="modulepreload" href="/build/_shared/section-matter-0d15c47c.js"/><link rel="modulepreload" href="/build/_shared/kind-of-7b83e3ef.js"/><link rel="modulepreload" href="/build/_shared/extend-shallow-3f03756a.js"/><link rel="modulepreload" href="/build/_shared/is-extendable-e9a66a9d.js"/><link rel="modulepreload" href="/build/_shared/strip-bom-string-2d7a6d5a.js"/><link rel="modulepreload" href="/build/_shared/esbuild-9cebdd44.js"/><link rel="modulepreload" href="/build/child_process"/><link rel="modulepreload" href="/build/crypto"/><link rel="modulepreload" href="/build/os"/><link rel="modulepreload" href="/build/tty"/><link rel="modulepreload" href="/build/worker_threads"/><link rel="modulepreload" href="/build/_shared/__esbuild-plugins/node-resolve-fbf78b19.js"/><link rel="modulepreload" href="/build/_shared/builtin-modules-abc042ab.js"/><link rel="modulepreload" href="/build/module"/><link rel="modulepreload" href="/build/_shared/resolve-c0760374.js"/><link rel="modulepreload" href="/build/_shared/path-parse-a9f1b4e5.js"/><link rel="modulepreload" href="/build/_shared/is-core-module-ff091d4a.js"/><link rel="modulepreload" href="/build/_shared/has-59288659.js"/><link rel="modulepreload" href="/build/_shared/function-bind-6908fa26.js"/><link rel="modulepreload" href="/build/util"/><link rel="modulepreload" href="/build/_shared/__fal-works/esbuild-plugin-global-externals-73787170.js"/><link rel="modulepreload" href="/build/routes/blog/$post-80ac699f.js"/><script>window.__remixContext = {'matches':[{'params':{},'pathname':'/','route':{'path':'/','id':'root','module':'/build/root-6c3b2226.js','hasLoader':true}},{'params':{},'pathname':'/blog','route':{'path':'blog','caseSensitive':false,'id':'routes/blog','parentId':'root','module':'/build/routes/blog-fdcdb93d.js','imports':['/build/_shared/react-router-dom-5cafc605.js','/build/_shared/react-moment-69e65e4f.js','/build/_shared/index-b6dcd8ad.js','/build/_shared/graphql-request-13a5e6fe.js','/build/_shared/moment-063fa120.js','/build/_shared/index-9e7d4c1b.js','/build/_shared/cross-fetch-c6e83489.js','/build/_shared/graphql-ee6475a1.js','/build/_shared/extract-files-eda57c75.js','/build/_shared/form-data-35f14aa2.js'],'hasLoader':true}},{'params':{'post':'compile-mdx-on-remix'},'pathname':'/blog/compile-mdx-on-remix','route':{'path':':post','caseSensitive':false,'id':'routes/blog/$post','parentId':'routes/blog','module':'/build/routes/blog/$post-80ac699f.js','imports':['/build/_shared/mdx-bundler-3054ae34.js','/build/path','/build/_shared/remark-frontmatter-a96f9e83.js','/build/_shared/micromark-extension-frontmatter-ead1a148.js','/build/_shared/fault-87b06a15.js','/build/_shared/format-ef0c8e7d.js','/build/_shared/mdast-util-frontmatter-1eb3e236.js','/build/_shared/remark-mdx-frontmatter-079ecf60.js','/build/_shared/estree-util-is-identifier-name-dbef2cfd.js','/build/_shared/estree-util-value-to-estree-46f2306a.js','/build/_shared/is-plain-obj-3e03ed06.js','/build/_shared/js-yaml-4fed501f.js','/build/_shared/toml-6eb0502e.js','/build/_shared/unist-util-visit-3423fa69.js','/build/_shared/unist-util-visit-parents-33bca2c9.js','/build/_shared/unist-util-is-b8a0263c.js','/build/_shared/gray-matter-dc1196f5.js','/build/fs','/build/_shared/section-matter-0d15c47c.js','/build/_shared/kind-of-7b83e3ef.js','/build/_shared/extend-shallow-3f03756a.js','/build/_shared/is-extendable-e9a66a9d.js','/build/_shared/strip-bom-string-2d7a6d5a.js','/build/_shared/esbuild-9cebdd44.js','/build/child_process','/build/crypto','/build/os','/build/tty','/build/worker_threads','/build/_shared/__esbuild-plugins/node-resolve-fbf78b19.js','/build/_shared/builtin-modules-abc042ab.js','/build/module','/build/_shared/resolve-c0760374.js','/build/_shared/path-parse-a9f1b4e5.js','/build/_shared/is-core-module-ff091d4a.js','/build/_shared/has-59288659.js','/build/_shared/function-bind-6908fa26.js','/build/util','/build/_shared/__fal-works/esbuild-plugin-global-externals-73787170.js'],'hasLoader':true}}],'componentDidCatchEmulator':{'trackBoundaries':true,'renderBoundaryRouteId':null,'loaderBoundaryRouteId':'root','error':{'message':'Named export \'generate\' not found. The requested module \'astring\' is a CommonJS module, which may not support all module.exports as named exports.\nCommonJS modules can always be imported via the default export, for example using:\n\nimport pkg from \'astring\';\nconst {baseGenerator, generate} = pkg;\n','stack':'file:///celPool/celData/Works/projects/vidette-remix-express/node_modules/xdm/lib/plugin/recma-stringify.js:2\nimport {baseGenerator, generate} from \'astring\'\n                       ^^^^^^^^\nSyntaxError: Named export \'generate\' not found. The requested module \'astring\' is a CommonJS module, which may not support all module.exports as named exports.\nCommonJS modules can always be imported via the default export, for example using:\n\nimport pkg from \'astring\';\nconst {baseGenerator, generate} = pkg;\n\n    at ModuleJob._instantiate (file://node:internal/modules/esm/module_job:105:21)\n    at async ModuleJob.run (file://node:internal/modules/esm/module_job:151:5)\n    at async Loader.import (file://node:internal/modules/esm/loader:166:24)\n    at async importModuleDynamicallyWrapper (file://node:internal/vm/module:435:15)\n    at async Object.bundleMDX (file:///celPool/celData/Works/projects/vidette-remix-express/node_modules/mdx-bundler/dist/index.js:53:27)\n    at async Object.loader (file:///celPool/celData/Works/projects/vidette-remix-express/build/routes/blog/$post.js:28582:16)\n    at async Object.loadRouteData (file:///celPool/celData/Works/projects/vidette-remix-express/node_modules/@remix-run/node/data.js:16:16)\n    at async Promise.all (file://index 2)\n    at async handleDocumentRequest (file:///celPool/celData/Works/projects/vidette-remix-express/node_modules/@remix-run/node/server.js:114:28)\n    at async /celPool/celData/Works/projects/vidette-remix-express/node_modules/@remix-run/express/server.js:32:22'}},'routeData':{'root':{'date':'2021-04-14T18:07:32.974Z','env':'development'},'routes/blog':{'data':{'posts':[{'title':'Compile MDX on Remix','id':2,'author_id':'646b5408-538f-4de9-aa1b-d08b17edcec5','author_name':'benwis','feat_img':'https://celcyon-aws.imgix.net/mdx.png','feat_img_alt':'MDX logo','created_at':'2021-02-15T18:50:03.894408+00:00','excerpt':'MDX is perfect for writing content, but getting it to work on Remix can be difficult.','slug':'compile-mdx-on-remix'},{'title':'Add Fathom Analytics to Remix','id':3,'author_id':'646b5408-538f-4de9-aa1b-d08b17edcec5','author_name':'benwis','feat_img':'https://celcyon-aws.imgix.net/fathom.jpg','feat_img_alt':'Fathom Analytics logo','created_at':'2021-02-01T18:53:07.561874+00:00','excerpt':'Learn how to track user activity while respecting their privacy.','slug':'add-fathom-to-remix'},{'title':'Remix-Run and GraphQL','id':4,'author_id':'646b5408-538f-4de9-aa1b-d08b17edcec5','author_name':'benwis','feat_img':'https://celcyon-aws.imgix.net/graphql-blue.jpg','feat_img_alt':'GraphQL logo on purple background','created_at':'2021-01-23T18:53:30.048452+00:00','excerpt':'GraphQL and Remix integrate beautifully, whether in actions, loaders, or components!','slug':'Remix-Run-and-GraphQL'}]}},'routes/blog/$post':null}};</script><script type="module">import * as route0 from "/build/root-6c3b2226.js";
import * as route1 from "/build/routes/blog-fdcdb93d.js";
import * as route2 from "/build/routes/blog/$post-80ac699f.js";
    window.__remixRouteModules = {"root":route0,"routes/blog":route1,"routes/blog/$post":route2};</script><script src="/build/manifest-10fbfaad.js" type="module"></script><script src="/build/entry.client-3c5e76b8.js" type="module"></script></body></html>

Suggested solution:

MDX Preview Support in Netlify cms

Btw I have a question that is there any way to see MDX components compiled using mdx-bundler in a git based headless cms like Netlify cms or even importing them in the cms like we do in pure MDX files locally.
I am unable to find how to do this same with mdx-bundler.
It's very easy to do the same with original mdx-runtime.
It would be great if it's somehow possible.
Thanks for reading this issue.

Add debugging output to the inMemory plugin

While working on #41 , I added console.log in a couple of places to better understand what was going on.
You can have a look here: main...gaelhameon:wip

What would you think of including debug as a dependency, and turning these console.log into actual debugging output ?

I'm guessing that it could prove useful for cases where we load files that load other files that load other files etc.

@kentcdodds , if I submitted a PR for that, is this something you'd be interested in merging ?

Question about bundleMDX (files, globals, cwd)

Hi,

thanks for building this project!

  • mdx-bundler version: ^5.2.1
  • node version: 15.14.0
  • npm version: 7.15.0

I am currently working on building this blog: https://github.com/chrislicodes/personal-blog.

The function to bundle the MDX looks like this:

export const prepareMDX = async (directoryName: string) => {
  if (process.platform === "win32") {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      "node_modules",
      "esbuild",
      "esbuild.exe"
    );
  } else {
    process.env.ESBUILD_BINARY_PATH = path.join(
      process.cwd(),
      "node_modules",
      "esbuild",
      "bin",
      "esbuild"
    );
  }

  const directory = path.join(POSTS_PATH, "/", directoryName);

  const source = getSourceOfFile(directory + "/index.mdx");

  // const files = await getComponents(directoryName);

  const { code, frontmatter } = await bundleMDX(source, {
    // files,
    cwd: directory,
    xdmOptions: (options) => {
      options.remarkPlugins = [
        ...(options.remarkPlugins ?? []),
        remarkMdxImages,
      ];

      return options;
    },
    esbuildOptions: (options) => {
      options.outdir = path.join(process.cwd(), "/public/img");
      options.loader = {
        ...options.loader,
        ".png": "file",
        ".jpg": "file",
        ".gif": "file",
        ".mp3": "file",
      };
      options.publicPath = "/img/";
      options.write = true;

      return options;
    },
  });

  return { frontmatter, code };
};

In this post, I am importing a component using React Three Fiber:

https://github.com/chrislicodes/personal-blog/blob/main/src/_posts/three-js-post/index.mdx

Using it this way leads to multiple instances of THREE being imported. The multiple imports should be fixed using the "globals" parameter, or would that only apply I import the libraries in the .mdx file directly?

My questions:

  1. With passing the "cwd" parameter, the imports in a .mdx file can be resolved - in which scenarios would I have to pass/use the "files" parameter?
  2. Can I fix the multiple THREE.js instances with the "globals" parameter, or would that only be needed if I import a library directly in an MDX file?

Peer dependency issue: The requested module 'astring' is expected to be of type CommonJS ...

  • mdx-bundler version: 3.1.2
  • node version: 14.12.0
  • npm version: 7.5.4
  • NextJS version: 10.06

Hi πŸ‘‹ I just gave this a try on my NextJS based site and ended up with the following error when trying to run the build or a specific MDX based page in development mode.
Pretty sure it's not directly related to mdx-bundler itself so if that's the case feel free to close the issue. I assume this might be related to my version of node, so maybe it will be a blocker for other people and would require to add a set of compatible Node versions in the README of this project

❯ yarn dev
yarn run v1.17.3
$ next dev
Loaded env from /Users/maxime/Developer/blog.maximeheckel.com/.env
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
Cached 33 blog elements
Cached 12 snippet elements
info  - Using external babel configuration from /Users/maxime/Developer/blog.maximeheckel.com/.babelrc
event - compiled successfully
event - build page: /posts/[slug]
wait  - compiling...
event - build page: /404
event - compiled successfully
file:///Users/maxime/Developer/blog.maximeheckel.com/node_modules/xdm/lib/plugin/recma-stringify.js:2
import {baseGenerator, generate} from 'astring'
                       ^^^^^^^^
SyntaxError: The requested module 'astring' is expected to be of type CommonJS, which does not support named exports. CommonJS modules can be imported by importing the default export.
For example:
import pkg from 'astring';
const {baseGenerator, generate} = pkg;
    at ModuleJob._instantiate (internal/modules/esm/module_job.js:98:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:137:5)
    at async Loader.import (internal/modules/esm/loader.js:165:24)
    at async bundleMDX (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/mdx-bundler/dist/index.js:40:27)
    at async getFileBySlug (webpack-internal:///./lib/mdx.ts:44:21)
    at async getStaticProps (webpack-internal:///./pages/posts/[slug].tsx:89:18)
    at async renderToHTML (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/render.js:28:109)
    at async /Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/next-server.js:107:97
    at async __wrapper (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/lib/coalesced-function.js:1:330)
    at async DevServer.renderToHTMLWithComponents (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/next-server.js:132:387)
    at async DevServer.renderToHTML (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/next-server.js:133:923)
    at async DevServer.renderToHTML (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/server/next-dev-server.js:34:578)
    at async DevServer.render (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/next-server.js:72:236)
    at async Object.fn (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/next-server.js:56:580)
    at async Router.execute (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/router.js:23:67)
    at async DevServer.run (/Users/maxime/Developer/blog.maximeheckel.com/node_modules/next/dist/next-server/server/next-server.js:66:1042)

Relevant code or config. Here's an excerpt of my mdx bundling related code:

  const source = fs.readFileSync(
    path.join(root, typeToPath[type], `${slug}.mdx`),
    'utf8'
  );

  const resultMDX = await bundleMDX(source, {
    files: {},
    xdmOptions(_, options) {
      options.remarkPlugins = [
        ...(options.remarkPlugins ?? []),
        require('remark-slug'),
        require('remark-autolink-headings'),
        remarkSectionize,
        remarkFigure,
      ];

      options.rehypePlugins = [];

      return options;
    },
    esbuildOptions(options) {
      options.minify = false;
      options.target = [
        'es2020',
        'chrome58',
        'firefox57',
        'safari11',
        'edge16',
        'node12',
      ];

      return options;
    },
  });

What you did:

Tried to bundle some MDX file on my current NextJS setup and ended up with the error above.
This also happens when trying out the example

What happened:

The error above prevents me from rendering the page

Reproduction repository: https://github.com/MaximeHeckel/blog.maximeheckel.com/tree/next-mdx-bundler

Pushed a branch of my project with the current setup:

  1. Pull the repo
  2. Run yarn
  3. Run yarn dev
  4. Navigate to http://localhost:3000/posts/learning-in-public/ for example see the error in the CLI

Gray-matter options?

  • mdx-bundler version: 5.1.2
  • node version: 14.17.3
  • npm version: 6.14.13

Relevant code or config

---
title: Test Post
published: 2021-02-13
description: Shows how a post is published via MDX
---

# Test Post

This is a test post

What you did:
I'm using mdx-bundler to build a nextjs static site. My frontmatter contains some date-looking things. I pass the frontmatter returned by bundleMDX as props for my component.

What happened:
Since its a static site, Nextjs tries to serialize the props as part of the component and fails because it isn't JSON serializable:

Error: Error serializing `.frontmatter.published` returned from `getStaticProps` in "/posts/[slug]".
Reason: `object` ("[object Date]") cannot be serialized as JSON. Please only return JSON serializable data types.

Problem description:
There's no way to configure the default gray-matter engine behavior.

Suggested solution:
I guess the most obvious thing is to provide the ability to pass options to gray-matter.

Error: Could not resolve './dirname-messed-up.cjs' from ./dirname-messed-up.cjs?commonjs-external

  • mdx-bundler version: 3.2.0
  • node version: 14.16
  • npm version: 6.14.11

What you did: Upgraded mdx-bundler on a remix.run project

What happened:

Building Remix app for production...
Error: Could not resolve './dirname-messed-up.cjs' from ./dirname-messed-up.cjs?commonjs-external
    at error (<redacted-repo dir>/node_modules/rollup/dist/shared/rollup.js:5305:30)
    at ModuleLoader.handleResolveId (<redacted-repo dir>/node_modules/rollup/dist/shared/rollup.js:18531:24)
    at <redacted-repo dir>/node_modules/rollup/dist/shared/rollup.js:18487:22
    at async Promise.all (index 0)
    at async ModuleLoader.fetchStaticDependencies (<redacted-repo dir>/node_modules/rollup/dist/shared/rollup.js:18485:34)
    at async Promise.all (index 0)
    at async ModuleLoader.fetchModule (<redacted-repo dir>/node_modules/rollup/dist/shared/rollup.js:18461:9)
    at async Promise.all (index 10)
    at async ModuleLoader.fetchStaticDependencies (<redacted-repo dir>/node_modules/rollup/dist/shared/rollup.js:18485:34)
    at async Promise.all (index 0)

Problem description: Something might be wrong in the implementation of #21

Suggested solution: Unfortunately I don't have a lot of background to suggest a solution. Thought about reporting given that the error seems related to #21 which was just released.

Compiling MDX files from command line.

Hi there,

I have a very unique use case that I think this library could work for, but I'm unsure based on what I've read so far. Also, sorry if this is not the right place to post a question like this - feel free to redirect me.


I have a single MDX file that is similar to this:

import MultiChoice from './multi_choice.tsx'

Lorem Ipsum <a href={url}>{name}</a>. Lorem Ipsum <MultiChoice options={options}/> stuff.

I'd like to build a script that will take in a list of objects like this...

posts = [
  {
    name: "Some Name",
    url: "www.somename.com",
    options: [
      "Option 1",
      "Option 2",
      "Option 3"
    ]
  }
]

...and output several MDX files with the variables replaced. For example the above object would output a single MDX file like this:

import MultiChoice from './multi_choice.tsx'

Lorem Ipsum <a href="www.somename.com">Some Name</a>. Lorem Ipsum <MultiChoice options={["Option 1", "Option 2", "Option 3"]}/> stuff.

Is something like this possible with your library?

Does not work on Vercel

First issue! Do I get a prize?

  • mdx-bundler version: 1.0.1
  • node version: 12
  • yarn version: 1.22.1

Relevant code or config

import React from "react";
import type {
  MetaFunction,
  LinksFunction,
  LoaderFunction,
} from "@remix-run/react";
import { useRouteData } from "@remix-run/react";
import { json } from "@remix-run/data";
import Moment from "react-moment";
import { useLocation, Link } from "react-router-dom";
import { Posts } from "../../types";
import { GraphQLClient, gql } from "graphql-request";
import { bundleMDX } from "mdx-bundler";
import { MDXProvider } from "@mdx-js/react";
import { getMDXComponent } from "mdx-bundler/client";
import { getHighlighter, remarkPlugin } from "../../helpers/post";

export let loader: LoaderFunction = async ({ params }) => {
  const endpoint = "https://example.com/graphql";
  let headers = {
    headers: {
      authorization: `Bearer token_goes_here`,
      "X-Hasura-Admin-Secret": "secret",
    },
  };
  let query = gql`
    query GetPostBySlug($slug: String!) {
      posts(where: { slug: { _eq: $slug } }) {
        author_name
        content
        created_at
        feat_img
        feat_img_caption
        feat_img_alt
        id
        title
        slug
        excerpt
      }
    }
  `;
  let variables = { slug: params.post };
  //   let data = null;
  //   let error = null;
  const client = new GraphQLClient(endpoint, headers);

  //Fetch post content from the API
  let data = await client.request(query, variables);
  const highlighter = await getHighlighter();
  // Process Returned post data into MDX
  const post = await bundleMDX(data.posts[0].content, {
    files: {},
    remarkPlugins: [[remarkPlugin, { highlighter }]],
  });
  // const post = await compilePost(data.posts[0].slug, data.posts[0]);

  const oneDay = 86400;
  const secondsSincePublished =
    (new Date().getTime() - post.frontmatter.published) / 1000;
  const barelyPublished = secondsSincePublished < oneDay;

  // If this was barely published then only cache it for one minute, giving you
  // a chance to make edits and have them show up within a minute for visitors.
  // But after the first day, then cache for a week, then if you make edits
  // get them there.
  const maxAge = barelyPublished ? 60 : oneDay * 7;

  // If the max-age has expired, we'll still send the current cached version of
  // the post to visitors until the CDN has cached the new one. If it's been
  // expired for more than one month though (meaning nobody has visited this
  // page for a month) we'll make them wait to see the newest version.
  const swr = oneDay * 30;
  return json(
    { data, post },
    {
      headers: {
        "cache-control": `public, max-age=${maxAge}, stale-while-revalidate=${swr}`,
      },
    }
  );
};
// The title and meta tags for the document's <head>
export let meta: MetaFunction = ({ data }: { data: Posts }) => {
  return {
    title: data.data.posts[0].title,
    description: data.data.posts[0].excerpt,
  };
};

function Post({ code, frontmatter }) {
  const Component = getMDXComponent(code);
  return (
    <MDXProvider>
      <Component />
    </MDXProvider>
  );
}

export default function BlogPost() {
  //Hide post menu on small screens when a post is selected
  let location = useLocation();

  let routeData = useRouteData<Posts>();
  // const { source, frontmatter } = routeData.post;
  const { code, frontmatter } = routeData.post;

  const { data } = routeData;
  // const content = hydrate(source);

  return (
    <React.Fragment>
      <div className="relative bg-white overflow-hidden">
        <div className="hidden lg:block lg:absolute lg:inset-y-0 lg:h-full lg:w-full">
          <div
            className="relative h-full text-lg max-w-prose mx-auto"
            aria-hidden="true"
          >
          </div>
        </div>
        <div className="relative px-4 sm:px-6 lg:px-8">
          <div className="text-lg max-w-prose mx-auto">
            <Link className="py-4 block" to="/blog">
              Return to Archive
            </Link>

            <header className="prose lg:prose-xl py-8">
              <h1>{frontmatter.title}</h1>
            </header>

            <figure>
              <img
                className="w-full rounded-lg"
                src={data.posts[0].feat_img ?? ""}
                alt={data.posts[0].feat_img_alt ?? ""}
                width="1310"
                height="873"
              />
              <figcaption>{data.posts[0].feat_img_caption}</figcaption>
            </figure>

            <div className="meta py-2 text-center">
              <Link to="/team/{data.posts[0].author_name}" className="inline">
                <h4 className="inline">By: {data.posts[0].author_name}</h4>
              </Link>
              <h4 className="inline pl-8">Posted: </h4>
              <Moment className="inline " format="HH:MM DD MMM, YY">
                {data.posts[0].created_at}
              </Moment>
            </div>
            <main className="prose lg:prose-xl pb-16">
              <Post code={code} frontmatter={frontmatter} />
            </main>
          </div>
        </div>
      </div>
    </React.Fragment>
  );
}

What you did: I setup a pretty straightforward blog post compiler on my vercel app and deployed it.

What happened: I can't get it to work, it always complains about babel missing

Cannot find module '@babel/preset-react' Require stack: - /var/task/node_modules/@babel/core/lib/config/files/plugins.js - /var/task/node_modules/@babel/core/lib/config/files/index.js - /var/task/node_modules/@babel/core/lib/index.js - /var/task/node_modules/@rollup/plugin-babel/dist/index.js - /var/task/node_modules/@remix-run/core/compiler.js - /var/task/node_modules/@remix-run/core/index.js - /var/task/node_modules/@remix-run/vercel/index.js - /var/task/index.js - /var/task/___vc_launcher.js - /var/runtime/UserFunction.js - /var/runtime/index.js

Reproduction repository:
https://remix-vercel.benwis.vercel.app/blog/compile-mdx-on-remix
You can see the error here.

Problem description:
It seems to work fine locally and then not when deployed on vercel. Runs great on my Express app.
Suggested solution:
I don't know, installing the package did not make the error go away. It is definitely in my package.json

Thanks for the quick fix!

I was literally just racking my brain about this ESM bug with remark-frontmatter and was writing an issue for it when you fixed it in f6bebc9. Working great now! πŸŽ‰

Thanks @kentcdodds

Module not found: Can't resolve 'builtin-modules'

  • mdx-bundler version: 3.1.2
  • node version: 14.15.4
  • yarn version: 1.22.10
  • next version: 10.1.3 & 10.0.9

Relevant code or config

// config for 10.1.3
module.exports = {
  future: {
    webpack5: true,
  },
  reactStrictMode: true,
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    if (!isServer) {
      // https://github.com/vercel/next.js/issues/7755
      config.resolve = {
        ...config.resolve,
        fallback: {
          ...config.resolve.fallback,
          // Don't load this on the client.
          fs: false,
        },
      }
    }

    return config
  },
}

// config for 10.0.9
module.exports = {
  reactStrictMode: true,
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    if (!isServer) {
      // https://github.com/vercel/next.js/issues/7755
      config.node = {
        // Don't load this on the client.
        fs: 'empty',
      }
    }

    return config
  },
}

What you did:

Following along with the documentation and after installing mdx-bundler and trying to run the dev sever for next I get the below output. The other screen shot is the output from executing yarn build I get the same using either version and configuration of next.

What happened:

Screen Shot 2021-04-05 at 10 33 43

Screen Shot 2021-04-05 at 11 51 25

Reproduction repository:

rockchalkwushock/codybrunner.dev#84

Problem description:

It seems as though if running either webpack@4 or opting in for webpack@5 in next there is an issue with the resolving of esm imports.

Global component that doesn't need being imported

I'm struggling with the case of having a series of predefined components ready to use in my mdx:

Some sample text <Highlight>and some highlighted text</Highlight>

In this case, I want Highlight to be some kind of "global" component and avoid the need of

import { Highlight } from '...' 

I'm trying with substitutions and globals, but without success. It loos like globals is for global modules, not components.

Exclude React code

Looks like a great library, I just started exploring it.

I noticed that even when compiling/building a plain markdown file (just "# heading"), the compiled output contains quite some react code. Is it possible to provide the react part of the runtime externally as well?

I've tried the following to no avail:

const result = await bundleMDX(contents, {
    cwd: path.dirname(file),
    globals: {
      react: "myreact",
      "react-dom": "myreactdom",
      "react-jsx": "myreactjsx",
      "react-jsx-runtime": "myreactjsx",
    },
  });

Image in frontmatter

Hi,

Thanks for this wonderful package ☺️

I was wondering if it's possible to use mdx-bundler to parse relative image file references in frontmatter. It's working perfectly for images in the mdx content itself but the images referenced in the frontmatter aren't written to the output directory.

I would like to use the frontmatter to display a preview of the blog post in card format, including the banner.

Any guidance would be much appreciated!

  • mdx-bundler version: 5.1.2
  • node version: 14.15.4
  • npm version: 7.20.0

Relevant code or config

---
title: My first blog post
date: "2021-07-22"
banner: "./images/banner.jpg"
---
const ROOT_PATH = process.cwd();
const PUBLIC_PATH = path.join(ROOT_PATH, "public");

const { code, frontmatter } = await bundleMDX(source, {
    cwd: directory,
    files,
    xdmOptions: (options) => {
      options.remarkPlugins = [
        ...(options.remarkPlugins ?? []),
        remarkMdxImages,
        gfm,
      ];

      return options;
    },
    esbuildOptions: (options) => {
      options.entryPoints = [getFilePath(directory)];
      options.outdir = path.join(PUBLIC_PATH, "images", type, slug);
      options.loader = {
        ...options.loader,
        ".webp": "file",
        ".png": "file",
        ".jpg": "file",
        ".jpeg": "file",
        ".gif": "file",
      };
      options.publicPath = `/images/${type}/${slug}`;
      options.write = true;

      return options;
    },
  });

What you did:

Ran bundleMDX in NextJS to bundle my MDX files.

What happened:

No error messages; the image file referenced in the frontmatter isn't written to the outdir like the images in the content of the mdx file.

Run script before MDX Bundler runs

  • mdx-bundler version: ^5.1.0
  • node version: v16.3.0
  • npm version: 7.15.1

Relevant code or config

I have a gif-to-png.js script that converts GIFs to PNG in development in the src/_posts directory. As to why I use it, I have written about it here.

TL;DR I'm creating a GIF Player that shows static PNG images when someone pauses a GIF & play button to play the GIF because I hate to see GIF loop while reading text.

gif-to-png.js

const fs = require('fs')
const path = require('path')
const sharp = require('sharp')
const fg = require('fast-glob')

const ROOT_PATH = process.cwd()
const POSTS_PATH = path.join(ROOT_PATH, 'src/_posts')

function* walkSync(dir) {
	const files = fs.readdirSync(dir, { withFileTypes: true })
	for (let i = 0; i < files.length; i++) {
		if (files[i].isDirectory()) {
			yield* walkSync(path.join(dir, files[i].name))
		} else {
			yield path.join(dir, files[i].name)
		}
	}
}

const gifToPng = async () => {
	try {
		for (let [i, file] of [...walkSync(POSTS_PATH)].entries()) {
			const extname = path.extname(file)
			if (extname === '.gif') {
				const dirname = path.dirname(file)
				const png = path.resolve(dirname, path.basename(file).replace('.gif', '.png'))
				await sharp(file).png().toFile(png)
			}
		}
	} catch (e) {
		console.error('Error thrown:', e)
	}
}

gifToPng()

What you did:

This makes a copy of PNG in src/_posts folder appropriately by converting the GIF file.

What happened:

It doesn't copy the PNG file to the appropriate location as mdx-bundler probably runs earlier than this.

Reproduction repository:

https://github.com/deadcoder0904/mdx-bundler-gif-png-error

Problem description:

I just want those PNG files in public/ directory. Is there any way to run the scripts before mdx-bundler?

Suggested solution:

No idea. I did try running the script in public directory instead of src/_posts so it renames there but it didn't work.

Code block meta string

Is there anyway to access code block meta strings? Example:

in an mdx page -

```js myVar=myValue

const foo = "bar"
...

```

Reference

Expose matter function (from gray-matter) through mdx-bundler

  • mdx-bundler version: 3.2.1
  • node version: 14.13.1
  • npm version: 6.14.8

Relevant code or config

import { matter } from 'mdx-bundler'
import readingTime from 'reading-time'

......
export const getStaticProps = async () => {
  const source = getPost('./data/blog/thePost');

  const { content, data } = matter(source);

  return {
    props: {
      ...data,
      readingTime: readingTime(content)   
    }
  }
}

What you did:
I'm creating a blog with next.js using mdx-bundler to get mdx code and frontmatter.

What happened:
I get always code and frontmatter from bundleMDX function

Problem description:
Sometimes only frontmatter is needed, for example, when rendering a list of post but not the post itself( where you don't really need the code). Another case might be when trying to display the reading time, where you need plain content to be more accurate instead of the string with generated code.

Suggested solution:
Exposing matter function (from gray-matter library) through mdx-bundler, so there is no need to add it to project dependencies.

Another option is having the possibility of only getting plain content and frontmatter from bundleMDX, instead of always getting the code.

Thanks to everyone, this is an awesome library!

Markdown tables are not working

  • mdx-bundler version: 5.2.1
  • node version: 16.6.1
  • npm version: 7.20.3
  • esbuild version: 0.12.19

Relevant code or config

  const { code, frontmatter } = await bundleMDX(source, {
    cwd: path.join(process.cwd(), 'src', 'content', 'docs'),
    xdmOptions(options) {
      // eslint-disable-next-line
      options.rehypePlugins = [rehypeSlug]
      return options
    },
    esbuildOptions(options) {
      // eslint-disable-next-line
      options.platform === 'node'
      return options
    },
  })

What you did:

Added a markdown table

| Syntax    | Description |
| --------- | ----------- |
| Header    | Title       |
| Paragraph | Text        |

What happened:

There is no table element output at all, this is the code output of bundleMDX()

`,(0,n.jsx)(e.p,{children:`| Syntax    | Description |
| --------- | ----------- |
| Header    | Title       |
| Paragraph | Text        |`}),`

And the visual output
Screenshot 2021-08-10 at 09 57 58

Reproduction repository:

https://replit.com/@mtt87/QuickwittedSpottedProcedures#index.js

Problem description:
Markdown tables are not rendered correctly

Suggested solution:
Not sure at the moment where the issue could be, I guess either mdx-bundler or esbuild or xdm are the packages involved here πŸ˜„ I'm gonna try to compile with xdm to see what's the output without mdx-bundler involved

Unrecognised components

(Not sure if this should live here or on xdm?)

  • mdx-bundler version: ^4.1.1
  • node version: v16.1.0
  • npm version: 7.11.2

On NextJS, pages/index.js

import React from 'react';
import path from 'path';
import { bundleMDX } from 'mdx-bundler';
import { getMDXComponent } from 'mdx-bundler/client';

process.env.ESBUILD_BINARY_PATH = path.join(
  process.cwd(),
  'node_modules',
  'esbuild',
  'bin',
  'esbuild'
);

export default function Home({ code }) {
  const Component = React.useMemo(() => getMDXComponent(code), [code]);

  return (
    <div>
      <Component />
    </div>
  );
}

export async function getStaticProps() {
  const result = await bundleMDX(`
    ---
    title: Foo!
    ---

    Foo Component
    
    <Foo />
  `);

  return {
    props: {
      code: result.code
    }
  };
}

What you did:

My remote MDX content contained a component which I did not expect (old component no longer in use, but still in the old MDX file). When using mdx-bundler with NextJS, this throws errors and the page is unable to compile/render.

I was previously using next-mdx-remote, using the v2 branch (so mdx v2 under the hood), and components which were not recognised were simply removed.

What happened:

With a single unrecognised component, I'm getting a lot of these logs:

Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
    at y (eval at getMDXComponent (/.../node_modules/mdx-bundler/dist/client.js:37:50), <anonymous>:3:1022)

Reproduction repository:

https://stackblitz.com/edit/nextjs-cmdd2t?file=pages/index.js

Problem description:

Add an unrecognised component, and an error will be thrown:

---
title: Foo
---

Foo Component...!

<Foo />

Suggested solution:

Either remove the Foo component from the tree or allow a way to hook onto unrecognised components and display something?

[SOLVED] Can't import components when using slug + index.mdx

Hi guys, this project is amazing, but I have a problem importing components and images when I put my posts in this structure. "_content/blog/${slug}/index.mdx"

but when I revert the changes before that structure so it looks like this "_content/blog/${slug}" it works fine, I don't understand. πŸ€·β€β™‚οΈ

  • mdx-bundler version: 4.1.0
  • node version: 14.17.0
  • npm version: 6.14.13

here is the code for the "_content/blog/${slug}/index.md" structure.
/utils/mdx.js:

import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { bundleMDX } from "mdx-bundler";
import { remarkMdxImages } from "remark-mdx-images";

const POSTS_PATH = path.join(process.cwd(), "_content/blog");

export const getSourceOfFile = (filePath) => {
  return fs.readFileSync(path.join(POSTS_PATH, filePath, "index.mdx"));
};

export const getAllPosts = () => {
  return fs
    .readdirSync(POSTS_PATH)
    .map((filePath) => {
      console.log(filePath);
      const source = getSourceOfFile(filePath);
      const slug = filePath.replace(/\.mdx?$/, "");
      const { data } = matter(source);

      return {
        frontmatter: data,
        slug: slug,
      };
    })
    .filter((posts) => !!posts.frontmatter.isPublished)
    .sort(
      (a, b) =>
        new Date(b.frontmatter.publishedOn) -
        new Date(a.frontmatter.publishedOn)
    );
};

export const getSinglePost = async (slug) => {
  const source = getSourceOfFile(slug);
  const imagesUrl = `/img/blog/${slug}/`;

  const { code, frontmatter } = await bundleMDX(source, {
    cwd: POSTS_PATH,
    xdmOptions: (options) => {
      options.remarkPlugins = [
        ...(options.remarkPlugins ?? []),
        remarkMdxImages,
      ];
      return options;
    },
    esbuildOptions: (options) => {
      options.outdir = path.join(process.cwd(), "public", imagesUrl);
      options.loader = {
        ...options.loader,
        ".webp": "file",
        ".jpeg": "file",
        ".jpg": "file",
        ".svg": "file",
        ".png": "file",
      };

      options.publicPath = imagesUrl;
      options.write = true;

      return options;
    },
  });

  return {
    frontmatter: frontmatter,
    code: code,
  };
};

Error:
esbuild error

Reproduction repository
Note: The main branch contains the described problem. I also created a branch with a working example for demonstrating what I mean when naming the file as a slug.

I hope this makes enough sense. Thanks in advance for any help given.

I solved the problem, it was because cwd was declared as POSTS_PATH and It needs to have the same path as the post slug. πŸ€¦β€β™‚οΈ

Solution:
  export const getSinglePost = async (slug) => {
    const source = getSourceOfFile(slug);
    const imagesUrl = `/img/blog/${slug}`;
    const directory = path.join(POSTS_PATH, slug);
  
    const { code, frontmatter } = await bundleMDX(source, {
      cwd: directory,
      xdmOptions: (options) => {
        options.remarkPlugins = [
          ...(options.remarkPlugins ?? []),
          remarkMdxImages,
        ];
        return options;
      },
      esbuildOptions: (options) => {
        options.outdir = path.join(process.cwd(), "public", imagesUrl);
        options.loader = {
          ...options.loader,
          ".webp": "file",
          ".jpeg": "file",
          ".jpg": "file",
          ".svg": "file",
          ".png": "file",
          ".gif": "file",
        };
  
        options.publicPath = imagesUrl;
        options.write = true;
  
        return options;
      },
    });
  
    return {
      frontmatter,
      code,
    };
  };

Next.Js build fails with `Error: Not supported` on `bundleMDX()` in getStaticProps

  • mdx-bundler version: ^3.1.2
  • node version: v12.16.3
  • npm version: 7.6.3

Relevant code or config

const mdxSource = `
---
title: Example Post
published: 2021-02-13
description: This is some description
---

# Wahoo

import Demo from './demo'

Here's a **neat** demo:
`.trim();

export const getStaticProps = async () => {
  const result = await bundleMDX(mdxSource);

  console.log(result);

  return {
    props: {},
  };
};

What you did:

Hey everyone, I tried replacing my old md setup with mdx bundler. For this I generated a new and clean nextjs (10.1.3) project today.
Using bundleMDX() I get the unhelpful error Error: Not supported when calling it in my getStaticProps function during generation. It's mostly a fresh project except tailwind installed and using out of the box typescript.
It has nothing to do with any other step like reading files as it also happens using the demo inline MDX string from the repo README.

Anything I am overlooking here? I saw the thread about esbuild fails but that seems to be a different issue.

Error message:

Error: Not supported
    at bundleMDX (project_folder_path/node_modules/mdx-bundler/dist/index.js:40:27)
    at getStaticProps (webpack-internal:///./pages/index.tsx:34:78)
    at renderToHTML (project_folder_path/node_modules/next/dist/next-server/server/render.js:28:1743)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async project_folder_path/node_modules/next/dist/next-server/server/next-server.js:112:97
    at async __wrapper (project_folder_path/node_modules/next/dist/lib/coalesced-function.js:1:330)
    at async DevServer.renderToHTMLWithComponents (project_folder_path/node_modules/next/dist/next-server/server/next-server.js:137:387)
    at async DevServer.renderToHTML (project_folder_path/node_modules/next/dist/next-server/server/next-server.js:138:522)
    at async DevServer.renderToHTML (project_folder_path/node_modules/next/dist/server/next-dev-server.js:35:578)
    at async DevServer.render (project_folder_path/node_modules/next/dist/next-server/server/next-server.js:75:236)
    at async Object.fn (project_folder_path/node_modules/next/dist/next-server/server/next-server.js:59:580)
    at async Router.execute (project_folder_path/node_modules/next/dist/next-server/server/router.js:25:67)
    at async DevServer.run (project_folder_path/node_modules/next/dist/next-server/server/next-server.js:69:1042)
    at async DevServer.handleRequest (project_folder_path/node_modules/next/dist/next-server/server/next-server.js:34:504)

Maybe its something easy I am just not seeing right now. I am thankful for any pointers :)

It says it needs Python to install

Hello, it says it needs Python....but no mention of python needed in the docs...
My environment is node:alpine docker container

/app/front # npm i --save mdx-bundler
npm WARN deprecated @types/[email protected]: This is a stub types definition for source-map (https://github.com/mozilla/source-map). source-map provides its own type definitions, so you don't need @types/source-map installed!
npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: request-promise-native has been deprecated because it extends the now deprecated request package, see request/request#3142
npm WARN deprecated [email protected]: request has been deprecated, see request/request#3142
npm ERR! code 1
npm ERR! path /app/front/node_modules/deasync
npm ERR! command failed
npm ERR! command sh -c node ./build.js
npm ERR! gyp info it worked if it ends with ok
npm ERR! gyp info using [email protected]
npm ERR! gyp info using [email protected] | linux | x64
npm ERR! gyp ERR! find Python
npm ERR! gyp ERR! find Python Python is not set from command line or npm configuration
npm ERR! gyp ERR! find Python Python is not set from environment variable PYTHON
npm ERR! gyp ERR! find Python checking if "python3" can be used
npm ERR! gyp ERR! find Python - "python3" is not in PATH or produced an error
npm ERR! gyp ERR! find Python checking if "python" can be used
npm ERR! gyp ERR! find Python - "python" is not in PATH or produced an error
npm ERR! gyp ERR! find Python checking if "python2" can be used
npm ERR! gyp ERR! find Python - "python2" is not in PATH or produced an error
npm ERR! gyp ERR! find Python
npm ERR! gyp ERR! find Python **********************************************************
npm ERR! gyp ERR! find Python You need to install the latest version of Python.

How can I get fast reloading working?

Hi, I’ve got a question.

I got it working and the bundler works great in my next.js app.
But since the content isn’t bundled by webpack anymore, it means I have to refresh the browser while I’m writing to see the changes.

Is there a way to get real-time updates while writing the mdx?
Do you live with cmd + R, or is there a better way?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.