Skip to content

Commit 673fcaf

Browse files
authored
Merge pull request #167 from llllvvuu/feat/on_emit
feat: --onEmit
2 parents 6d8a08f + 1c266ae commit 673fcaf

File tree

7 files changed

+90
-0
lines changed

7 files changed

+90
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
1212
| `--onSuccess COMMAND` | Executes `COMMAND` on **every successful** compilation. |
1313
| `--onFirstSuccess COMMAND` | Executes `COMMAND` on the **first successful** compilation. |
14+
| `--onEmit COMMAND` | Executes debounced `COMMAND` on **every emitted file**, ignoring unchanged files and disregards compilation success or failure. |
15+
| `--onEmitDebounceMs DELAY` | Delay by which to debounce `--onEmit` (default: 300). |
1416
| `--onFailure COMMAND` | Executes `COMMAND` on **every failed** compilation. |
1517
| `--onCompilationStarted COMMAND` | Executes `COMMAND` on **every compilation start** event (initial and incremental). |
1618
| `--onCompilationComplete COMMAND` | Executes `COMMAND` on **every successful or failed** compilation. |
@@ -118,5 +120,9 @@ try {
118120
Notes:
119121

120122
- The (`onSuccess`) `COMMAND` will not run if the compilation failed.
123+
- The (`onEmit`) `COMMAND` will not run if the compilation succeeded with no changed files, unless it is the first success.
124+
- The (`onEmit`) `COMMAND` will run even if the compilation failed, but emitted changed files.
125+
- The (`onEmit`) `COMMAND` will not run 100 times for 100 files, due to `--onEmitDebounce`
126+
- The (`onEmit`) `COMMAND` is not cancelling the `onSuccess`/`onFirstSuccess`/`onFailure`/`onCompilationComplete`/`onCompilationStarted` commands and vice versa.
121127
- `tsc-watch` is using the currently installed TypeScript compiler.
122128
- `tsc-watch` is not changing the compiler, just adds the new arguments, compilation is the same, and all other arguments are the same.

src/client/client.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ export class TscWatchClient extends EventEmitter {
5858
this.tsc.send('run-on-success-command');
5959
}
6060
}
61+
62+
runOnEmitCommand() {
63+
if (this.tsc) {
64+
this.tsc.send('run-on-emit-command');
65+
}
66+
}
6167
}
6268

6369
function deserializeTscMessage(strMsg: string): [string, string?] {

src/lib/args-manager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export function extractArgs(inputArgs: string[]) {
4848
const onFirstSuccessCommand = extractCommandWithValue(args, '--onFirstSuccess');
4949
const onSuccessCommand = extractCommandWithValue(args, '--onSuccess');
5050
const onFailureCommand = extractCommandWithValue(args, '--onFailure');
51+
const onEmitCommand = extractCommandWithValue(args, '--onEmit');
52+
const onEmitDebounceMs = Number(extractCommandWithValue(args, '--onEmitDebounceMs')) || 300;
5153
const onCompilationStarted = extractCommandWithValue(args, '--onCompilationStarted');
5254
const onCompilationComplete = extractCommandWithValue(args, '--onCompilationComplete');
5355
const maxNodeMem = extractCommandWithValue(args, '--maxNodeMem');
@@ -75,6 +77,8 @@ export function extractArgs(inputArgs: string[]) {
7577
onFirstSuccessCommand,
7678
onSuccessCommand,
7779
onFailureCommand,
80+
onEmitCommand,
81+
onEmitDebounceMs,
7882
onCompilationStarted,
7983
onCompilationComplete,
8084
maxNodeMem,

src/lib/debounce.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function debounce<T extends (...args: Parameters<T>) => void>(this: ThisParameterType<T>, fn: T, delay = 300) {
2+
let timer: ReturnType<typeof setTimeout> | undefined
3+
return (...args: Parameters<T>) => {
4+
timer && clearTimeout(timer)
5+
timer = setTimeout(() => fn.apply(this, args), delay)
6+
}
7+
}

src/lib/stdout-manipulator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const newAdditionToSyntax = [
2222
' --onSuccess COMMAND Executes `COMMAND` on **every successful** compilation.',
2323
' --onFirstSuccess COMMAND Executes `COMMAND` on the **first successful** compilation.',
2424
' --onFailure COMMAND Executes `COMMAND` on **every failed** compilation.',
25+
' --onEmit COMMAND Executes debounced `COMMAND` on **every emitted file**, ignoring unchanged files and disregards compilation success or failure.',
26+
' --onEmitDebounceMs DELAY Delay by which to debounce `--onEmit` (default: 300).',
2527
' --onCompilationStarted COMMAND Executes `COMMAND` on **every compilation start** event.',
2628
' --onCompilationComplete COMMAND Executes `COMMAND` on **every successful or failed** compilation.',
2729
' --noColors Removes the red/green colors from the compiler output',

src/lib/tsc-watch.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import nodeCleanup, { uninstall } from 'node-cleanup';
44
import spawn from 'cross-spawn';
55
import { run } from './runner';
66
import { extractArgs } from './args-manager';
7+
import { debounce } from './debounce';
78
import { manipulate, detectState, deleteClear, print } from './stdout-manipulator';
89
import { createInterface } from 'readline';
910
import { ChildProcess } from 'child_process';
@@ -12,13 +13,16 @@ let firstTime = true;
1213
let firstSuccessKiller: (() => Promise<void>) | null = null;
1314
let successKiller: (() => Promise<void>) | null = null;
1415
let failureKiller: (() => Promise<void>) | null = null;
16+
let emitKiller: (() => Promise<void>) | null = null;
1517
let compilationStartedKiller: (() => Promise<void>) | null = null;
1618
let compilationCompleteKiller: (() => Promise<void>) | null = null;
1719

1820
const {
1921
onFirstSuccessCommand,
2022
onSuccessCommand,
2123
onFailureCommand,
24+
onEmitCommand,
25+
onEmitDebounceMs,
2226
onCompilationStarted,
2327
onCompilationComplete,
2428
maxNodeMem,
@@ -71,6 +75,27 @@ function killProcesses(currentCompilationId: number, killAll: boolean): Promise<
7175
return runningKillProcessesPromise;
7276
}
7377

78+
let runningKillEmitProcessesPromise: Promise<number> | null = null;
79+
// The same as `killProcesses`, but we separate it to avoid canceling each other
80+
function killEmitProcesses(currentEmitId: number): Promise<number> {
81+
if (runningKillEmitProcessesPromise) {
82+
return runningKillEmitProcessesPromise.then(() => currentEmitId);
83+
}
84+
85+
let emitKilled = Promise.resolve();
86+
if (emitKiller) {
87+
emitKilled = emitKiller();
88+
emitKiller = null;
89+
}
90+
91+
runningKillEmitProcessesPromise = emitKilled.then(() => {
92+
runningKillEmitProcessesPromise = null;
93+
return currentEmitId;
94+
});
95+
96+
return runningKillEmitProcessesPromise;
97+
}
98+
7499
function runOnCompilationStarted(): void {
75100
if (onCompilationStarted) {
76101
compilationStartedKiller = run(onCompilationStarted);
@@ -101,6 +126,14 @@ function runOnSuccessCommand(): void {
101126
}
102127
}
103128

129+
const debouncedEmit = onEmitCommand
130+
? debounce(() => { emitKiller = run(onEmitCommand) }, onEmitDebounceMs)
131+
: undefined;
132+
133+
function runOnEmitCommand(): void {
134+
debouncedEmit?.();
135+
}
136+
104137
function getTscPath(): string {
105138
let tscBin: string;
106139
try {
@@ -152,6 +185,14 @@ tscProcess.stderr.pipe(process.stderr);
152185
const rl = createInterface({ input: tscProcess.stdout });
153186

154187
let compilationId = 0;
188+
let emitId = 0;
189+
190+
function triggerOnEmit() {
191+
if (onEmitCommand) {
192+
killEmitProcesses(++emitId).then((previousEmitId) => previousEmitId === emitId && runOnEmitCommand());
193+
}
194+
}
195+
155196
rl.on('line', function (input) {
156197
if (noClear) {
157198
input = deleteClear(input);
@@ -171,6 +212,7 @@ rl.on('line', function (input) {
171212

172213
if (state.fileEmitted !== null) {
173214
Signal.emitFile(state.fileEmitted);
215+
triggerOnEmit();
174216
}
175217

176218
if (compilationStarted) {
@@ -200,6 +242,7 @@ rl.on('line', function (input) {
200242
firstTime = false;
201243
Signal.emitFirstSuccess();
202244
runOnFirstSuccessCommand();
245+
triggerOnEmit();
203246
}
204247

205248
Signal.emitSuccess();
@@ -242,6 +285,12 @@ if (typeof process.on === 'function') {
242285
}
243286
break;
244287

288+
case 'run-on-emit-command':
289+
if (emitKiller) {
290+
emitKiller().then(runOnEmitCommand);
291+
}
292+
break;
293+
245294
default:
246295
console.log('Unknown message', msg);
247296
}

src/test/args-manager.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,22 @@ describe('Args Manager', () => {
100100
).toBe('COMMAND_TO_RUN');
101101
});
102102

103+
it('Should return the onEmit', () => {
104+
expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onEmitCommand).toBe(null);
105+
expect(
106+
extractArgs(['node', 'tsc-watch.js', '--onEmit', 'COMMAND_TO_RUN', '1.ts'])
107+
.onEmitCommand,
108+
).toBe('COMMAND_TO_RUN');
109+
});
110+
111+
it('Should return the onEmitDebounceMs', () => {
112+
expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onEmitDebounceMs).toBe(300);
113+
expect(
114+
extractArgs(['node', 'tsc-watch.js', '--onEmitDebounceMs', '200', '1.ts'])
115+
.onEmitDebounceMs,
116+
).toBe(200);
117+
});
118+
103119
it('Should return the onCompilationComplete', () => {
104120
expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onCompilationComplete).toBe(null);
105121
expect(

0 commit comments

Comments
 (0)