bitttttten/jest-transformer-mdx

synchronous transformer must export a "process" function.

Opened this issue ยท 17 comments

Currently there is an issue on some CI servers where this error is reported, it's because I am using processAsync, which only works with ESM support enabled.

I am using processAsync to support mdx's async behaviour, since now I allow the user to configure mdx, and therefore they might be sending in plugins to mdx that are asynchronous.

The solution is to either get ESM support working, and the user would also need to run jest with ESM support. Or.. I don't know yet.

how can we fix this?

what about this

process(src, filepath, config) {
		const options = resolveOptions(config)
		const mdxOptions = resolveMdxOptions(options?.mdxOptions)

		const withFrontMatter = parseFrontMatter(src, options?.frontMatterName)

		const jsx = mdx.sync(withFrontMatter, { ...mdxOptions, filepath })

		const toTransform = `import {mdx} from '@mdx-js/react';${jsx}`

		// supports babel-jest@27 (which exports with .default) and older versions
		// see: https://github.com/bitttttten/jest-transformer-mdx/issues/22
		const babelProcess = babelJest.default?.process ?? babelJest.process
		return babelProcess(toTransform, filepath, config).code
	},

is this fixed on master?

latest release does not include it

Hey @sibelius for some reason I was not getting notifications! Sorry for the slow poke. Did you manage to work something out?

The tricky thing is that to use an async process means I cannot support jest version <=26.

So my plan is to republish the package and to recommend people install a certain version depending on which jest version they have.

Hey y'all! First off, thanks for all of your work on this, seems like a beast of an issue ๐Ÿ’ช

I am currently working on updating our app (a storybook react component library) to React 17/Jest 27/etc. and am running into the same issues.

Do you happen to have any ideas on when there would be a fix out for this issue? Also, In the meantime is the only current known "workaround" implementing ESM support on our end - and if so (sorry for asking, but) would you be able to provide a basic example of what that might look like?

Let me know if you would like any other info from my scenario to help troubleshoot - happy to help however I can.

Cheers!

So as it stands now: if you're not configuring mdx and are using a jest version below 27, then you could just install jest-transformer-mdx@2.x.x. However you are using Jest 27. To run jest with ESM you can look into this doc: https://jestjs.io/docs/ecmascript-modules

Then it would just be a matter of turning this file from commonjs into esm: https://github.com/bitttttten/jest-transformer-mdx/blob/master/index.js which you can do by following https://nodejs.org/api/esm.html#esm_differences_between_es_modules_and_commonjs

My guess it would be something like:

  • resolveMdxOptions is now async
  • process is now async
  • imports match esm style
import path from "path"
import matter from "gray-matter"
import stringifyObject from "stringify-object"
import mdx from "@mdx-js/mdx"
import babelJest from "babel-jest"

// we support either a path to a file, or the options itself
// see: https://github.com/bitttttten/jest-transformer-mdx/pull/20
async function resolveMdxOptions(src) {
	if (typeof src === "string") {
		return await import(path.resolve(process.cwd(), src))
	}
	return src
}

function parseFrontMatter(src, frontMatterName = "frontMatter") {
	const { content, data } = matter(src)

	return `export const ${frontMatterName} = ${stringifyObject(data)};
${content}`
}

// this helper resolves both jest 27, and versions of jest below 27
// as the way that transformer config is picked up has changed
// see: https://github.com/bitttttten/jest-transformer-mdx/issues/22
function resolveOptions(config) {
	if (config?.transformerConfig) {
		return config.transformerConfig
	}

	if (config?.transform && Array.isArray(config.transform)) {
		for (let i = 0; i < config.transform.length; i++) {
			if (new RegExp(config.transform[i][0]).test(filename)) {
				return config.transform[i][2]
			}
		}
	}

	return {}
}

export async function process(src, filepath, config) {
	const options = resolveOptions(config)
	const mdxOptions = resolveMdxOptions(options?.mdxOptions)

	const withFrontMatter = parseFrontMatter(src, options?.frontMatterName)

	const jsx = mdx.sync(withFrontMatter, { ...mdxOptions, filepath })

	const toTransform = `import {mdx} from '@mdx-js/react';${jsx}`

	// supports babel-jest@27 (which exports with .default) and older versions
	// see: https://github.com/bitttttten/jest-transformer-mdx/issues/22
	const babelProcess = babelJest.default?.process ?? babelJest.process
	return babelProcess(toTransform, filepath, config).code
}

I have not tested this yet, just psuedo code :)

tubbo commented

I was able to get past this error by enabling ESM, but I just ran into another error that prevented me from continuing:

    SyntaxError: /path/to/file.stories.mdx: Support for the experimental syntax 'jsx' isn't currently enabled (7:33):

       5 | import { mocks } from './event-results.mocks'
       6 | export const frontMatterName = {};
    >  7 | export const Template = args => <ApolloProvider mocks={mocks} mdxType="ApolloProvider">
         |                                 ^
       8 |     <EventResults {...args} mdxType="EventResults" />

I'm trying to load a .stories.mdx file into my tests to set it up via composeStories().

You might be running node natively with experimental esm support, and therefore not running it through babel who will transform your jsx into something that node can run. Just my first thought ๐Ÿค”

tubbo commented

@bitttttten Yes, I got that error after enabling experimental ESM support. I tried re-enabling it all but now I can't get back to it, just getting that must export a process function. error again. Installing jest-transformer-mdx@2 actually got past that error for me, even though yarn why tells me I'm running Jest 28, so I'm not sure what's going on here..

Oh interesting! I haven't looked or thought about this issue for a while, modules are tricky for me. When it finally clicks I am sure I will be able to work something out, but happy you have something working in the meantime. Perhaps I can point people to your comment when they are stuck, so I really appreciate you sharing your solution!

tubbo commented

@bitttttten This might be due to me wanting to use MDX files in my tests so I could load in Storybook stories as test setup, but I wasn't getting anything from the exports of my .mdx files, so what I described above might not actually solve anyone's problem, it just gets past one error so you can see another.

Hi @bitttttten. I know this is an old issue but I was wondering if you had a recommended way to transform .mdx for Jest versions 28+ for someone who is only interested in supporting the latest jest version? Thank you!

@barclayd could you try jest-transformer-mdx@3.0.0-beta.0? This version works with jest27 although not sure about jest28 support ๐Ÿ˜…

If you're familiar with patch-package, you could experiment with something like this: #22 (comment)

Hello! Resurrecting the undead here... I'm now on jest 29 and there's a new issue. Using 3.0.0-beta.0 throws an Invalid return value error.

Error:  Invalid return value:
  `process()` or/and `processAsync()` method of code transformer found at
  "/home/so/code/p5-cljs-web-editor/node_modules/jest-transformer-mdx/index.js"
  should return an object or a Promise resolving to an object. The object
  must have `code` property with a string of processed code.
  This error may be caused by a breaking change in Jest 28:
  https://jestjs.io/docs/28.x/upgrading-to-jest28#transformer
  Code Transformation Documentation:
  https://jestjs.io/docs/code-transformation

Using 3.0.0-beta.0, I changed

return process(toTransform, filename, config).code

to

return process(toTransform, filename, config)

And then I installed @mds-js/mdx as a dev dependency. This fixed the issue for me. Using jest@29.5.0.

๐Ÿ‘‹ Does anybody have a PR to support Jest 29?