Skip to content

Commit 2cdf04e

Browse files
committed
feat(Compiler): Add file removal tracking in watch-run
-Closes 5072.
1 parent 432d2a3 commit 2cdf04e

File tree

5 files changed

+160
-13
lines changed

5 files changed

+160
-13
lines changed

lib/Compiler.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class Compiler extends Tapable {
128128
/** @type {string|null} */
129129
this.recordsOutputPath = null;
130130
this.records = {};
131+
this.removedFiles = new Set();
131132
/** @type {Map<string, number>} */
132133
this.fileTimestamps = new Map();
133134
/** @type {Map<string, number>} */
@@ -192,6 +193,7 @@ class Compiler extends Tapable {
192193
this.running = true;
193194
this.fileTimestamps = new Map();
194195
this.contextTimestamps = new Map();
196+
this.removedFiles = new Set();
195197
return new Watching(this, watchOptions, handler);
196198
}
197199

@@ -299,7 +301,6 @@ class Compiler extends Tapable {
299301

300302
emitAssets(compilation, callback) {
301303
let outputPath;
302-
303304
const emitFiles = err => {
304305
if (err) return callback(err);
305306

lib/WatchIgnorePlugin.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ class IgnoringWatchFileSystem {
3838
dirsModified,
3939
missingModified,
4040
fileTimestamps,
41-
dirTimestamps
41+
dirTimestamps,
42+
removedFiles
4243
) => {
4344
if (err) return callback(err);
44-
4545
for (const path of ignoredFiles) {
4646
fileTimestamps.set(path, 1);
4747
}
@@ -56,7 +56,8 @@ class IgnoringWatchFileSystem {
5656
dirsModified,
5757
missingModified,
5858
fileTimestamps,
59-
dirTimestamps
59+
dirTimestamps,
60+
removedFiles
6061
);
6162
},
6263
callbackUndelayed

lib/Watching.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ class Watching {
5050
this.compiler.emitAssets(compilation, err => {
5151
if (err) return this._done(err);
5252
if (this.invalid) return this._done();
53-
5453
this.compiler.emitRecords(err => {
5554
if (err) return this._done(err);
5655

@@ -95,7 +94,6 @@ class Watching {
9594
this.handler(err, stats);
9695
return;
9796
}
98-
9997
this.compiler.hooks.done.callAsync(stats, () => {
10098
this.handler(null, stats);
10199
if (!this.closed) {
@@ -124,7 +122,8 @@ class Watching {
124122
contextModified,
125123
missingModified,
126124
fileTimestamps,
127-
contextTimestamps
125+
contextTimestamps,
126+
removedFiles
128127
) => {
129128
this.pausedWatcher = this.watcher;
130129
this.watcher = null;
@@ -133,6 +132,7 @@ class Watching {
133132
}
134133
this.compiler.fileTimestamps = fileTimestamps;
135134
this.compiler.contextTimestamps = contextTimestamps;
135+
this.compiler.removedFiles = removedFiles;
136136
this._invalidate();
137137
},
138138
(fileName, changeTime) => {

lib/node/NodeWatchFileSystem.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,34 @@ class NodeWatchFileSystem {
4444
if (callbackUndelayed) {
4545
this.watcher.once("change", callbackUndelayed);
4646
}
47-
47+
const cachedFiles = files;
48+
const cachedDirs = dirs;
4849
this.watcher.once("aggregated", (changes, removals) => {
4950
changes = changes.concat(removals);
5051
if (this.inputFileSystem && this.inputFileSystem.purge) {
5152
this.inputFileSystem.purge(changes);
5253
}
5354
const times = objectToMap(this.watcher.getTimes());
55+
files = new Set(files);
56+
dirs = new Set(dirs);
57+
missing = new Set(missing);
58+
removals = new Set(removals.filter(file => files.has(file)));
5459
callback(
5560
null,
56-
changes.filter(file => files.includes(file)).sort(),
57-
changes.filter(file => dirs.includes(file)).sort(),
58-
changes.filter(file => missing.includes(file)).sort(),
61+
changes.filter(file => files.has(file)).sort(),
62+
changes.filter(file => dirs.has(file)).sort(),
63+
changes.filter(file => missing.has(file)).sort(),
64+
times,
5965
times,
60-
times
66+
removals
6167
);
6268
});
6369

64-
this.watcher.watch(files.concat(missing), dirs.concat(missing), startTime);
70+
this.watcher.watch(
71+
cachedFiles.concat(missing),
72+
cachedDirs.concat(missing),
73+
startTime
74+
);
6575

6676
if (oldWatcher) {
6777
oldWatcher.close();

test/RemoveFiles.test.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"use strict";
2+
3+
/* globals describe it */
4+
const path = require("path");
5+
const MemoryFs = require("memory-fs");
6+
const webpack = require("../");
7+
const fs = require("fs");
8+
const rimraf = require("rimraf");
9+
10+
const createCompiler = config => {
11+
const compiler = webpack(config);
12+
compiler.outputFileSystem = new MemoryFs();
13+
return compiler;
14+
};
15+
16+
const tempFolderPath = path.join(__dirname, "temp");
17+
const tempFilePath = path.join(tempFolderPath, "temp-file.js");
18+
const tempFile2Path = path.join(tempFolderPath, "temp-file2.js");
19+
20+
const createSingleCompiler = () => {
21+
return createCompiler({
22+
entry: tempFilePath,
23+
watch: true,
24+
output: {
25+
path: tempFolderPath,
26+
filename: "bundle.js"
27+
}
28+
});
29+
};
30+
31+
describe("RemovedFiles", () => {
32+
jest.setTimeout(20000);
33+
34+
function cleanup() {
35+
rimraf.sync(tempFolderPath);
36+
}
37+
38+
beforeAll(() => {
39+
cleanup();
40+
fs.mkdirSync(tempFolderPath);
41+
fs.writeFileSync(
42+
tempFilePath,
43+
"module.exports = function temp() {return 'temp file';};\n require('./temp-file2')",
44+
"utf-8"
45+
);
46+
fs.writeFileSync(
47+
tempFile2Path,
48+
"module.exports = function temp2() {return 'temp file 2';};",
49+
"utf-8"
50+
);
51+
});
52+
afterAll(done => {
53+
cleanup();
54+
done();
55+
});
56+
57+
it("should track removed files when they've been deleted in watchRun", done => {
58+
const compiler = createSingleCompiler();
59+
let watcher;
60+
function handleError(err) {
61+
if (err) done(err);
62+
}
63+
setTimeout(() => {
64+
fs.unlinkSync(tempFilePath, handleError);
65+
}, 2000);
66+
compiler.hooks.watchRun.tap("RemovedFilesTest", (compiler, err) => {
67+
if (err) {
68+
done(err);
69+
}
70+
const removals = Array.from(compiler.removedFiles);
71+
if (removals.length > 0) {
72+
setTimeout(() => {
73+
expect(removals).toContain(tempFilePath);
74+
watcher.close();
75+
done();
76+
}, 100);
77+
}
78+
});
79+
80+
watcher = compiler.watch(
81+
{
82+
aggregateTimeout: 50
83+
},
84+
(err, stats) => {}
85+
);
86+
});
87+
88+
it("should not track removed files when they have not been deleted in watchRun", done => {
89+
const compiler = createSingleCompiler();
90+
let watcher;
91+
compiler.hooks.watchRun.tap("RemovedFilesTest", (compiler, err) => {
92+
if (err) {
93+
done(err);
94+
}
95+
expect(Array.from(compiler.removedFiles)).toHaveLength(0);
96+
done();
97+
watcher.close();
98+
});
99+
100+
watcher = compiler.watch(
101+
{
102+
aggregateTimeout: 50
103+
},
104+
(err, stats) => {}
105+
);
106+
});
107+
108+
it("should not track removed files when files have been modified", done => {
109+
const compiler = createSingleCompiler();
110+
let watcher;
111+
function handleError(err) {
112+
if (err) done(err);
113+
}
114+
let updateFile = () => {
115+
fs.writeFile(tempFile2Path, "hello world", "utf-8", handleError);
116+
};
117+
updateFile();
118+
compiler.hooks.watchRun.tap("RemovedFilesTest", (compiler, err) => {
119+
handleError(err);
120+
setTimeout(() => {
121+
expect(Array.from(compiler.removedFiles)).toHaveLength(0);
122+
watcher.close();
123+
done();
124+
}, 500);
125+
watcher.close();
126+
});
127+
128+
watcher = compiler.watch(
129+
{
130+
aggregateTimeout: 50
131+
},
132+
(err, stats) => {}
133+
);
134+
});
135+
});

0 commit comments

Comments
 (0)