refactor(@ngtools/webpack): adjust virtual file watching for Webpack 5

The Webpack 5 filesystem watch function has a different return object type than Webpack 4. This change adjusts the behavior based on the used Webpack version.
This commit is contained in:
Charles Lyding 2020-09-18 03:29:26 -04:00 committed by Filipe Silva
parent e5266fdd44
commit 3740d80976

View File

@ -10,6 +10,7 @@ import { Stats } from 'fs';
import { InputFileSystem } from 'webpack';
import { WebpackCompilerHost } from './compiler_host';
import { NodeWatchFileSystemInterface } from './webpack';
import { isWebpackFiveOrHigher } from './webpack-version';
export const NodeWatchFileSystem: NodeWatchFileSystemInterface = require(
'webpack/lib/node/NodeWatchFileSystem');
@ -112,106 +113,187 @@ export class VirtualWatchFileSystemDecorator extends NodeWatchFileSystem {
super(_virtualInputFileSystem);
}
watch = (
files: Iterable<string>,
dirs: Iterable<string>,
missing: Iterable<string>,
startTime: number,
options: {},
callback: Parameters<NodeWatchFileSystemInterface['watch']>[5],
callbackUndelayed: (filename: string, timestamp: number) => void,
): ReturnType<NodeWatchFileSystemInterface['watch']> => {
const reverseReplacements = new Map<string, string>();
const reverseTimestamps = <T>(map: Map<string, T>) => {
for (const entry of Array.from(map.entries())) {
const original = reverseReplacements.get(entry[0]);
if (original) {
map.set(original, entry[1]);
map.delete(entry[0]);
mapReplacements(
original: Iterable<string>,
reverseReplacements: Map<string, string>,
): Iterable<string> {
if (!this._replacements) {
return original;
}
const replacements = this._replacements;
return [...original].map(file => {
if (typeof replacements === 'function') {
const replacement = getSystemPath(replacements(normalize(file)));
if (replacement !== file) {
reverseReplacements.set(replacement, file);
}
}
return map;
};
const newCallbackUndelayed = (filename: string, timestamp: number) => {
const original = reverseReplacements.get(filename);
if (original) {
this._virtualInputFileSystem.purge(original);
callbackUndelayed(original, timestamp);
return replacement;
} else {
callbackUndelayed(filename, timestamp);
}
};
const replacement = replacements.get(normalize(file));
if (replacement) {
const fullReplacement = getSystemPath(replacement);
reverseReplacements.set(fullReplacement, file);
const newCallback: Parameters<NodeWatchFileSystemInterface['watch']>[5] = (
err: Error | null,
filesModified: string[],
contextModified: string[],
missingModified: string[],
fileTimestamps: Map<string, number>,
contextTimestamps: Map<string, number>,
) => {
// Update fileTimestamps with timestamps from virtual files.
const virtualFilesStats = this._virtualInputFileSystem.getVirtualFilesPaths()
.map((fileName) => ({
path: fileName,
mtime: +this._virtualInputFileSystem.statSync(fileName).mtime,
}));
virtualFilesStats.forEach(stats => fileTimestamps.set(stats.path, +stats.mtime));
callback(
err,
filesModified.map(value => reverseReplacements.get(value) || value),
contextModified.map(value => reverseReplacements.get(value) || value),
missingModified.map(value => reverseReplacements.get(value) || value),
reverseTimestamps(fileTimestamps),
reverseTimestamps(contextTimestamps),
);
};
const mapReplacements = (original: Iterable<string>): Iterable<string> => {
if (!this._replacements) {
return original;
}
const replacements = this._replacements;
return [...original].map(file => {
if (typeof replacements === 'function') {
const replacement = getSystemPath(replacements(normalize(file)));
if (replacement !== file) {
reverseReplacements.set(replacement, file);
}
return replacement;
return fullReplacement;
} else {
const replacement = replacements.get(normalize(file));
if (replacement) {
const fullReplacement = getSystemPath(replacement);
reverseReplacements.set(fullReplacement, file);
return fullReplacement;
} else {
return file;
}
return file;
}
});
};
}
});
}
const watcher = super.watch(
mapReplacements(files),
mapReplacements(dirs),
mapReplacements(missing),
startTime,
options,
newCallback,
newCallbackUndelayed,
);
reverseTimestamps<T>(
map: Map<string, T>,
reverseReplacements: Map<string, string>,
): Map<string, T> {
for (const entry of Array.from(map.entries())) {
const original = reverseReplacements.get(entry[0]);
if (original) {
map.set(original, entry[1]);
map.delete(entry[0]);
}
}
return {
close: () => watcher.close(),
pause: () => watcher.pause(),
getFileTimestamps: () => reverseTimestamps(watcher.getFileTimestamps()),
getContextTimestamps: () => reverseTimestamps(watcher.getContextTimestamps()),
return map;
}
createWebpack4Watch() {
return (
files: Iterable<string>,
dirs: Iterable<string>,
missing: Iterable<string>,
startTime: number,
options: {},
callback: Parameters<NodeWatchFileSystemInterface['watch']>[5],
callbackUndelayed: (filename: string, timestamp: number) => void,
): ReturnType<NodeWatchFileSystemInterface['watch']> => {
const reverseReplacements = new Map<string, string>();
const newCallbackUndelayed = (filename: string, timestamp: number) => {
const original = reverseReplacements.get(filename);
if (original) {
this._virtualInputFileSystem.purge(original);
callbackUndelayed(original, timestamp);
} else {
callbackUndelayed(filename, timestamp);
}
};
const newCallback: Parameters<NodeWatchFileSystemInterface['watch']>[5] = (
err: Error | null,
filesModified: string[],
contextModified: string[],
missingModified: string[],
fileTimestamps: Map<string, number>,
contextTimestamps: Map<string, number>,
) => {
// Update fileTimestamps with timestamps from virtual files.
const virtualFilesStats = this._virtualInputFileSystem.getVirtualFilesPaths()
.map((fileName) => ({
path: fileName,
mtime: +this._virtualInputFileSystem.statSync(fileName).mtime,
}));
virtualFilesStats.forEach(stats => fileTimestamps.set(stats.path, +stats.mtime));
callback(
err,
filesModified.map(value => reverseReplacements.get(value) || value),
contextModified.map(value => reverseReplacements.get(value) || value),
missingModified.map(value => reverseReplacements.get(value) || value),
this.reverseTimestamps(fileTimestamps, reverseReplacements),
this.reverseTimestamps(contextTimestamps, reverseReplacements),
);
};
const watcher = super.watch(
this.mapReplacements(files, reverseReplacements),
this.mapReplacements(dirs, reverseReplacements),
this.mapReplacements(missing, reverseReplacements),
startTime,
options,
newCallback,
newCallbackUndelayed,
);
return {
close: () => watcher.close(),
pause: () => watcher.pause(),
getFileTimestamps: () =>
this.reverseTimestamps(watcher.getFileTimestamps(), reverseReplacements),
getContextTimestamps: () =>
this.reverseTimestamps(watcher.getContextTimestamps(), reverseReplacements),
};
};
}
createWebpack5Watch() {
return (
files: Iterable<string>,
dirs: Iterable<string>,
missing: Iterable<string>,
startTime: number,
options: {},
callback: Parameters<NodeWatchFileSystemInterface['watch']>[5],
callbackUndelayed: (filename: string, timestamp: number) => void,
): ReturnType<NodeWatchFileSystemInterface['watch']> => {
const reverseReplacements = new Map<string, string>();
const newCallbackUndelayed = (filename: string, timestamp: number) => {
const original = reverseReplacements.get(filename);
if (original) {
this._virtualInputFileSystem.purge(original);
callbackUndelayed(original, timestamp);
} else {
callbackUndelayed(filename, timestamp);
}
};
const newCallback = (
err: Error,
// tslint:disable-next-line: no-any
fileTimeInfoEntries: Map<string, any>,
// tslint:disable-next-line: no-any
contextTimeInfoEntries: Map<string, any>,
missing: Set<string>,
removals: Set<string>,
) => {
// Update fileTimestamps with timestamps from virtual files.
const virtualFilesStats = this._virtualInputFileSystem.getVirtualFilesPaths()
.map((fileName) => ({
path: fileName,
mtime: +this._virtualInputFileSystem.statSync(fileName).mtime,
}));
virtualFilesStats.forEach(stats => fileTimeInfoEntries.set(stats.path, +stats.mtime));
callback(
err,
this.reverseTimestamps(fileTimeInfoEntries, reverseReplacements),
this.reverseTimestamps(contextTimeInfoEntries, reverseReplacements),
new Set([...missing].map(value => reverseReplacements.get(value) || value)),
new Set([...removals].map(value => reverseReplacements.get(value) || value)),
);
};
const watcher = super.watch(
this.mapReplacements(files, reverseReplacements),
this.mapReplacements(dirs, reverseReplacements),
this.mapReplacements(missing, reverseReplacements),
startTime,
options,
newCallback,
newCallbackUndelayed,
);
return {
close: () => watcher.close(),
pause: () => watcher.pause(),
getFileTimeInfoEntries: () =>
this.reverseTimestamps(watcher.getFileTimeInfoEntries(), reverseReplacements),
getContextTimeInfoEntries: () =>
this.reverseTimestamps(watcher.getContextTimeInfoEntries(), reverseReplacements),
};
};
}
watch = isWebpackFiveOrHigher() ? this.createWebpack5Watch : this.createWebpack4Watch();
}