question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

MDX is not compatible with @babel/plugin-transform-react-inline-elements

See original GitHub issue

Subject of the issue

When combined with @babel/plugin-transform-react-inline-elements, MDX often not picking up the custom components provided through MDXProvider and renders the default components instead. This is probably due to heavy optimizations this babel plugin performs on react components. Removing the plugin from babel configuration fixes the issue.

This plugin is only typically used in production and so the problem will not manifest during development or in tests. Therefore this breakage may slip unnoticed to production. In fact, I just shipped a broken version yesterday.

Wouldn’t it be nice for MDX to support configurations with this babel plugin?

Your environment

  • OS: Ubuntu 20.04

  • Packages:

    {
      "dependencies": {
        "@mdx-js/react": "1.6.21",
        "@next/mdx": "10.0.1",
        "next": "10.0.1",
        "react": "16.14.0",
        "react-dom": "16.14.0",
        "styled-components": "5.2.1"
      },
      "devDependencies": {
        "@babel/plugin-transform-react-inline-elements": "7.12.1",
        "@mdx-js/loader": "1.6.21",
        "prettier": "2.1.2"
      }
    }
    
  • Env:

    node: v14.15.0
    yarn: 1.22.10
    chromium: 83.0.4103.116
    

Steps to reproduce

I created a small (but not minimal) example demonstrating the issue here: https://github.com/ivan-aksamentov/repro-mdx-inline-elements

It’s based on Next.js and also contains styled-components (but none of this matter, see below).

The points of interest are:

In order to reproduce the bug, run the production version of this app with:


yarn install
next build && next start

and navigate to localhost:3000. You will see something like this: bad

Note how all of the components are styled with plain useragent stylesheet (meaning most of the custom components ARE NOT picked up). Inspect the HTML code for the “link” in dev tools and note that it DOES have target="_blank" rel="noopener noreferrer" attributes (meaning that the custom LinkExternal component IS picked up). Also note how >>>> LinkExternal <<<<< is printed in build console (in terminal), but not >>>> H1 <<<<< (these are console.log() statements in the corresponding components).

Go to babel.config.js and comment-out the line containing '@babel/plugin-transform-react-inline-elements'. Cleanup, rebuild and restart the app.

rm -rf .build out .cache .next
next build && next start

It should look like this now: good

Note that all cusom components now work correctly: styled components’ classnames are present, h1’s text is replaced, backrgounds and margins are correctly styled, both console statements are printed in the build log, as expected. The LinkExternal component also still works as before. Reversing the change in babel.config.js reverts the fix.

Styled components don’t play a role here, I just wanted to show that they are working as well, because they are important for my usecase. With these components, babel-plugin-styled-components, and associated npm packages removed the issue still persists.

Expected behaviour

It is expected that the custom components are picked up, whether the @babel/plugin-transform-react-inline-elements is used or not

Actual behaviour

Custom components are not picked up when @babel/plugin-transform-react-inline-elements is used

Discussion

Let’s generate a readable bunde code and see what is changing when adding/removing the plugin. I added two Next.js plugins withoutMinification() and withFriendlyChunkNames(), which remove code minification and hashes from filenames in production build.

I did the following experiment:

  • prepared output directories:

    rm -rf compare/{good,bad}/**
    mkdir -p compare/{good,bad}
    
  • with babel plugin DISABLED, produced static build for “GOOD” version (static build has exactly the same issues as normal build, but it’s easier to make sense of files it produces):

    rm -rf .build out .cache .next
    next build && next export
    cp -r .next/static/chunks/* compare/good/
    
  • with babel plugin ENABLED, produced static build for “BAD” version:

    rm -rf .build out .cache .next
    next build && next export
    cp -r .next/static/chunks/* compare/bad/
    
  • compared the resulting directories with webstorm:

    detach webstorm diff compare/{good,bad}
    

    the only difference seems to be in pages/index.js

  • generated a diff file for pages/index.js

    diff -u compare/{good,bad}/pages/index.js > compare/index.js.diff
    
  • one could also use diff-so-fancy to see the pretty diff in terminal:

     diff -u compare/{good,bad}/pages/index.js | diff-so-fancy
    

You can find the results in compare/, directory, inluding the compare/index.js.diff

I was not able to make sense of the diff yet.

Unrelated to diff, but interestingly, the reason LinkExternal works seems to be the fact that it uses (renders) children props. Adding children to H1 component also fixes the h1 rendering. So props seems to be influencing the code optimizations in question. However, side effects, like console.log() don’t seem to be preserved (notice how they are not printed during build) Sadly, this workaround will not work for styled components.

Related issues in the community

styled-components had a seemingly similar issue, and were able to solve it, while emotion given up on this: link1, link2.

There is also a similarly useful plugin, @babel/plugin-transform-react-constant-elements. So far it does not seem to cause any breakage. However, it woth keeping an eye on it as well.

Possible workarounds

  • remove @babel/plugin-transform-react-inline-elements, paying extra runtime performance and bundle size cost.

  • use children prop in custom components - does not work for many components, like styled-components, or components that are not meant to have children.

Update:

Perhaps webstorm’s diff is a bit more readable (left side is “good”, right side is “bad”):

Looks like something is going on with this added function, as well as with null and void 0 arguments on call site. Still cannot tell what’s wrong. For example, console.log() calls are present in H1 on both sides, but are not working on the right side, while working on the left side.

Update 2:

It may make more sense to examine the output of the normal next build in .next/static/chunks (rather than of static export).

github_iconTop GitHub Comments

wooormcommented, Nov 12, 2020
1reaction
wooormcommented, Nov 12, 2020

Thanks! I have no clue why this is happening, maybe ask the babel folks?

Btw, most of the diff you’re seeing is because the heading component uses an arrow, where a is just a function, index.jsx like so:

import * as React from "react";
import * as ReactDOM from "react-dom";

import { MDXProvider } from "@mdx-js/react";

import Content from "./content.md";

function h1() {
  return <span>!!!heading 1</span>;
}

function a() {
  return <span>!!!link</span>;
}

export default function Index() {
  return (
    <MDXProvider components={{ h1, a }}>
      <Content />
    </MDXProvider>
  );
}

ReactDOM.render(<Index />, document.getElementById("app"));

Produces:

var layoutProps = {};
var MDXLayout = "wrapper";
function MDXContent(_ref) {
  var components = _ref.components,
      props = content_objectWithoutProperties(_ref, ["components"]);

  return createElement(MDXLayout, content_extends({}, layoutProps, props, {
    components: components,
    mdxType: "MDXLayout"
  }), /*#__PURE__*/_jsx("h1", {}, void 0, "Heading 1"), /*#__PURE__*/_jsx("p", {}, void 0, createElement("a", content_extends({
    parentName: "p"
  }, {
    "href": "http://example.com"
  }), "link")));
}

...

function h1() {
  return /*#__PURE__*/src_jsx("span", {}, void 0, "!!!heading 1");
}

function a() {
  return /*#__PURE__*/src_jsx("span", {}, void 0, "!!!link");
}

function Index() {
  return /*#__PURE__*/src_jsx(esm_MDXProvider, {
    components: {
      h1: h1,
      a: a
    }
  }, void 0, /*#__PURE__*/src_jsx(MDXContent, {}));
}
react_dom["render"]( /*#__PURE__*/src_jsx(Index, {}), document.getElementById("app"));

…and I don’t know what babel-plugin-transform-react-inline-elements is basing the difference in the a and h1 on? 🤷‍♂️

Read more comments on GitHub >

github_iconTop Results From Across the Web

gatsby-plugin-mdx
This config option is used for compatibility with a set of plugins many people use with remark that require the Gatsby environment to...
Read more >
Unable to Install and configure the MDX transformer plugin ...
After trying one of the suggestion given by the fellow stack-overflow site member, a new error is thrown! So ignoring the dependency tree...
Read more >
I have the iPhone 8 and an Acura MDX I ha…
I have not received helpful info from either support line (Acura or ... will need to release new firmware to be compatible with...
Read more >
2023 Acura MDX Images | Interior and Exclusive Design
We noticed you're using Internet Explorer 11, which is no longer supported on this site. Please switch to a different browser to continue....
Read more >
2023 MDX - Media and Connectivity - Acura Info Center
Wireless Apple CarPlay ® Compatibility. Offers seamless integration of key smartphone features and functions, including smartphone-powered GPS navigation and ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found