Msw: CRA now enforce public url to be used in dev server and this breaks MSW integration.

Created on 4 Sep 2020  路  3Comments  路  Source: mswjs/msw

Describe the bug

Recently the react-dev-utils that is part of the react-scripts package used by create react app got an update that makes it so that the public path is used in the dev-server. The mockedServiceWorker will need to be registered with this public url but now it will not intercept any requests.

Environment

  • msw: 0.20.5
  • create-react-app: 3.4.1

Please also provide your browser version.
chrome 84

To Reproduce

Steps to reproduce the behavior:

  1. Make a create-react-app project using at least create-react-app 3.4.1.
  2. Add msw as dependency and run init to put the mockServiceWorker.js in the public folder.
  3. Add a "homepage" : "/foo/bar" property in the package.json file to make it the public path. The app is now hosted on localhost:3000/foo/bar.
  4. Add browser.js and handler.js as usual and integrate the msw in the app.
//src/mocks/handlers.ts
import { rest } from "msw";

const handlers = [
  rest.get(`${process.env.PUBLIC_URL}/test`, (req, res, ctx) =>
    res(ctx.json({ message: "Handled by mock service worker" }))
  ),
];

export { handlers };
//src/mocks/browser.ts
import { setupWorker } from 'msw'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers);

Remember to add the process.env.PUBLIC_URL to the path of the service worker

//index.tsx
import("./mocks/browser").then(({ worker }) => {
  worker
    ?.start({
      serviceWorker: {
        url: `${process.env.PUBLIC_URL}/mockServiceWorker.js`,
      },
    })
    .then(() => {
      ReactDOM.render(
        <React.StrictMode>
             <button
              onClick={() =>
                fetch(`${process.env.PUBLIC_URL}/test`)
                  .then((response) => response.json())
                  .then((json) => console.log("Got reponse:", json.message))
                  .catch(error => console.error(error))
              }
            >
              FETCH
            </button>
        </React.StrictMode>,
        document.getElementById("root")
      );
    });
});
  1. Start the app, open developer console and notice the [MSW] Mocking enabled message.
  2. Press the FETCH-button
  3. Check the console. There will be an error index.js:1 SyntaxError: Unexpected token < in JSON at position 0 because the mockedServiceWorker did not intercept the request.

Expected behavior

I would expect to be able to run the application with a PUBLIC_URL and still use MSW

bug potentially solved

Most helpful comment

I was having the same issue. Here is my solution, I have a quick redirect when running mocked version.

// index.tsx

async function render() {
  if (process.env.REACT_APP_MOCKED_DATA === "true") {
    if (window.location.pathname === "/abc") {
      window.location.pathname = "/abc/";
      return;
    }

    // eslint-disable-next-line
    const { worker } = require("./mock-api/browser");

    await worker.start({
      waitUntilReady: true,
      serviceWorker: {
        url: `${process.env.PUBLIC_URL}/mockServiceWorker.js`,
      },
      onUnhandledRequest: (req: MockedRequest) => {
        console.error("Found an unhanded %s request to %s", req.method, req.url.href);
      },
    });
  }

  ReactDOM.render(<App />, document.getElementById("root"));
}

render();

This might have some unintended consequences as locally there will be a trialing slash.

All 3 comments

Hello! I looked into this for you. Good and bad news - the only way you're going to be able to make this work is by making sure you have a trailing slash on your URL - http://localhost:3000/foo/bar/.

Basically, msw is working fine, but it's the actual claim behavior of service workers in general that is a problem. Here is an example error of when I tried to force scope: '/':

The path of the provided scope ('/') is not under the max scope allowed ('/foo/bar/'). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.

Notice it automatically enforcing the trailing slash. There is a lengthy thread about the problematic behavior here: https://github.com/w3c/ServiceWorker/issues/1272.

I don't exactly understand your explanation for needing to have the app loaded from a sub route, but the best idea is to just have the mockServiceWorker.js file served from the root of the domain so it has access to all child routes if possible. If not, the trailing slash appears to be the only resolution.

Thanks. Your explanation of the service worker scope was really helpful. I鈥檒l close the ticket.

I was having the same issue. Here is my solution, I have a quick redirect when running mocked version.

// index.tsx

async function render() {
  if (process.env.REACT_APP_MOCKED_DATA === "true") {
    if (window.location.pathname === "/abc") {
      window.location.pathname = "/abc/";
      return;
    }

    // eslint-disable-next-line
    const { worker } = require("./mock-api/browser");

    await worker.start({
      waitUntilReady: true,
      serviceWorker: {
        url: `${process.env.PUBLIC_URL}/mockServiceWorker.js`,
      },
      onUnhandledRequest: (req: MockedRequest) => {
        console.error("Found an unhanded %s request to %s", req.method, req.url.href);
      },
    });
  }

  ReactDOM.render(<App />, document.getElementById("root"));
}

render();

This might have some unintended consequences as locally there will be a trialing slash.

Was this page helpful?
0 / 5 - 0 ratings