Search code examples
reactjswebpackcreate-react-appbabel-loader

import "!babel-loader!mdx-loader!foo.mdx" not working with [email protected]


Trying to upgrade an existing React project from react-scripts@4 to @5.0.0 fails for importing transpiled MDX sources.

/* eslint import/no-webpack-loader-syntax: off */
import AboutMDX from "!babel-loader!mdx-loader!./About.mdx"

AboutMDX does not receive an MDXComponent but instead now as of react-scripts 5 end up with a string which is the path name of the transpiled javascript source code file. How can I fix this change in behavior to correctly import the MDXComponent?


Solution

  • This has been an od(d)ysee because the whole MDX2+CRA5+remark/rehype ecosystem is prone to breakage in my experience. While MDX documents to use CRACO7 with CRA5, the MDX project when kindly asked points fingers to CRACO and wasn't helpful to me in getting me over ES modules and CSJ hurdles in order to finally get the pieces to work. While the following now works for me (at the moment) I don't know how robust this set up actually is.

    • upgrade to CRA 5
    • install CRACO 5
    • make sure to call the craco command instead of react in your package.json scripts.
    • make sure to clean up your (stale) dependencies.
    • add these dependencies and dev dependencies:
        "@mdx-js/loader": "^2.2.1",
        "@mdx-js/mdx": "^2.2.1",
        "@mdx-js/react": "^2.2.1",
        "@types/mdx": "^2.0.3",
    
        "@craco/craco": "^7.0.0",
        "@craco/types": "^7.0.0",
    
    • if in the past you had a declare module '*.mdx {...}' in a src/index.d.ts then remove this now completely, as it would conflict with what comes with the MDXv2 loader.
    • remove !babel-loader!mdx-loader! from all your *.mdx imports. Do not use !@mdx-js/loader! etc. either, as the webpack configuration below will take care of the preprocessing.
    • create a craco.config.js as follows; this is a more elaborate configuration that shows how to actually pull in ES modules with CRACO 5 still not supporting ESM in their configuration, but requiring to go through the dynamic import with delayed configuration setup hurdles:
    const { addAfterLoader, loaderByName } = require('@craco/craco')
    
    module.exports = async (env) => {
        const remarkGfm = (await import('remark-gfm')).default
        const remarkImages = (await import('remark-images')).default
        const remarkTextr = (await import('remark-textr')).default
        const rehypeSlug = (await import('rehype-slug')).default
        const textrTypoApos = (await import('typographic-apostrophes')).default
        const textrTypoQuotes = (await import('typographic-quotes')).default
        const textrTypoPossPluralsApos = (await import('typographic-apostrophes-for-possessive-plurals')).default
        const textrTypoEllipses = (await import('typographic-ellipses')).default
        const textrTypoEmDashes = (await import('typographic-em-dashes')).default
        const textrTypoEnDashes = (await import('typographic-en-dashes')).default
    
        return {
            webpack: {
                configure: (webpackConfig) => {
                    addAfterLoader(webpackConfig, loaderByName('babel-loader'), {
                        test: /\.(md|mdx)$/,
                        loader: require.resolve('@mdx-js/loader'),
                        /** @type {import('@mdx-js/loader').Options} */
                        options: {
                            remarkPlugins: [
                                remarkGfm,
                                remarkImages,
                                [remarkTextr, {
                                    plugins: [
                                        textrTypoApos,
                                        textrTypoQuotes,
                                        textrTypoPossPluralsApos,
                                        textrTypoEllipses,
                                        // textrTypoEmDashes,
                                        textrTypoEnDashes,
                                    ],
                                    options: {
                                        locale: 'en-us'
                                    }
                                }],
                            ],
                            rehypePlugins: [
                                rehypeSlug,
                            ],
                        }
                    })
                    return webpackConfig
                }
            }
        }
    }