kentcdodds/mdx-bundler

`components` do not override the built-in react components

jlaramie opened this issue · 4 comments

    "@mdx-js/esbuild": "^2.1.1",
    "@mdx-js/react": "^2.1.1",
    "esbuild": "^0.14.39",
    "mdx-bundler": "^9.0.0",
    "next": "12.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"

Relevant code or config

// index.tsx
// Created and slightly modified from from https://nextjs.org/docs/api-reference/create-next-app

import { bundleMDX } from "mdx-bundler";
import type { NextPage } from "next";
import Head from "next/head";
import { useMemo } from "react";
import { getMDXComponent } from "mdx-bundler/client/index";
import styles from "../styles/Home.module.css";
import path from "path";
import { MDXProvider } from "@mdx-js/react";

const components = {
  "jsx.img": () => {
    return <b>This Does Not Work</b>;
  },
  img: () => {
    return <b>This Does Not Work</b>;
  },
  Img: () => {
    return <b>This Does Work</b>;
  },
};

const Home: NextPage<{ code: any }> = ({ code }) => {
  const MdxComponent = useMemo(() => getMDXComponent(code, {}), [code]);

  console.log(code);

  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <MDXProvider components={components}>
          <MdxComponent components={components} />
        </MDXProvider>
      </main>
    </div>
  );
};

export default Home;

export async function getStaticProps() {
  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 result = await bundleMDX({
    source: `
      <div>
          does not override: <img src="test.png" />
          <br />
          does override: <Img src="test.png" />
      </div>
    `,
    mdxOptions: (options) => {
      return {
        ...options,
        providerImportSource: "@mdx-js/react",
      };
    },
  });

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

What you did:

I tried to provide my own custom components to override the default ones. Specifically these are MDX / React components like p and img and not md paragraphs and ![]() img links as these types of conversions work.

What happened:

It does not seem possible to directly override a JSX native component like img. If I name it Img and pass an Img component that works.

var Component=(()=>{var p=Object.create;var i=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var u=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty;var x=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),g=(e,n)=>{for(var t in n)i(e,t,{get:n[t],enumerable:!0})},c=(e,n,t,s)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of f(n))!a.call(e,r)&&r!==t&&i(e,r,{get:()=>n[r],enumerable:!(s=m(n,r))||s.enumerable});return e};var l=(e,n,t)=>(t=e!=null?p(u(e)):{},c(n||!e||!e.__esModule?i(t,"default",{value:e,enumerable:!0}):t,e)),b=e=>c(i({},"__esModule",{value:!0}),e);var _=x((w,d)=>{d.exports=_jsx_runtime});var v={};g(v,{default:()=>h});var o=l(_());function j(e={}){let{wrapper:n}=e.components||{};return n?(0,o.jsx)(n,Object.assign({},e,{children:(0,o.jsx)(t,{})})):t();function t(){let s=Object.assign({p:"p"},e.components),{Img:r}=s;return r||y("Img",!0,"5:26-5:48"),(0,o.jsxs)("div",{children:[(0,o.jsxs)(s.p,{children:["does not override: ",(0,o.jsx)("img",{src:"test.png"})]}),(0,o.jsx)("br",{}),(0,o.jsxs)(s.p,{children:["does override: ",(0,o.jsx)(r,{src:"test.png"})]})]})}}var h=j;function y(e,n,t){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it."+(t?"\nIt\u2019s referenced in your code at `"+t+"` in `D:\\Repos\\cloud-graphics\\__mdx_bundler_fake_dir__\\_mdx_bundler_entry_point-7484e3ca-f393-4f22-b3f2-48e3853a6125.mdx`":""))}return b(v);})();

Reproduction repository:

https://github.com/jlaramie/mdx-bundler-components-override

Problem description:

Native components like img should be replaceable with custom components

Suggested solution:

Not sure and still haven't figured out why this works in next-remote-mdx and not mdx-bundler

Related to:

I think you might need to set providerImportSource:

const { code } = await bundleMDX(source, {
  ...,
  xdmOptions: (input, options) => ({
    ....options,
    providerImportSource: '@mdx-js/react'
  })
})

I think you might need to set providerImportSource:

const { code } = await bundleMDX(source, {
  ...,
  xdmOptions: (input, options) => ({
    ....options,
    providerImportSource: '@mdx-js/react'
  })
})

@souporserious I had tried this and I just updated my setup to try again and it still doesn't work. I'll update the description of this issue to include the newer versions and sample. I also tried manually wrapping the code with MDXProvider.

Also I updated the repo linked in the description with my latest test. If someone could take a look it has few direct dependencies and shows my issue.

I found the root of the issue mdx-js/mdx#2050

After that pull request got closed I opened another one and had a bigger discussion on it all mdx-js/mdx#2052.

If you want to override native HTML elements you'll need to provide a custom plugin that ensures they get wrapped in the components check.

{
  rehypePlugins: [
    () => (tree) => {
      visit(tree, "mdxJsxTextElement", (node) => {
        if (node.data) {
          delete node.data._mdxExplicitJsx;
        }
      });
    },
  ],
}