Skip to content

Commit

Permalink
feat(js): use the daemon watcher instead of parcel/watcher (nrwl#13413)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cammisuli authored Nov 30, 2022
1 parent 183e3c1 commit 8bfc0b5
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 75 deletions.
1 change: 0 additions & 1 deletion packages/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"@nrwl/jest": "file:../jest",
"@nrwl/linter": "file:../linter",
"@nrwl/workspace": "file:../workspace",
"@parcel/watcher": "2.0.4",
"chalk": "4.1.0",
"fast-glob": "3.2.7",
"fs-extra": "^10.1.0",
Expand Down
7 changes: 4 additions & 3 deletions packages/js/src/executors/tsc/tsc.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,15 @@ export async function* tscExecutor(
const disposeWatchAssetChanges =
await assetHandler.watchAndProcessOnAssetChange();
const disposePackageJsonChanges = await watchForSingleFileChanges(
join(context.root, projectRoot),
context.projectName,
options.projectRoot,
'package.json',
() => updatePackageJson(options, context, target, dependencies)
);
const handleTermination = async (exitCode: number) => {
await disposeWatchAssetChanges();
await disposePackageJsonChanges();
await typescriptCompilation.close();
disposeWatchAssetChanges();
disposePackageJsonChanges();
process.exit(exitCode);
};
process.on('SIGINT', () => handleTermination(128 + 2));
Expand Down
98 changes: 72 additions & 26 deletions packages/js/src/utils/assets/copy-assets-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,62 @@ import * as fse from 'fs-extra';

import { CopyAssetsHandler } from './copy-assets-handler';

import { Subject } from 'rxjs';
import type { ChangedFile } from 'nx/src/daemon/client/client';

const mockWatcher = new Subject<ChangedFile>();

jest.mock(
'nx/src/daemon/client/client',
(): Partial<typeof import('nx/src/daemon/client/client')> => {
const original = jest.requireActual('nx/src/daemon/client/client');
return {
...original,
daemonClient: {
registerFileWatcher: async (
config: unknown,
callback: (
err,
data: {
changedProjects: string[];
changedFiles: ChangedFile[];
}
) => void
) => {
mockWatcher.subscribe((data) => {
callback(null, {
changedProjects: [],
changedFiles: [data],
});
});
return () => {};
},
},
};
}
);

function createMockedWatchedFile(path: string) {
mockWatcher.next({
type: 'create',
path,
});
}

function deletedMockedWatchedFile(path: string) {
mockWatcher.next({
type: 'delete',
path,
});
}

function updateMockedWatchedFile(path: string) {
mockWatcher.next({
type: 'update',
path,
});
}

describe('AssetInputOutputHandler', () => {
let sut: CopyAssetsHandler;
let rootDir: string;
Expand Down Expand Up @@ -50,29 +106,17 @@ describe('AssetInputOutputHandler', () => {
test('watchAndProcessOnAssetChange', async () => {
const dispose = await sut.watchAndProcessOnAssetChange();

fse.writeFileSync(path.join(rootDir, 'LICENSE'), 'license');
await wait(100);
fse.writeFileSync(path.join(projectDir, 'README.md'), 'readme');
await wait(100); // give watch time to react
fse.writeFileSync(path.join(projectDir, 'docs/test1.md'), 'test');
await wait(100);
fse.writeFileSync(path.join(projectDir, 'docs/test2.md'), 'test');
await wait(100);
fse.writeFileSync(path.join(projectDir, 'docs/ignore.md'), 'IGNORE ME');
await wait(100);
fse.writeFileSync(path.join(projectDir, 'docs/git-ignore.md'), 'IGNORE ME');
await wait(100);
fse.writeFileSync(path.join(projectDir, 'docs/nx-ignore.md'), 'IGNORE ME');
await wait(100);
fse.writeFileSync(
path.join(projectDir, 'docs/a/b/nested-ignore.md'),
'IGNORE ME'
);
await wait(100);
fse.writeFileSync(path.join(projectDir, 'docs/test1.md'), 'updated');
await wait(100);
fse.removeSync(path.join(projectDir, 'docs'));
await wait(100);
createMockedWatchedFile(path.join(rootDir, 'LICENSE'));
createMockedWatchedFile(path.join(projectDir, 'README.md'));
createMockedWatchedFile(path.join(projectDir, 'docs/test1.md'));
createMockedWatchedFile(path.join(projectDir, 'docs/test2.md'));
createMockedWatchedFile(path.join(projectDir, 'docs/ignore.md'));
createMockedWatchedFile(path.join(projectDir, 'docs/git-ignore.md'));
createMockedWatchedFile(path.join(projectDir, 'docs/nx-ignore.md'));
createMockedWatchedFile(path.join(projectDir, 'docs/a/b/nested-ignore.md'));
updateMockedWatchedFile(path.join(projectDir, 'docs/test1.md'));
deletedMockedWatchedFile(path.join(projectDir, 'docs/test1.md'));
deletedMockedWatchedFile(path.join(projectDir, 'docs/test2.md'));

expect(callback.mock.calls).toEqual([
[
Expand Down Expand Up @@ -120,14 +164,17 @@ describe('AssetInputOutputHandler', () => {
},
],
],
// Deleting the directory should only happen once, not per file.
[
[
{
type: 'delete',
src: path.join(rootDir, 'mylib/docs/test1.md'),
dest: path.join(rootDir, 'dist/mylib/docs/test1.md'),
},
],
],
[
[
{
type: 'delete',
src: path.join(rootDir, 'mylib/docs/test2.md'),
Expand All @@ -137,8 +184,7 @@ describe('AssetInputOutputHandler', () => {
],
]);

await dispose();
fse.removeSync(rootDir);
dispose();
});

test('processAllAssetsOnce', async () => {
Expand Down
25 changes: 15 additions & 10 deletions packages/js/src/utils/assets/copy-assets-handler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Event } from '@parcel/watcher';
import * as minimatch from 'minimatch';
import * as path from 'path';
import * as fse from 'fs-extra';
import ignore, { Ignore } from 'ignore';
import * as fg from 'fast-glob';
import { AssetGlob } from '@nrwl/workspace/src/utilities/assets';
import { logger } from '@nrwl/devkit';
import { ChangedFile, daemonClient } from 'nx/src/daemon/client/client';

export type FileEventType = 'create' | 'update' | 'delete';

Expand Down Expand Up @@ -138,23 +138,28 @@ export class CopyAssetsHandler {
);
}

async watchAndProcessOnAssetChange(): Promise<() => Promise<void>> {
const watcher = await import('@parcel/watcher');
const subscription = await watcher.subscribe(
this.rootDir,
(err, events) => {
if (err) {
async watchAndProcessOnAssetChange(): Promise<() => void> {
const unregisterFileWatcher = await daemonClient.registerFileWatcher(
{
watchProjects: 'all',
includeGlobalWorkspaceFiles: true,
},
(err, data) => {
if (err === 'closed') {
logger.error(`Watch error: Daemon closed the connection`);
process.exit(1);
} else if (err) {
logger.error(`Watch error: ${err?.message ?? 'Unknown'}`);
} else {
this.processEvents(events);
this.processEvents(data.changedFiles);
}
}
);

return () => subscription.unsubscribe();
return () => unregisterFileWatcher();
}

private async processEvents(events: Event[]): Promise<void> {
private async processEvents(events: ChangedFile[]): Promise<void> {
const fileEvents: FileEvent[] = [];
for (const event of events) {
const pathFromRoot = path.relative(this.rootDir, event.path);
Expand Down
3 changes: 2 additions & 1 deletion packages/js/src/utils/package-json/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export async function copyPackageJson(

if (options.watch) {
const dispose = await watchForSingleFileChanges(
join(context.root, projectRoot),
context.projectName,
options.projectRoot,
'package.json',
() => updatePackageJson(options, context, target, dependencies)
);
Expand Down
35 changes: 20 additions & 15 deletions packages/js/src/utils/watch-for-single-file-changes.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { logger } from '@nrwl/devkit';
import { daemonClient } from 'nx/src/daemon/client/client';
import { join } from 'path';

export async function watchForSingleFileChanges(
watchDir: string,
projectName: string,
projectRoot: string,
relativeFilePath: string,
callback: () => void
) {
const watcher = await import('@parcel/watcher');
const subscription = await watcher.subscribe(watchDir, (err, events) => {
const file = join(watchDir, relativeFilePath);
if (err) {
logger.error(`Watch error: ${err?.message ?? 'Unknown'}`);
} else {
for (const event of events) {
if (event.path === file) {
callback();
break;
}
): Promise<() => void> {
const unregisterFileWatcher = await daemonClient.registerFileWatcher(
{ watchProjects: [projectName] },
(err, data) => {
if (err === 'closed') {
logger.error(`Watch error: Daemon closed the connection`);
process.exit(1);
} else if (err) {
logger.error(`Watch error: ${err?.message ?? 'Unknown'}`);
} else if (
data.changedFiles.some(
(file) => file.path == join(projectRoot, relativeFilePath)
)
) {
callback();
}
}
});
);

return () => subscription.unsubscribe();
return () => unregisterFileWatcher();
}
6 changes: 5 additions & 1 deletion packages/nx/src/daemon/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const DAEMON_ENV_SETTINGS = {
};

export type UnregisterCallback = () => void;
export type ChangedFile = {
path: string;
type: 'create' | 'update' | 'delete';
};

export class DaemonClient {
constructor(private readonly nxJson: NxJsonConfiguration) {
Expand Down Expand Up @@ -113,7 +117,7 @@ export class DaemonClient {
error: Error | null | 'closed',
data: {
changedProjects: string[];
changedFiles: { path: string; type: 'CREATE' | 'UPDATE' | 'DELETE' }[];
changedFiles: ChangedFile[];
} | null
) => void
): Promise<UnregisterCallback> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { projectFileMapWithFiles } from '../project-graph-incremental-recomputat

export type ChangedFile = {
path: string;
type: 'CREATED' | 'UPDATED' | 'DELETED';
type: 'create' | 'update' | 'delete';
};

export function getProjectsAndGlobalChanges(
Expand All @@ -24,15 +24,15 @@ export function getProjectsAndGlobalChanges(
const allChangedFiles: ChangedFile[] = [
...(createdFiles ?? []).map<ChangedFile>((c) => ({
path: c,
type: 'CREATED',
type: 'create',
})),
...(updatedFiles ?? []).map<ChangedFile>((c) => ({
path: c,
type: 'UPDATED',
type: 'update',
})),
...(deletedFiles ?? []).map<ChangedFile>((c) => ({
path: c,
type: 'DELETED',
type: 'delete',
})),
];

Expand Down
32 changes: 18 additions & 14 deletions packages/nx/src/project-graph/build-nodes/workspace-projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,24 @@ export function buildWorkspaceProjectNodes(
if (existsSync(join(projectRoot, 'package.json'))) {
p.targets = mergeNpmScriptsWithTargets(projectRoot, p.targets);

const { nx }: PackageJson = readJsonFile(
join(projectRoot, 'package.json')
);
if (nx?.tags) {
p.tags = [...(p.tags || []), ...nx.tags];
}
if (nx?.implicitDependencies) {
p.implicitDependencies = [
...(p.implicitDependencies || []),
...nx.implicitDependencies,
];
}
if (nx?.namedInputs) {
p.namedInputs = { ...(p.namedInputs || {}), ...nx.namedInputs };
try {
const { nx }: PackageJson = readJsonFile(
join(projectRoot, 'package.json')
);
if (nx?.tags) {
p.tags = [...(p.tags || []), ...nx.tags];
}
if (nx?.implicitDependencies) {
p.implicitDependencies = [
...(p.implicitDependencies || []),
...nx.implicitDependencies,
];
}
if (nx?.namedInputs) {
p.namedInputs = { ...(p.namedInputs || {}), ...nx.namedInputs };
}
} catch {
// ignore json parser errors
}
}

Expand Down

0 comments on commit 8bfc0b5

Please sign in to comment.