webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
216 lines (202 loc) • 6.49 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
;
const util = require("util");
const Watchpack = require("watchpack");
/** @typedef {import("watchpack").TimeInfoEntries} TimeInfoEntries */
/** @typedef {import("watchpack").WatchOptions} WatchOptions */
/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("../util/fs").WatchMethod} WatchMethod */
/** @typedef {import("../util/fs").Changes} Changes */
/** @typedef {import("../util/fs").Removals} Removals */
class NodeWatchFileSystem {
/**
* Creates an instance of NodeWatchFileSystem.
* @param {InputFileSystem} inputFileSystem input filesystem
*/
constructor(inputFileSystem) {
/** @type {InputFileSystem} */
this.inputFileSystem = inputFileSystem;
/** @type {WatchOptions} */
this.watcherOptions = {
aggregateTimeout: 0
};
/** @type {Watchpack | null} */
this.watcher = new Watchpack(this.watcherOptions);
}
/** @type {WatchMethod} */
watch(
files,
directories,
missing,
startTime,
options,
callback,
callbackUndelayed
) {
if (!files || typeof files[Symbol.iterator] !== "function") {
throw new Error("Invalid arguments: 'files'");
}
if (!directories || typeof directories[Symbol.iterator] !== "function") {
throw new Error("Invalid arguments: 'directories'");
}
if (!missing || typeof missing[Symbol.iterator] !== "function") {
throw new Error("Invalid arguments: 'missing'");
}
if (typeof callback !== "function") {
throw new Error("Invalid arguments: 'callback'");
}
if (typeof startTime !== "number" && startTime) {
throw new Error("Invalid arguments: 'startTime'");
}
if (typeof options !== "object") {
throw new Error("Invalid arguments: 'options'");
}
if (typeof callbackUndelayed !== "function" && callbackUndelayed) {
throw new Error("Invalid arguments: 'callbackUndelayed'");
}
const oldWatcher = this.watcher;
this.watcher = new Watchpack(options);
if (callbackUndelayed) {
this.watcher.once("change", callbackUndelayed);
}
const fetchTimeInfo = () => {
/** @type {TimeInfoEntries} */
const fileTimeInfoEntries = new Map();
/** @type {TimeInfoEntries} */
const contextTimeInfoEntries = new Map();
if (this.watcher) {
this.watcher.collectTimeInfoEntries(
fileTimeInfoEntries,
contextTimeInfoEntries
);
}
return { fileTimeInfoEntries, contextTimeInfoEntries };
};
const directoriesSet =
directories instanceof Set ? directories : new Set(directories);
// Watchpack reports a watched directory (a context dependency) in
// `changes` whenever its contents change, alongside the individual
// file events. The default `fs.purge(dir)` matches cache keys by
// prefix, so it would wipe the stat cache of every file inside the
// directory even though only file-level events actually invalidate
// file stats. For directories we explicitly watch, purge only the
// directory's own entry (`{ exact: true }`, enhanced-resolve >=
// 5.22.0); file-level events in the same aggregated batch still
// handle file stats and the parent readdir invalidation.
/**
* @param {Changes | null | undefined} changes changes set
* @param {Removals | null | undefined} removals removals set
*/
const purgeChanges = (changes, removals) => {
const fs = this.inputFileSystem;
if (!fs || !fs.purge) return;
if (changes) {
for (const item of changes) {
if (directoriesSet.has(item)) {
fs.purge(item, { exact: true });
} else {
fs.purge(item);
}
}
}
if (removals) {
for (const item of removals) {
fs.purge(item);
}
}
};
this.watcher.once(
"aggregated",
/**
* Handles the callback logic for this hook.
* @param {Changes} changes changes
* @param {Removals} removals removals
*/
(changes, removals) => {
// pause emitting events (avoids clearing aggregated changes and removals on timeout)
/** @type {Watchpack} */
(this.watcher).pause();
purgeChanges(changes, removals);
const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo();
callback(
null,
fileTimeInfoEntries,
contextTimeInfoEntries,
changes,
removals
);
}
);
this.watcher.watch({ files, directories, missing, startTime });
if (oldWatcher) {
oldWatcher.close();
}
return {
close: () => {
if (this.watcher) {
this.watcher.close();
this.watcher = null;
}
},
pause: () => {
if (this.watcher) {
this.watcher.pause();
}
},
getAggregatedRemovals: util.deprecate(
() => {
const items = this.watcher && this.watcher.aggregatedRemovals;
const fs = this.inputFileSystem;
if (items && fs && fs.purge) {
for (const item of items) {
fs.purge(item);
}
}
return items;
},
"Watcher.getAggregatedRemovals is deprecated in favor of Watcher.getInfo since that's more performant.",
"DEP_WEBPACK_WATCHER_GET_AGGREGATED_REMOVALS"
),
getAggregatedChanges: util.deprecate(
() => {
const items = this.watcher && this.watcher.aggregatedChanges;
const fs = this.inputFileSystem;
if (items && fs && fs.purge) {
for (const item of items) {
fs.purge(item);
}
}
return items;
},
"Watcher.getAggregatedChanges is deprecated in favor of Watcher.getInfo since that's more performant.",
"DEP_WEBPACK_WATCHER_GET_AGGREGATED_CHANGES"
),
getFileTimeInfoEntries: util.deprecate(
() => fetchTimeInfo().fileTimeInfoEntries,
"Watcher.getFileTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.",
"DEP_WEBPACK_WATCHER_FILE_TIME_INFO_ENTRIES"
),
getContextTimeInfoEntries: util.deprecate(
() => fetchTimeInfo().contextTimeInfoEntries,
"Watcher.getContextTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.",
"DEP_WEBPACK_WATCHER_CONTEXT_TIME_INFO_ENTRIES"
),
getInfo: () => {
const removals = this.watcher && this.watcher.aggregatedRemovals;
const changes = this.watcher && this.watcher.aggregatedChanges;
purgeChanges(changes, removals);
const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo();
return {
changes,
removals,
fileTimeInfoEntries,
contextTimeInfoEntries
};
}
};
}
}
module.exports = NodeWatchFileSystem;