Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"TypeError: A dynamic import callback was invoked without --experimental-vm-modules" in Jest #2254

Closed
4 tasks done
johnal95 opened this issue Aug 30, 2024 · 8 comments · Fixed by #2258
Closed
4 tasks done
Labels
bug Something isn't working needs:triage Issues that have not been investigated yet. scope:node Related to MSW running in Node

Comments

@johnal95
Copy link

johnal95 commented Aug 30, 2024

Prerequisites

Environment check

  • I'm using the latest msw version
  • I'm using Node.js version 18 or higher

Node.js version

20.15.1

Reproduction repository

https://github.com/johnal95/repro-msw-graphql-import-error

Reproduction steps

  1. Install dependencies (this repo uses pnpm)
pnpm install
  1. Run tests
pnpm test
  1. The following errors are printed to the stdout:
[MSW] Failed to parse a GraphQL query: cannot import the "graphql" module.
Please make sure you install it if you wish to intercept GraphQL requests.
See the original import error below.

TypeError: A dynamic import callback was invoked without --experimental-vm-modules
        at importModuleDynamicallyCallback (node:internal/modules/esm/utils:226:11)
        ... rest of the stack trace

Current behavior

The graphql module fails to be imported, preventing the mocking of GraphQL requests.

Likely introduced with 1799e06

Expected behavior

Same bahavior as in the previous version - 2.4.0. graphql module is imported successfully and GraphQL requests are mocked as expected.

@johnal95 johnal95 added bug Something isn't working needs:triage Issues that have not been investigated yet. scope:node Related to MSW running in Node labels Aug 30, 2024
@kettanaito
Copy link
Member

Hi, @johnal95. Thanks for reporting this!

First, it seems to be an issue exclusively with Jest and its still lacking proper support for ESM. Here's why I suspect that.

  1. This error isn't the case in Vitest.
  2. The same msw imports work without problems in Node.js v20:
import { graphql, HttpResponse } from "msw";
import { setupServer } from "msw/node";

const server = setupServer(
    graphql.query("GetUser", () => {
        return HttpResponse.json({ data: { user: {} } });
    })
);

server.listen();

await fetch("https://api.example.com", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        query: `
      query GetUser {
        user {
          id
          name
        }
      }
    `,
    }),
});

The mention of VM heavily implies Node.js is throwing on how Jest is using the node:vm module to run tests. Jest doesn't support running ESM properly in this instance, and dynamic import seems to be a part of ESM.

How to fix this

Follow the suggestion from the error:

// package.json
{
     "scripts": {
-        "test": "jest"
+        "test": "NODE_OPTIONS=--experimental-vm-modules jest"
     },
}

MSW fix

We are not addressing Jest-related issues anymore. As much as I admire Jest, it holds the entire ecosystem back. If you can, use Vitest or other modern testing frameworks.

@kettanaito kettanaito changed the title graphql dynamic import error with jest "TypeError: A dynamic import callback was invoked without --experimental-vm-modules" in Jest Aug 30, 2024
@johnal95
Copy link
Author

@kettanaito I understand the decision. Although it might be worth noting that a considerable amount of test suites out there still run on Jest. And migrating some of these codebases (such as the one where I originally encountered this issue, which has thousands of test files) to e.g. Vitest might not be a small/easy task.

I definitely agree with the idea of moving to more modern testing frameworks and towards ESM. But it would be nice to maintain support for Jest for a while longer if possible, to allow for more and/or bigger codebases to adapt.

Since the dynamic import of graphql seems to be the only detail causing trouble here.

Could it be an option to transpile the dynamic import, or post-process the build output for parseGraphQLRequest (exclusively for the CJS build)?

E.g.

try {
// replace `await import` by `require`
- const { parse } = await import('graphql')
+ const { parse } = require('graphql')

  // ... rest of implementation
} catch (error) {
  devUtils.error('Failed to parse a GraphQL query: cannot import...')
  throw error
}

(I know it may not look great, but maybe that could keep the lights on a bit longer 🙂)

Using the --experimental-vm-modules flag is not a viable option for my current use case. (and I personally haven't had a great experience with it in the past, e.g. intermittent segfaults) 🥲

@kettanaito
Copy link
Member

kettanaito commented Aug 30, 2024

Since the dynamic import of graphql seems to be the only detail causing trouble here.

Unfortunately, that's only on the surface. It's a symptom of a bigger issue—not installing graphql for developers that don't intend to do GraphQL mocking. Dynamic import works without issues, but the one you experience is relevant only to Jest.

Could it be an option to transpile the dynamic import, or post-process the build output for parseGraphQLRequest (exclusively for the CJS build)?

This sounds like a good idea. Are you aware of require will remain scoped to that parseQuery function? I guess it should, but I haven't experimented with it in a while.

We can have a custom esbuild plugin that will do that for us. Would you help me test that solution in your project?

@kettanaito
Copy link
Member

@johnal95, I've opened a fix at #2258. Could you please test it in your project?

@johnal95
Copy link
Author

johnal95 commented Aug 30, 2024

@kettanaito out of interest why the decision of modifying the ESM build instead of the CJS? 🤔

I also just tested a very crude example which would produce a very similar outcome in my repro repo, and seems to be working as expected:

https://github.com/johnal95/repro-msw-graphql-import-error/tree/master

(there is a postprocess-msw script that very crudely makes the change to the msw dist. The first change would be assumed to be committed in msw, and the second would be part of the transpilation/post processing logic)

The idea here being that we could refactor the current code to wrap the require/import in a try catch instead of using Promise catch. Then it's just a matter of replacing "await import" by "require" in that one file.

What do you think?

@kettanaito
Copy link
Member

But does the fix I proposed above work?

why the decision of modifying the ESM build instead of the CJS?

Mostly due to the .catch() chaining. Your try/catch proposal is tempting but it will require to store { parse } as a nullable variable first, then assign to it, then the rest of the code is broken if we caught an import error. Now, we need to wrap the entire parse function in try/catch and that may produce faulty errors on other things failing. Now, we have to differentiate between the error origin...

I find it much simpler to replace require() with import().catch().

If you can confirm my fix working, I can proceed with publishing it.

@johnal95
Copy link
Author

Mostly due to the .catch() chaining. Your try/catch proposal is tempting but it will require to store { parse } as a nullable variable first, then assign to it, then the rest of the code is broken if we caught an import error. Now, we need to wrap the entire parse function in try/catch and that may produce faulty errors on other things failing. Now, we have to differentiate between the error origin...

Fair enough.

If you can confirm my fix working, I can proceed with publishing it.

Just tested and I confirm that does solve the issue. Many thanks! 🙏

@kettanaito
Copy link
Member

Released: v2.4.2 🎉

This has been released in v2.4.2!

Make sure to always update to the latest version (npm i msw@latest) to get the newest features and bug fixes.


Predictable release automation by @ossjs/release.

@github-actions github-actions bot locked and limited conversation to collaborators Oct 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working needs:triage Issues that have not been investigated yet. scope:node Related to MSW running in Node
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants