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

Next.js Project Fails to Compile due to Package path ./browser is not exported from package /node_modules/msw #1801

Closed
james-sheen-gg opened this issue Oct 27, 2023 · 11 comments

Comments

@james-sheen-gg
Copy link

james-sheen-gg commented Oct 27, 2023

Description

I'm working on a Next.js project where I'm using MSW for mocking API calls. However, after updating to msw v2.0.0 I am unable to compile the project due to the following error:

./src/mocks/browser.ts:3:0
Module not found: Package path ./browser is not exported from package /node_modules/msw (see exports field in /node_modules/msw/package.json)

Steps to Reproduce

  1. Initialize a Next.js project with TypeScript.
  2. Install MSW using npm install msw or yarn add msw.
  3. Add a mocks/browser.ts file to set up MSW.
  4. Import setupWorker from msw/browser in the browser.ts file. import { setupWorker } from 'msw/browser';
  5. Run npm run dev or yarn dev.

Expected Behavior

The Next.js project should compile successfully and MSW should intercept the network requests.

Actual Behavior

The project fails to compile with the error mentioned above.

Additional Information

  • Next.js version: 12.0.7
  • MSW version: 2.0.0
  • Node.js version: 20.8.0
@ZeroCho
Copy link
Contributor

ZeroCho commented Oct 27, 2023

I think your msw/browser is called in server environment;

"use client";
import { useEffect } from "react";

export const MSWComponent = () => {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") {
        require("@/mocks/browser");
      }
    }
  }, []);

  return null;
};

If I ensure mocks/browser.ts is imported in client (typeof window !== 'undefined'), it works.

Vinnl added a commit to mozilla/fx-private-relay that referenced this issue Oct 30, 2023
Following this migration guide:
https://mswjs.io/docs/migrations/1.x-to-2.x

Note that the migration is not yet complete; we're still running
into what might be an MSW bug, described in
mswjs/msw#1801.
Vinnl added a commit to mozilla/fx-private-relay that referenced this issue Oct 30, 2023
Following this migration guide:
https://mswjs.io/docs/migrations/1.x-to-2.x

Note that the migration is not yet complete; we're still running
into what might be an MSW bug, described in
mswjs/msw#1801.
Vinnl added a commit to mozilla/fx-private-relay that referenced this issue Oct 30, 2023
Following this migration guide:
https://mswjs.io/docs/migrations/1.x-to-2.x

Note that the migration is not yet complete; we're still running
into what might be an MSW bug, described in
mswjs/msw#1801.
@ribeaud
Copy link

ribeaud commented Nov 3, 2023

Same here, but getting following output error instead:

Module not found: Package path ./node is not exported from package /usr/app/node_modules/msw
(see exports field in /usr/app/node_modules/msw/package.json)

Maybe it is worth noting that, in my case, the error is triggered while trying to access a file packed in a third-party library which has been migrated to the latest msw version as well. Somehow, I think that following import (in the third party library):

import { setupServer } from 'msw/node';

did not get correctly exported.

@jakubriedl
Copy link

We had the same issue in our Next.js app. I our case it's caused by the fact that we have our package with complete network toolkit including msw.

In the package we have following snippet (simplified for illustration)

// index.ts
export * from "./jestHelpers"  // includes `import {} from msw/node`
export * from "./browserHelpers"  // includes `import {} from msw/browser`
export * from "./nodeHelpers" // includes `import {} from msw/node`

The error happens during build time (not runtime). None of nodeHelpers are never called in browser and they are not even included in browser bundle thanks to tree-shaking. Webpack though still needs to go through them during build and because of ./node being blocked in browser it errors as it can't find the entry point.

But that's a good thing (I think) because it serves as strict check that node code isn't included in browser bundle and vice versa. Having the code there could cause deeper and hard to debug problems. Which we did have in past with msw@1

We've workaround this, or better say enforced it on our side using aliases in webpack (docs on how to add custom webpack config) and with that the issue goes away. So for anyone who has the same problem this is how you can fix it. However it would be good to add to migration guide and/or FAQ with explanation why is it happening.

  if (context?.isServer) {
    // next server build => ignore msw/browser
    if (Array.isArray(config.resolve.alias)) { // in Next the type is always object, so this branch isn't necessary. But to keep TS happy, avoid @ts-ignore and prevent possible future breaking changes it's good to have it
      config.resolve.alias.push({ name: "msw/browser", alias: false })
    } else {
      config.resolve.alias["msw/browser"] = false
    }
  } else {
    // browser => ignore msw/node
    if (Array.isArray(config.resolve.alias)) {
      config.resolve.alias.push({ name: "msw/node", alias: false })
    } else {
      config.resolve.alias["msw/node"] = false
    }
  }

@ribeaud
Copy link

ribeaud commented Nov 6, 2023

Hi @jakubriedl. Thanks. And yes, this is how (I came across this workaround over the weekend as well) I solved it on our end:

// next.config.js

module.exports = {
  webpack: (config, { isServer }) => {
    if (!isServer) {
      // Setting `resolve.alias` to `false` will tell webpack to ignore a module.
      // `msw/node` is a server-only module that exports methods not available in
      // the `browser`.
      config.resolve.alias = {
        ...config.resolve.alias,
        'msw/node': false,
      };
    }
    return config;
  },
...
};

I was thinking about doing the same for isServer and browser. However I will wait until we face the first problems.

This looks like a hack to me still. I mean, we are talking about a test dependency which somehow manages to disturb our whole deployment.

I am not deep enough in Next.js/webpack to suggest a proper solution here. In my head, all the tests and associated imports (which might be parts of external dependencies) should just be ignored by Next.js during building. So, in the end, this is maybe a Next.js problem?

Thanks for confirming my hack anyway... 💪

Vinnl added a commit to mozilla/fx-private-relay that referenced this issue Nov 6, 2023
Following this migration guide:
https://mswjs.io/docs/migrations/1.x-to-2.x

Note that the migration is not yet complete; we're still running
into what might be an MSW bug, described in
mswjs/msw#1801.
@CaptainOfFlyingDutchman
Copy link

Thank you so much!

We had the same issue in our Next.js app. I our case it's caused by the fact that we have our package with complete network toolkit including msw.

In the package we have following snippet (simplified for illustration)

// index.ts
export * from "./jestHelpers"  // includes `import {} from msw/node`
export * from "./browserHelpers"  // includes `import {} from msw/browser`
export * from "./nodeHelpers" // includes `import {} from msw/node`

The error happens during build time (not runtime). None of nodeHelpers are never called in browser and they are not even included in browser bundle thanks to tree-shaking. Webpack though still needs to go through them during build and because of ./node being blocked in browser it errors as it can't find the entry point.

But that's a good thing (I think) because it serves as strict check that node code isn't included in browser bundle and vice versa. Having the code there could cause deeper and hard to debug problems. Which we did have in past with msw@1

We've workaround this, or better say enforced it on our side using aliases in webpack (docs on how to add custom webpack config) and with that the issue goes away. So for anyone who has the same problem this is how you can fix it. However it would be good to add to migration guide and/or FAQ with explanation why is it happening.

  if (context?.isServer) {
    // next server build => ignore msw/browser
    if (Array.isArray(config.resolve.alias)) { // in Next the type is always object, so this branch isn't necessary. But to keep TS happy, avoid @ts-ignore and prevent possible future breaking changes it's good to have it
      config.resolve.alias.push({ name: "msw/browser", alias: false })
    } else {
      config.resolve.alias["msw/browser"] = false
    }
  } else {
    // browser => ignore msw/node
    if (Array.isArray(config.resolve.alias)) {
      config.resolve.alias.push({ name: "msw/node", alias: false })
    } else {
      config.resolve.alias["msw/node"] = false
    }
  }

@kettanaito
Copy link
Member

Thanks for the active participation here, @ribeaud @jakubriedl!

I agree the proposed solution may work but is indeed a hack. You shouldn't be meddling with import resolution. Next should do that for you. Export conditions are relatively new in Node.js so I suspect Next has to respect them correctly, loading node export condition on the server.

You are free to follow the proposed solution above until that happens. There's nothing we can or should do on MSW's side to mitigate this. I'm fairly confident our exports are correctly written. We forbid msw/node imports in the browser and msw/browser imports in Node.js. In the end, it's your framework's compiler that evaluates those, and so it must be configured correctly by the framework to understand what modules should use what export condition.

@xereda
Copy link

xereda commented Nov 21, 2023

  if (context?.isServer) {
    // next server build => ignore msw/browser
    if (Array.isArray(config.resolve.alias)) { // in Next the type is always object, so this branch isn't necessary. But to keep TS happy, avoid @ts-ignore and prevent possible future breaking changes it's good to have it
      config.resolve.alias.push({ name: "msw/browser", alias: false })
    } else {
      config.resolve.alias["msw/browser"] = false
    }
  } else {
    // browser => ignore msw/node
    if (Array.isArray(config.resolve.alias)) {
      config.resolve.alias.push({ name: "msw/node", alias: false })
    } else {
      config.resolve.alias["msw/node"] = false
    }
  }

In which file do I add this?

@PriscillaSam
Copy link

  if (context?.isServer) {
    // next server build => ignore msw/browser
    if (Array.isArray(config.resolve.alias)) { // in Next the type is always object, so this branch isn't necessary. But to keep TS happy, avoid @ts-ignore and prevent possible future breaking changes it's good to have it
      config.resolve.alias.push({ name: "msw/browser", alias: false })
    } else {
      config.resolve.alias["msw/browser"] = false
    }
  } else {
    // browser => ignore msw/node
    if (Array.isArray(config.resolve.alias)) {
      config.resolve.alias.push({ name: "msw/node", alias: false })
    } else {
      config.resolve.alias["msw/node"] = false
    }
  }

In which file do I add this?

In the webpack entry of your next.config.js file

@kettanaito
Copy link
Member

kettanaito commented Nov 28, 2023

I am really confused as to why Next.js doesn't use the export conditions to properly resolve third-party packages (afaik, Rollup supports those). They know the import context (isServer). They should do that for you, the consumer.

Also, this can still be the consumer's fault if the msw/browser import ends up in the server build somehow. You need to make sure you don't import msw/browser in Next.js modules that are meant for the server-side (like your API routes).

@mifrej
Copy link

mifrej commented Jan 6, 2024

I'm importing a component with a 'use client' directive, where I import import type { SetupWorker } from 'msw/browser'; . Which makes it client-only (browser) code. I'm getting the same error as a result while trying to build.

@LifeofMichal
Copy link

  if (context?.isServer) {
    // next server build => ignore msw/browser
    if (Array.isArray(config.resolve.alias)) { // in Next the type is always object, so this branch isn't necessary. But to keep TS happy, avoid @ts-ignore and prevent possible future breaking changes it's good to have it
      config.resolve.alias.push({ name: "msw/browser", alias: false })
    } else {
      config.resolve.alias["msw/browser"] = false
    }
  } else {
    // browser => ignore msw/node
    if (Array.isArray(config.resolve.alias)) {
      config.resolve.alias.push({ name: "msw/node", alias: false })
    } else {
      config.resolve.alias["msw/node"] = false
    }
  }

In which file do I add this?

I tried this solution but now I'm getting Collecting page data .ReferenceError: expect is not defined even though I'm using jest and the config doesn't do anything to jest setup. I'm trying to sort it out, but maybe someone had the same problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants