create-react-app
create-react-app copied to clipboard
Duplicate path segment error in dynamic imports in Web Worker (react-scripts 5, webpack 5)
Describe the bug
When my app attempts to dynamically import something via a Web Worker, the request has the static/js
part of the path repeated, resulting in a 404 error.
I can reproduce this with a clean install of the typescript template of create-react-app.
The two main factors required are:
- A web worker (I'm using webpack 5's syntax with
import.meta.url
.) - A
homepage
attribute set inpackage.json
. If the app is served directly vialocalhost
it seems the faulty part of the resolution doesn't happen. Our production environment uses a subdomain, though, so I need thehomepage
attribute,
Did you try recovering your dependencies?
I've reproduced this with a clean install. The dependencies are not an issue.
I'm using yarn 1.22.18. I believe the same problem would happen with npm.
$ yarn --version
1.22.18
Which terms did you search for in User Guide?
- webpack 5
- web worker
Environment
Environment Info:
current version of create-react-app: 5.0.1
running from C:\Users\<my-user-name>\AppData\Local\Yarn\Data\global\node_modules\create-react-app
System:
OS: Windows 10 10.0.19041
CPU: (8) x64 Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
Binaries:
Node: 14.19.0 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.18 - C:\Program Files\nodejs\yarn.CMD
npm: 6.14.16 - C:\Program Files\nodejs\npm.CMD
Browsers:
Chrome: Not Found
Edge: Spartan (44.19041.1266.0), Chromium (99.0.1150.52)
Internet Explorer: 11.0.19041.1202
npmPackages:
react: ^18.1.0 => 18.1.0
react-dom: ^18.1.0 => 18.1.0
react-scripts: 5.0.1 => 5.0.1
npmGlobalPackages:
create-react-app: Not Found
Steps to reproduce
- Add a Web Worker to your project, as described in the webpack 5 docs.
- Within the Web Worker, use a dynamic
import()
statement to load something from elsewhere. It can be a local module or something fromnode_modules
. - Consume the web worker somewhere.
- You also need the
homepage
property set inpackage.json
to reproduce this.
(I have fuller details below in the Reproducible demo section.)
Expected behavior
The dynamic import should work.
Actual behavior
The dynamic import fails with a 404 error. A network error is shown. The path includes static/js/static/js
, where it should just be static/js
.
Reproducible demo
- Make a new app using "create react app" with the TypeScript template:
yarn create react-app example-app --template typescript
- Edit the
package.json
file, adding these two lines:
"type": "module",
"homepage": ".",
- Add a new file called
src/OtherModule.ts
with the following content:
export const message = "Message from another module.";
- Add a new file called
src/TestWorker.worker.ts
with the following content:
/* eslint no-restricted-globals: 0 */
const worker: Worker = self as any;
worker.addEventListener("message", async (event) => {
const data = event.data;
console.log("[Worker] Received:", data);
// This dynamic import is put into a separate chunk by webpack.
// In a production build, the loading of this module fails with a duplicate "static/js" in the path.
const { message } = await import("./OtherModule");
worker.postMessage(message);
});
export {};
- Add the following code to
src/App.tsx
, just before thereturn
statement:
useEffect(() => {
const worker = new Worker(new URL("./TestWorker.worker.ts", import.meta.url));
worker.onmessage = (event: MessageEvent<any>) => {
console.log(`[App] Received:`, event.data);
};
// The expected behaviour is for the worker to send a message in response to this one.
// In reality, there's an error loading one of the chunks.
worker.postMessage("abc");
});
- Add
useEffect
to the import from'react'
insrc/App.tsx
:
import React, { useEffect } from 'react';
- Build the app:
yarn build
- Serve the
build
folder via a web server. - Visit the site and open the console.
Expected behavior:
The console should show log output of the App and the Worker receiving messages from each other.
[Worker] Received: abc
[App] Received: Message from another module.
Actual behavior:
A network error is shown. The path includes static/js/static/js
, where it should just be static/js
.
[Worker] Received: abc
GET http://localhost/example-app/static/js/static/js/975.6b1d0bce.chunk.js
[HTTP/1.1 404 Not Found 0ms]
NetworkError: A network error occurred.
I'm having the exact same problem.
I'm using node v16.13.2 and npm 8.1.2.
I tried poking around in the react scripts webpack config with no luck. I'm going to create a script that copies and pastes all the static/js
files to static/js/static/js
as a workaround for now.
Same Issue for me. I just created the path so it would work for now. I"m using:
I am implementing a workaround in the short term, too. I was able to safely determine which files needed to move to static/js/static/js
which I did rather than copying everything.
In my case, only main.[hash].js
and web-worker.[hash].chunk.js
remained in the original location.
I named the latter like so:
new Worker(
/* webpackChunkName: "web-worker" */
new URL("./my-file-path.worker.ts", import.meta.url)
);
It's good to be unblocked, but I'd still be interested to see this fixed so I can remove my workaround.
We are also seeing this bug. Removing homepage: '.'
may not be an option for us. @JacobCutshall and @kenlyon , could you please explain to me your workaround? 🙏
We are also seeing this bug. Removing
homepage: '.'
may not be an option for us. @JacobCutshall and @kenlyon , could you please explain to me your workaround? 🙏
@kialam I added a function that is called during my build process:
import fse from "fs-extra"; // You could also use node's own "fe" module for moving files.
import path from "path";
import { fileURLToPath, URL } from "url";
async function handleWebpackPathBug() {
const repoRootUrl = new URL("..", import.meta.url);
const repoRoot = fileURLToPath(repoRootUrl);
const sourceFolder = path.join(repoRoot, "build/static/js");
const destinationFolder = path.join(sourceFolder, "static/js");
if (!fse.existsSync(sourceFolder)) {
throw new Error(
`Failed to handle webpack path bug. Source folder (${sourceFolder}) does not exist.`
);
}
await fse.ensureDir(destinationFolder);
const staticJsFiles = await fse.readdir(sourceFolder);
// Move all .js files that do not start with "main." or "web-worker." as those two get correctly loaded directly from static/js.
const filesToMove = staticJsFiles.filter((val) => /^(?!main\.|web-worker\.).+\.js$/.test(val));
for (const file of filesToMove) {
await fse.move(path.join(sourceFolder, file), path.join(destinationFolder, file), {
overwrite: true,
});
}
}
This can be resolved by setting publicPath='auto'
in webpack, however this is not currently supported by CRA.
@amcgee Do you know if support for publicPath='auto'
has been added to CRA in the past year?
I've fallen foul of this bug again today as my workaround moved some new chunks that should have remained in the original static/js
location.
For anyone struggling at same issue, I solved it with CRACO and the following config on craco.config.js
:
module.exports = {
webpack: {
configure: {
output: {
publicPath: 'auto',
},
},
},
};
Is 2024 and still not supported by CRA.