using `windowsHide: true` is causing an issue on windows: Ctrl + C handling isn't enabled and no `SIGINT` is sent to the child process when users exit the process. See https://github.com/nodejs/node/issues/29837 and https://github.com/nodejs/node-v0.x-archive/issues/5054 for reference. This will cause leftover processes throughout nx. This PR sets `windowsHide: false` everywhere except for the plugin workers and some short-lived utils. They `spawn` child processes but have explicit handling to make sure they kill themselves when the parent process dies, so the missing Ctrl + C handling doesn't cause issues. We will follow up to make sure any other culprits that still cause windows popups (especially when used through Nx Console) are handled. Leaving no leftover processes running is more important for now, though. Keep in mind the underlying tooling (like vite) might have some windows popups themselves that Nx will inherit.
106 lines
3.0 KiB
TypeScript
106 lines
3.0 KiB
TypeScript
import { exec } from 'child_process';
|
|
import type { Compiler } from 'webpack';
|
|
import { daemonClient, isDaemonEnabled } from 'nx/src/daemon/client/client';
|
|
import { BatchFunctionRunner } from 'nx/src/command-line/watch/watch';
|
|
import { output } from 'nx/src/utils/output';
|
|
|
|
export class WebpackNxBuildCoordinationPlugin {
|
|
private currentlyRunning: 'none' | 'nx-build' | 'webpack-build' = 'none';
|
|
private buildCmdProcess: ReturnType<typeof exec> | null = null;
|
|
|
|
constructor(private readonly buildCmd: string, skipInitialBuild?: boolean) {
|
|
if (!skipInitialBuild) {
|
|
this.buildChangedProjects();
|
|
}
|
|
if (isDaemonEnabled()) {
|
|
this.startWatchingBuildableLibs();
|
|
} else {
|
|
output.warn({
|
|
title:
|
|
'Nx Daemon is not enabled. Buildable libs will not be rebuilt on file changes.',
|
|
});
|
|
}
|
|
}
|
|
|
|
apply(compiler: Compiler) {
|
|
compiler.hooks.beforeCompile.tapPromise(
|
|
'IncrementalDevServerPlugin',
|
|
async () => {
|
|
while (this.currentlyRunning === 'nx-build') {
|
|
await sleep(50);
|
|
}
|
|
this.currentlyRunning = 'webpack-build';
|
|
}
|
|
);
|
|
compiler.hooks.done.tapPromise('IncrementalDevServerPlugin', async () => {
|
|
this.currentlyRunning = 'none';
|
|
});
|
|
}
|
|
|
|
async startWatchingBuildableLibs() {
|
|
const unregisterFileWatcher = await this.createFileWatcher();
|
|
|
|
process.on('exit', () => {
|
|
unregisterFileWatcher();
|
|
});
|
|
}
|
|
|
|
async buildChangedProjects() {
|
|
while (this.currentlyRunning === 'webpack-build') {
|
|
await sleep(50);
|
|
}
|
|
this.currentlyRunning = 'nx-build';
|
|
try {
|
|
return await new Promise<void>((res) => {
|
|
this.buildCmdProcess = exec(this.buildCmd, {
|
|
windowsHide: false,
|
|
});
|
|
|
|
this.buildCmdProcess.stdout.pipe(process.stdout);
|
|
this.buildCmdProcess.stderr.pipe(process.stderr);
|
|
this.buildCmdProcess.on('exit', () => {
|
|
res();
|
|
});
|
|
this.buildCmdProcess.on('error', () => {
|
|
res();
|
|
});
|
|
});
|
|
} finally {
|
|
this.currentlyRunning = 'none';
|
|
this.buildCmdProcess = null;
|
|
}
|
|
}
|
|
|
|
private createFileWatcher() {
|
|
const runner = new BatchFunctionRunner(() => this.buildChangedProjects());
|
|
return daemonClient.registerFileWatcher(
|
|
{
|
|
watchProjects: 'all',
|
|
},
|
|
(err, { changedProjects, changedFiles }) => {
|
|
if (err === 'closed') {
|
|
output.error({
|
|
title: 'Watch connection closed',
|
|
bodyLines: [
|
|
'The daemon has closed the connection to this watch process.',
|
|
'Please restart your watch command.',
|
|
],
|
|
});
|
|
process.exit(1);
|
|
}
|
|
|
|
if (this.buildCmdProcess) {
|
|
this.buildCmdProcess.kill(2);
|
|
this.buildCmdProcess = null;
|
|
}
|
|
// Queue a build
|
|
runner.enqueue(changedProjects, changedFiles);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
function sleep(time: number) {
|
|
return new Promise((resolve) => setTimeout(resolve, time));
|
|
}
|