Skip to main content

Watch

启用 Watch 模式。这意味着在初始构建之后,webpack 将继续监听任何已解析文件的更改。Watch 模式默认关闭。

watch: true

webpack-dev-serverwebpack-dev-middleware 里 Watch 模式默认开启。
watch 和 watchOptions | webpack 中文网

在一开始的创建 compiler 对象添加文件的处理方法。

// webpack.js
const webpack = (options, callback) => {
	// ...
	new NodeEnvironmentPlugin({2
		infrastructureLogging: options.infrastructureLogging
	}).apply(compiler);
    // ...
}


// /node/NodeEnvironmentPlugin.js
class NodeEnvironmentPlugin {
	  apply(compiler) {
		  // ...
        compiler.inputFileSystem = new CachedInputFileSystem(
            new NodeJsInputFileSystem(),
            60000
        );
        const inputFileSystem = compiler.inputFileSystem;
        compiler.outputFileSystem = new NodeOutputFileSystem();
        compiler.watchFileSystem = new NodeWatchFileSystem(
            compiler.inputFileSystem
        ); // 生成一个监听器,没有绑定任何事件
        compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
            if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
        });
    }
}

普通模式下执行 compiler.run 方法。在开启了watch 模式后,执行 compiler.watch 方法:

watch(watchOptions, handler) {
	  // ...
	  return new Watching(this, watchOptions, handler);
}

初始化new Watching,内部最终执行到 this._go(),内部与 compiler.run 有些类似,最终会执行 this._done(null, compilation)

class Watching {
  constructor(compiler, watchOptions, handler) {
    // ...
    this.compiler.readRecords((err) => {
      if (err) return this._done(err);

      this._go();
    });
  }

  _go() {
    this.startTime = Date.now();
    this.running = true;
    this.invalid = false;
    this.compiler.hooks.watchRun.callAsync(this.compiler, (err) => {
      if (err) return this._done(err);
      const onCompiled = (err, compilation) => {
        if (err) return this._done(err);
        if (this.invalid) return this._done();

        if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
          return this._done(null, compilation);
        }

        this.compiler.emitAssets(compilation, (err) => {
          if (err) return this._done(err);
          if (this.invalid) return this._done();
          this.compiler.emitRecords((err) => {
            if (err) return this._done(err);

            if (compilation.hooks.needAdditionalPass.call()) {
              compilation.needAdditionalPass = true;

              const stats = new Stats(compilation);
              stats.startTime = this.startTime;
              stats.endTime = Date.now();
              this.compiler.hooks.done.callAsync(stats, (err) => {
                if (err) return this._done(err);

                this.compiler.hooks.additionalPass.callAsync((err) => {
                  if (err) return this._done(err);
                  this.compiler.compile(onCompiled);
                });
              });
              return;
            }
            return this._done(null, compilation);
          });
        });
      };
      this.compiler.compile(onCompiled);
    });
  }
}

this._done中,触发了 hooks.done 。在构建完成之后执行 this.watch 为文件添加监听器。传入fileDependencies,contextDependencies,missingDependencies这 3 个参数。

    _done(err, compilation) {
        this.running = false;
        if (this.invalid) return this._go();

        const stats = compilation ? this._getStats(compilation) : null;
        if (err) {
            this.compiler.hooks.failed.call(err);
            this.handler(err, stats);
            return;
        }
        this.compiler.hooks.done.callAsync(stats, () => {
            this.handler(null, stats);
            if (!this.closed) {
                this.watch(
                    Array.from(compilation.fileDependencies),
                    Array.from(compilation.contextDependencies),
                    Array.from(compilation.missingDependencies)
                );
            }
            for (const cb of this.callbacks) cb();
            this.callbacks.length = 0;
        });
    }

    watch(files, dirs, missing) {
        this.pausedWatcher = null;
        this.watcher = this.compiler.watchFileSystem.watch(
            files,
            dirs,
            missing,
            this.startTime,
            this.watchOptions,
            (
                err,
                filesModified,
                contextModified,
                missingModified,
                fileTimestamps,
                contextTimestamps,
                removedFiles
            ) => {
                this.pausedWatcher = this.watcher;
                this.watcher = null;
                if (err) {
                    return this.handler(err);
                }
                this.compiler.fileTimestamps = fileTimestamps;
                this.compiler.contextTimestamps = contextTimestamps;
                this.compiler.removedFiles = removedFiles;
                if (!this.suspended) {
                    this._invalidate();
                }
            },
            (fileName, changeTime) => {
                this.compiler.hooks.invalid.call(fileName, changeTime);
            }
        );
    }

Watcher 继承至 node 的 EventEmitter,可以进行事件的绑定监听。绑定了 changeaggregated事件。

this.watcher.once('change', callbackUndelayed);
this.watcher.once('aggregated', (changes, removals) => {
  changes = changes.concat(removals);
  if (this.inputFileSystem && this.inputFileSystem.purge) {
    this.inputFileSystem.purge(changes);
  }
  const times = objectToMap(this.watcher.getTimes());
  files = new Set(files);
  dirs = new Set(dirs);
  missing = new Set(missing);
  removals = new Set(removals.filter((file) => files.has(file)));
  callback(
    null,
    changes.filter((file) => files.has(file)).sort(),
    changes.filter((file) => dirs.has(file)).sort(),
    changes.filter((file) => missing.has(file)).sort(),
    times,
    times,
    removals
  );
});

fileWatcher 使用 chokidar 进行监听

var chokidar = require('./chokidar');

this.watcher = chokidar.watch(directoryPath, {
  ignoreInitial: true,
  persistent: true,
  followSymlinks: false,
  depth: 0,
  atomic: false,
  alwaysStat: true,
  ignorePermissionErrors: true,
  ignored: options.ignored,
  usePolling: options.poll ? true : undefined,
  interval: interval,
  binaryInterval: interval,
  disableGlobbing: true
});
this.watcher.on('add', this.onFileAdded.bind(this));
this.watcher.on('addDir', this.onDirectoryAdded.bind(this));
this.watcher.on('change', this.onChange.bind(this));
this.watcher.on('unlink', this.onFileUnlinked.bind(this));
this.watcher.on('unlinkDir', this.onDirectoryUnlinked.bind(this));
this.watcher.on('error', this.onWatcherError.bind(this));

在 done.hooks 中为每个文件增加一个监听器。

#webpack