Files

169 lines
3.8 KiB
TypeScript

// From packages\babel-cli\src\babel\watcher.ts
import { createRequire } from "module";
import path from "path";
import type { WatchOptions, FSWatcher } from "chokidar";
const fileToDeps = new Map<string, Set<string>>();
const depToFiles = new Map<string, Set<string>>();
let isWatchMode = false;
let watcher: FSWatcher;
const watchQueue = new Set<string>();
let hasStarted = false;
export function enable({ enableGlobbing }: { enableGlobbing: boolean }) {
isWatchMode = true;
const { FSWatcher } = requireChokidar();
const options: WatchOptions = {
disableGlobbing: !enableGlobbing,
persistent: true,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 50,
pollInterval: 10,
},
};
watcher = new FSWatcher(options);
watcher.on("unlink", unwatchFile);
}
export function startWatcher() {
hasStarted = true;
for (const dep of watchQueue) {
watcher.add(dep);
}
watchQueue.clear();
watcher.on("ready", () => {
console.log("The watcher is ready.");
});
}
export function watch(filename: string): void {
if (!isWatchMode) {
throw new Error(
"Internal Babel error: .watch called when not in watch mode.",
);
}
if (!hasStarted) {
watchQueue.add(path.resolve(filename));
} else {
watcher.add(path.resolve(filename));
}
}
/**
* Call @param callback whenever a dependency (source file)/
* external dependency (non-source file) changes.
*
* Handles mapping external dependencies to their corresponding
* dependencies.
*/
export function onFilesChange(
callback: (filenames: string[], event: string, cause: string) => void,
): void {
if (!isWatchMode) {
throw new Error(
"Internal Babel error: .onFilesChange called when not in watch mode.",
);
}
watcher.on("all", (event, filename) => {
if (event !== "change" && event !== "add") return;
const absoluteFile = path.resolve(filename);
callback(
[absoluteFile, ...(depToFiles.get(absoluteFile) ?? [])],
event,
absoluteFile,
);
});
}
export function updateExternalDependencies(
filename: string,
dependencies: Set<string>,
) {
if (!isWatchMode) return;
// Use absolute paths
const absFilename = path.resolve(filename);
const absDependencies = new Set(
Array.from(dependencies, dep => path.resolve(dep)),
);
const deps = fileToDeps.get(absFilename);
if (deps) {
for (const dep of deps) {
if (!absDependencies.has(dep)) {
removeFileDependency(absFilename, dep);
}
}
}
for (const dep of absDependencies) {
let deps = depToFiles.get(dep);
if (!deps) {
depToFiles.set(dep, (deps = new Set()));
if (!hasStarted) {
watchQueue.add(dep);
} else {
watcher.add(dep);
}
}
deps.add(absFilename);
}
fileToDeps.set(absFilename, absDependencies);
}
function removeFileDependency(filename: string, dep: string) {
const deps = depToFiles.get(dep) as Set<string>;
deps.delete(filename);
if (deps.size === 0) {
depToFiles.delete(dep);
if (!hasStarted) {
watchQueue.delete(dep);
} else {
watcher.unwatch(dep);
}
}
}
function unwatchFile(filename: string) {
const deps = fileToDeps.get(filename);
if (!deps) return;
for (const dep of deps) {
removeFileDependency(filename, dep);
}
fileToDeps.delete(filename);
}
function requireChokidar(): any {
const require = createRequire(import.meta.url);
try {
return process.env.BABEL_8_BREAKING
? require("chokidar")
: parseInt(process.versions.node) >= 8
? require("chokidar")
: require("@nicolo-ribaudo/chokidar-2");
} catch (err) {
console.error(
"The optional dependency chokidar failed to install and is required for " +
"--watch. Chokidar is likely not supported on your platform.",
);
throw err;
}
}