Compilation stuck at 'Files successfully emitted, waiting for typecheck results...' caused by race condition in TypeScript + web workers context
Created by: bluenote10
Describe the bug
This is a more specific version of bug #8707 (closed) with an attempt to provide a minimal reproducing example and a first cause analysis. Edit: Just found another existing issue for that https://github.com/facebook/create-react-app/issues/10315
When working with loaders for web workers there seems to be a race condition in handling the TypeScript compilation, which can cause the compilation to get stuck at: "Files successfully emitted, waiting for typecheck results...".
Did you try recovering your dependencies?
This has nothing to do with a corrupt dependency tree.
Environment
Environment Info:
current version of create-react-app: 4.0.3
running from /home/fabian/.npm/_npx/c67e74de0542c87c/node_modules/create-react-app
System:
OS: Linux 4.15 Ubuntu 18.04.4 LTS (Bionic Beaver)
CPU: (4) x64 Intel(R) Core(TM) i5-4670 CPU @ 3.40GHz
Binaries:
Node: 14.16.0 - ~/.nvm/versions/node/v14.16.0/bin/node
Yarn: Not Found
npm: 7.6.3 - ~/.nvm/versions/node/v14.16.0/bin/npm
Browsers:
Chrome: 80.0.3987.162
Firefox: 87.0
npmPackages:
react: ^17.0.2 => 17.0.2
react-dom: ^17.0.2 => 17.0.2
react-scripts: 4.0.3 => 4.0.3
npmGlobalPackages:
create-react-app: Not Found
Steps to reproduce
I have setup a reproducing example here. Since the problem involves a race condition, there is no 100% reproduction. In the reproduction example it is roughly 10%, in our real production code it is more like 80%.
The general pattern causing the problem involves loaders for web workers. In my example I'm using comlink-loader
, but issue #8707 (closed) shows cases involving other loaders like workerize-loader
.
index.tsx
:
import React from "react";
import ReactDOM from "react-dom";
// No double compilation without these two lines:
import Worker from "./worker";
const worker = new Worker();
ReactDOM.render(
<React.StrictMode>
<div>Hello World</div>
</React.StrictMode>,
document.getElementById("root")
);
// The following code merely exists to slow down the TS compilation
// of this file (it slows it down a lot, several seconds on my machine!).
// To trigger a recompilation change the number suffix (it looks like
// simply re-saving / touching the file isn't sufficient to cause a
// recompilation).
export function complexGeneric1234(name: keyof JSX.IntrinsicElements) {
const wrapped: React.FC<JSX.IntrinsicElements[typeof name]> = (props) =>
React.createElement(name, { ...props }, props.children);
return wrapped;
}
worker/index.ts
:
// eslint-disable-next-line
import Worker from "comlink-loader!./worker";
export default Worker;
worker/worker.ts
:
export function compute(): number {
return 42;
}
worker/custom.d.ts
:
declare module "comlink-loader!*" {
class WebpackWorker extends Worker {
constructor();
compute(): Promise<number>;
}
export = WebpackWorker;
}
Expected behavior
Compilation not stuck.
Actual behavior
Compilation is randomly stuck at WebpackDevServerUtils.js#L187
Files successfully emitted, waiting for typecheck results...
because the promise in WebpackDevServerUtils.js#L192 never completes.
The cause of the problem seems to be a race condition between the callbacks "beforeCompile"
(WebpackDevServerUtils.js#L143-L147) and "afterTypeScriptCheck"
(WebpackDevServerUtils.js#L149-L163). Due to the web worker loader, there are multiple calls to these callbacks, in contrast to just a single call in normal projects.
I have placed console.log
's in the callbacks, and enforcing a non-interactive execution via npm start | cat
reveals this order in case the compilation gets stuck:
Compiling...
beforeCompile
afterTypeScriptCheck
beforeCompile
Files successfully emitted, waiting for typecheck results...
Note that beforeCompile
gets called a second time after the promise has been completed. This resets the promise to "pending" state, and therefore the compilation gets stuck.
For comparison, this is an execution which did not trigger the problem:
Compiling...
beforeCompile
beforeCompile
Files successfully emitted, waiting for typecheck results...
afterTypeScriptCheck
Compiled with warnings.
Note that this time the second beforeCompile
occurred early enough to not reset the promise. Therefore it looks like it is a race condition between these callbacks.
Reproducible demo
https://github.com/bluenote10/js-debug-projects/tree/master/cra_webworker