1, the Compiler

The Compiler module is the backbone engine of WebPack and creates a Compilation instance through all the options passed through the CLI or Node API. It extends from the Tapable class for registering and calling plug-ins. Most user-facing plug-ins are first registered with Compiler.

NodeEnvironmentPlugin is one of the most important modules in Compiler, mainly responsible for file I/O and listening for file content changes. The following is a simple compilation flow chart, which summarizes the key role of NodeEnvironmentPlugin

1.2, NodeEnvironmentPlugin

NodeEnvironmentPlugin consists of four basic file systems, inputFileSystem and outputFileSystem handle file I/O, watchFileSystem listens for file changes, The intermediateFileSystem handles all file system operations that are not considered input or output, such as writing records, caching, or analyzing the output. OutputFileSystem and intermediateFileSystem are ordinary FS, while inputFileSystem and watchFileSystem are optimized FS.

1.2, inputFileSystem

compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
Copy the code

The CachedInputFileSystem encapsulates the method by which node files (information) are read. The CachedInputFileSystem adds a caching mechanism. The caching principle is to store file paths and information in and out of the Map in kv mode, and to split expiration times into n sets of 500ms units. Then use the timer every 500ms to delete the contents corresponding to all file paths in the Set to expire the contents.

CachedInputFileSystem Source address

1.3, watchFileSystem

The watchFileSystem contains a lot of content. If you are interested, you can check out the WatchPack

There are two ways to monitor file changes in watchpack: polledWatching and fs.watch. PolledWatching is a file scan (active) while fs.watch is a system callback (passive).

1.2.1, polledWatching

Poll (number) enables poll scanning, which starts a timer and scans files at a fixed interval (options.poll value). The fs.lstat command is used to obtain the file status as follows:

Stats {
  dev: 2114,
  ino: 48064969,
  mode: 33188,
  nlink: 1,
  uid: 85,
  gid: 100,
  rdev: 0,
  size: 527,
  blksize: 4096,
  blocks: 8,
  atimeMs: 1318289051000.1,
  mtimeMs: 1318289051000.1,
  ctimeMs: 1318289051000.1,
  birthtimeMs: 1318289051000.1,
  atime: Mon, 10 Oct 2011 23:24:11 GMT,
  mtime: Mon, 10 Oct 2011 23:24:11 GMT,
  ctime: Mon, 10 Oct 2011 23:24:11 GMT,
  birthtime: Mon, 10 Oct 2011 23:24:11 GMT }
Copy the code
  • Actime-active time Indicates the access time
  • Mtime-modify time Modify time
  • Ctime-change time The metadata of the file is changed. Such as permissions, owners, etc

The principle is also relatively simple, through the current file state and the last file state mtime, if the file is not the same, it means that the file has been modified.

1.2.2, fs. Watch

Watchpack abstracts two types of Watcher, namely DirectWatcher and RecursiveWatcher, which are essentially fs.watch. The difference is that recursive is true for the latter.

Due to computer performance issues, the number of Watchers is limited, and the watchpack defaults to 2000 for MAC and 10000 for Windows.

const watcherLimit = +process.env.WATCHPACK_WATCHER_LIMIT || (IS_OSX ? 2000 : 10000);
Copy the code

Watchpack only creates DirectWatcher when Watcher exceeds its limits.

// Fast case when we are not reaching the limit
if(! SUPPORTS_RECURSIVE_WATCHING || watcherLimit - watcherCount >= map.size) {// Create watchers for all entries in the map
    for (const [filePath, entry] of map) {
        const w = createDirectWatcher(filePath);
        if (Array.isArray(entry)) {
            for (const item of entry) w.add(item);
        } else{ w.add(entry); }}return;
}
Copy the code

If the limit is not exceeded, Watchpack determines whether RecursiveWatcher is used for multiple files in the current directory or for subdirectories. After all, Both DirectWatcher and RecursiveWatcher builds are just one Watcher, and RecursiveWatcher can listen for a lot more files than DirectWatcher.

// Merge map entries to keep watcher limit
// Create a 10% buffer to be able to enter fast case more often
const plan = reducePlan(map, watcherLimit * 0.9);

// Update watchers for all entries in the map
for (const [filePath, entry] of plan) {
    if (entry.size === 1) {
        for (const [watcher, filePath] of entry) {
            const w = createDirectWatcher(filePath);
            const old = underlyingWatcher.get(watcher);
            if (old === w) continue;
            w.add(watcher);
            if(old ! = =undefined) old.remove(watcher); }}else {
        const filePaths = new Set(entry.values());
        if (filePaths.size > 1) {
            const w = createRecursiveWatcher(filePath);
            for (const [watcher, watcherPath] of entry) {
                const old = underlyingWatcher.get(watcher);
                if (old === w) continue;
                w.add(watcherPath, watcher);
                if(old ! = =undefined) old.remove(watcher); }}else {
            for (const filePath of filePaths) {
                const w = createDirectWatcher(filePath);
                for (const watcher of entry.keys()) {
                    const old = underlyingWatcher.get(watcher);
                    if (old === w) continue;
                    w.add(watcher);
                    if(old ! = =undefined) old.remove(watcher);
                }
            }
        }
    }
}
Copy the code