Skip to content

Commit

Permalink
fix(webpack): use new watcher for webpack build coordination plugin (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz authored Aug 25, 2023
1 parent 2122900 commit c175f9b
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 74 deletions.
82 changes: 52 additions & 30 deletions packages/nx/src/command-line/watch/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export interface WatchArguments {
const DEFAULT_PROJECT_NAME_ENV = 'NX_PROJECT_NAME';
const DEFAULT_FILE_CHANGES_ENV = 'NX_FILE_CHANGES';

class BatchCommandRunner {
running = false;
export class BatchFunctionRunner {
private running = false;

pendingProjects = new Set<string>();
pendingFiles = new Set<string>();
private pendingProjects = new Set<string>();
private pendingFiles = new Set<string>();

private get _verbose() {
protected get _verbose() {
return process.env.NX_VERBOSE_LOGGING === 'true';
}

Expand All @@ -32,9 +32,10 @@ class BatchCommandRunner {
}

constructor(
private command: string,
private projectNameEnv: string = DEFAULT_PROJECT_NAME_ENV,
private fileChangesEnv: string = DEFAULT_FILE_CHANGES_ENV
private callback: (
projects: Set<string>,
files: Set<string>
) => Promise<unknown>
) {}

enqueue(projectNames: string[], fileChanges: ChangedFile[]) {
Expand All @@ -48,60 +49,77 @@ class BatchCommandRunner {
return this.process();
}

async process() {
private async process() {
if (!this.running && this.hasPending) {
this.running = true;

// process all pending commands together
const envs = this.createCommandEnvironments();
// Clone the pending projects and files before clearing
const projects = new Set(this.pendingProjects);
const files = new Set(this.pendingFiles);

this._verbose &&
output.logSingleLine(
'about to run commands with these environments: ' +
JSON.stringify(envs)
);
// Clear the pending projects and files
this.pendingProjects.clear();
this.pendingFiles.clear();

return this.run(envs).then(() => {
return this.callback(projects, files).then(() => {
this.running = false;
this._verbose &&
output.logSingleLine('running complete, processing the next batch');
this.process();
});
} else {
this._verbose &&
this.running &&
output.logSingleLine('waiting for commands to finish executing');
output.logSingleLine('waiting for function to finish executing');

this._verbose &&
!this.hasPending &&
output.logSingleLine('no more commands to process');
output.logSingleLine('no more function to process');
}
}
}

class BatchCommandRunner extends BatchFunctionRunner {
constructor(
private command: string,
private projectNameEnv: string = DEFAULT_PROJECT_NAME_ENV,
private fileChangesEnv: string = DEFAULT_FILE_CHANGES_ENV
) {
super((projects, files) => {
// process all pending commands together
const envs = this.createCommandEnvironments(projects, files);

return this.run(envs);
});
}

createCommandEnvironments(): Record<string, string>[] {
private createCommandEnvironments(
projects: Set<string>,
files: Set<string>
): Record<string, string>[] {
const commandsToRun = [];

if (this.pendingProjects.size > 0) {
this.pendingProjects.forEach((projectName) => {
if (projects.size > 0) {
projects.forEach((projectName) => {
commandsToRun.push({
[this.projectNameEnv]: projectName,
[this.fileChangesEnv]: Array.from(this.pendingFiles).join(' '),
[this.fileChangesEnv]: Array.from(files).join(' '),
});
});
} else {
commandsToRun.push({
[this.projectNameEnv]: '',
[this.fileChangesEnv]: Array.from(this.pendingFiles).join(' '),
[this.fileChangesEnv]: Array.from(files).join(' '),
});
}

this.pendingProjects.clear();
this.pendingFiles.clear();

return commandsToRun;
}

async run(envs: Record<string, string>[]) {
this._verbose &&
output.logSingleLine(
'about to run commands with these environments: ' + JSON.stringify(envs)
);

return Promise.all(
envs.map((env) => {
return new Promise<void>((resolve, reject) => {
Expand All @@ -123,7 +141,11 @@ class BatchCommandRunner {
});
});
})
);
).then((r) => {
this._verbose &&
output.logSingleLine('running complete, processing the next batch');
return r;
});
}
}

Expand Down
2 changes: 0 additions & 2 deletions packages/webpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@
"babel-loader": "^9.1.2",
"browserslist": "^4.21.4",
"chalk": "^4.1.0",
"chokidar": "^3.5.1",
"copy-webpack-plugin": "^10.2.4",
"css-loader": "^6.4.0",
"css-minimizer-webpack-plugin": "^5.0.0",
"fork-ts-checker-webpack-plugin": "7.2.13",
"ignore": "^5.0.4",
"less": "4.1.3",
"less-loader": "11.1.0",
"license-webpack-plugin": "^4.0.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { watch } from 'chokidar';
import { execSync } from 'child_process';
import { workspaceLayout } from '@nx/devkit';
import { joinPathFragments } from '@nx/devkit';
import ignore from 'ignore';
import { readFileSync } from 'fs';
import { exec } from 'child_process';
import type { Compiler } from 'webpack';
import { daemonClient } from 'nx/src/daemon/client/client';
import { BatchFunctionRunner } from 'nx/src/command-line/watch';
import { output } from 'nx/src/utils/output';

export class WebpackNxBuildCoordinationPlugin {
private currentlyRunning: 'none' | 'nx-build' | 'webpack-build' = 'none';
Expand All @@ -31,9 +29,23 @@ export class WebpackNxBuildCoordinationPlugin {
});
}

startWatchingBuildableLibs() {
createFileWatcher(process.cwd(), () => {
this.buildChangedProjects();
async startWatchingBuildableLibs() {
const unregisterFileWatcher = await createFileWatcher(
() => this.buildChangedProjects(),
() => {
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);
}
);

process.on('exit', () => {
unregisterFileWatcher();
});
}

Expand All @@ -42,46 +54,46 @@ export class WebpackNxBuildCoordinationPlugin {
await sleep(50);
}
this.currentlyRunning = 'nx-build';
try {
execSync(this.buildCmd, { stdio: [0, 1, 2] });
// eslint-disable-next-line no-empty
} catch (e) {}
this.currentlyRunning = 'none';
return new Promise<void>((res) => {
try {
const cp = exec(this.buildCmd);

cp.stdout.pipe(process.stdout);
cp.stderr.pipe(process.stderr);
cp.on('exit', () => {
res();
});
cp.on('error', () => {
res();
});
// eslint-disable-next-line no-empty
} catch (e) {
res();
} finally {
this.currentlyRunning = 'none';
}
});
}
}

function sleep(time: number) {
return new Promise((resolve) => setTimeout(resolve, time));
}

function getIgnoredGlobs(root: string) {
const ig = ignore();
try {
ig.add(readFileSync(`${root}/.gitignore`, 'utf-8'));
} catch {}
try {
ig.add(readFileSync(`${root}/.nxignore`, 'utf-8'));
} catch {}
return ig;
}

function createFileWatcher(root: string, changeHandler: () => void) {
const ignoredGlobs = getIgnoredGlobs(root);
const layout = workspaceLayout();

const watcher = watch(
[
joinPathFragments(layout.appsDir, '**'),
joinPathFragments(layout.libsDir, '**'),
],
async function createFileWatcher(
changeHandler: () => Promise<void>,
onClose: () => void
) {
const runner = new BatchFunctionRunner(changeHandler);
return daemonClient.registerFileWatcher(
{
cwd: root,
ignoreInitial: true,
watchProjects: 'all',
},
(err, { changedProjects, changedFiles }) => {
if (err === 'closed') {
onClose();
}
// Queue a build
runner.enqueue(changedProjects, changedFiles);
}
);
watcher.on('all', (_event: string, path: string) => {
if (ignoredGlobs.ignores(path)) return;
changeHandler();
});
return { close: () => watcher.close() };
}

0 comments on commit c175f9b

Please sign in to comment.