kentcdodds / mdx-bundler

🦤 Give me MDX/TSX strings and I'll give you back a component you can render. Supports imports!
MIT License
1.73k stars 74 forks source link

ReferenceError: process is not defined when importing .tsx files inside MDX file #165

Open melosomelo opened 2 years ago

melosomelo commented 2 years ago

Relevant code or config

// root/pages/blog/[slug].tsx
export default function BlogArticle({ code, frontmatter }: ArticleProps) {
  const { asPath } = useRouter();
  const Component = useMemo(() => getMDXComponent(code), [code]); // error happens here
  return (...);
}
// ...
const getStaticProps: GetStaticProps<ArticleProps, PathDict> = async (ctx) => {
  const { params } = ctx;
  const postsDirectory = path.join(__dirname, "..", "..", "..", "..", "posts");
  const mdxFile = (
    await fs.promises.readFile(path.join(postsDirectory, `${params!.slug}.mdx`))
  ).toString();
  const { code, frontmatter } = await bundleMDX<ArticleFrontmatter>({
    source: mdxFile,
    cwd: postsDirectory,
    xdmOptions(options, frontmatter) {
      options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkMath];
      options.rehypePlugins = [
        ...(options.rehypePlugins ?? []),
        rehypeKatex,
        rehypeHighlight,
      ];
      return options;
    },
  });
  validateFrontmatter(params!.slug, frontmatter);
  if (!frontmatter.published) {
    return {
      notFound: true,
    };
  }
  const now = new Date().toISOString();
  const finalFrontmatter: ArticleMetadata = {
    ...frontmatter,
    publishedOn: frontmatter.publishedOn ?? now,
    updatedOn: frontmatter.updatedOn ?? null,
  };
  return {
    props: {
      code,
      frontmatter: finalFrontmatter,
    },
  };
};

What you did: I tried to import TSX components into an MDX file.

What happened: I got a client-side error that said ReferenceError: process is not defined.

image

Problem description: I'm currently building my blog with MDX and NextJS. I'm on the final stages and I saw that I hadn't configured the part with regards to the path resolution of component imports inside MDX files, so I tried following the docs and used the cwd option in the bundleMDX function, which is set to the directory where the MDX files live. All the imports within the MDX files are also relative to the posts directory. If I remove the imports, the error goes away. I don't really know why I'm getting this error and I don't know what I could do to fix it.

melosomelo commented 2 years ago

The MDX files live in the root/posts folder. My components folder is root/src/components. Here are some imports that I tried testing in a MDX file:

import Layout from "../src/components/Layout/index.tsx";
import otherThing from "../src/components/common/CategoryPill/index.tsx";
melosomelo commented 2 years ago

I managed to make it work, but I'm not quite sure how solid of a solution this is. Would very much like some feedback.

I took a look at the code for getMDXComponent and saw that it only defines a function based on the code given by esbuild. Considering that process is a variable defined in the NodeJS environment, it makes sense for the browser to throw an error if the provided code has process in it. So the problem must have been in the esbuild code given to the browser.

Then I did a bit more digging and found that other people have had this problem with esbuild. The solution I found was to scower the code produced by esbuild for appearances of any process.env and found out they are all NextJS related.

I then used the property esbuildOptions.define and replaced each variable that I found:

esbuildOptions(options) {
      options.define = {
        "process.env.__NEXT_TRAILING_SLASH": '""',
        "process.env.__NEXT_CROSS_ORIGIN": '""',
        "process.env.__NEXT_I18N_SUPPORT": '""',
        "process.env.__NEXT_ROUTER_BASEPATH": '""',
        "process.env.__NEXT_SCROLL_RESTORATION": '""',
        "process.env.__NEXT_HAS_REWRITES": '""',
      };
      return options;
    },

Is this the default way of doing this? Seems kinda weird to me.

darichey commented 2 years ago

I was able to resolve this by passing the component I was importing to the components prop on the component returned by getMDXComponent. Then, no import is necessary.

FradSer commented 2 years ago

@darichey that's what I do, but it's not an elegant way to do it.

melosomelo commented 2 years ago

@darichey doesn't that lead to import overhead? Or are you using some sort of lazy loading?

themikejr commented 2 years ago

I was able to resolve this by passing the component I was importing to the components prop on the component returned by getMDXComponent. Then, no import is necessary.

@darichey can you share share an example of the solution you mention here?

darichey commented 2 years ago

@themikejr Sure, here is the relevant change I made to my site to get things working: https://github.com/darichey/darichey.com/commit/a841bc6dfa17c05490f22c28f023077f565d38b4#diff-94b274c9bf8b1ec216419d94e28d844d4d0f35e18b743214a4769448534c0918

@melosomelo I am not doing anything special, so it's possible. I'm afraid I'm not sure exactly what that entails or how to check. The relevant code is public now (sorry for the delay), so you can check if you'd like. Hopefully we can find a better solution or get a fix!

voluntadpear commented 2 years ago

I went with the approach suggested by @melosomelo but by using the actual values defined by Next:

options.define = {
      "process.env.__NEXT_TRAILING_SLASH": JSON.stringify(process.env.__NEXT_TRAILING_SLASH),
      "process.env.__NEXT_IMAGE_OPTS": JSON.stringify(process.env.__NEXT_IMAGE_OPTS),
      "process.env.__NEXT_REACT_ROOT": JSON.stringify(process.env.__NEXT_REACT_ROOT),
      "process.env.__NEXT_OPTIMIZE_FONTS": JSON.stringify(process.env.__NEXT_OPTIMIZE_FONTS)
    };

Seems to work so far.

FradSer commented 2 years ago

@voluntadpear thanks, it works for me. In my case, I add below in bundleMDX():

esbuildOptions: (options) => {
  options.define = {
    'process.env.__NEXT_TRAILING_SLASH': JSON.stringify(
      process.env.__NEXT_TRAILING_SLASH
    ),
    'process.env.__NEXT_IMAGE_OPTS': JSON.stringify(
      process.env.__NEXT_IMAGE_OPTS
    ),
    'process.env.__NEXT_REACT_ROOT': JSON.stringify(
      process.env.__NEXT_REACT_ROOT
    ),
    'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
      process.env.__NEXT_OPTIMIZE_FONTS
    ),
  };
  return options;
},

Just like https://github.com/FradSer/frad-me/commit/8c8cb421eddadd259769312edc46dd0e8c501fb3 .

colinsteidtmann commented 1 year ago

This worked for me

            options.define = {
                "process.env": JSON.stringify(process.env)
            };
kjxbyz commented 6 months ago

same issue in nextjs.